diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 4f9e26b..1dccdac 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -67,16 +67,16 @@ std::string AppConfig::get_hms_host() std::string host = ""; #if !QDT_RELEASE_TO_PUBLIC if (sel == ENV_DEV_HOST) - host = "e-dev.qidilab.net"; + host = "e-dev.qiditech.net"; else if (sel == ENV_QAT_HOST) - host = "e-qa.qidilab.net"; + host = "e-qa.qiditech.net"; else if (sel == ENV_PRE_HOST) - host = "e-pre.qidilab.net"; + host = "e-pre.qiditech.net"; else if (sel == ENV_PRODUCT_HOST) - host = "e.qidilab.com"; + host = "e.qiditech.com"; return host; #else - return "e.qidilab.com"; + return "e.qiditech.com"; #endif } @@ -172,8 +172,16 @@ void AppConfig::set_defaults() if (get("zoom_to_mouse").empty()) set_bool("zoom_to_mouse", false); - if (get("user_bed_type").empty()) + if (get("show_shells_in_preview").empty()) + set_bool("show_shells_in_preview", true); + if (get("enable_lod").empty()) + set_bool("enable_lod", true); + if (get("enable_opengl_multi_instance").empty()) + set_bool("enable_opengl_multi_instance", true); + if (get("user_bed_type").empty()) set_bool("user_bed_type", true); + if (get("grabber_size_factor").empty()) + set("grabber_size_factor", "1.0"); //#ifdef SUPPORT_SHOW_HINTS if (get("show_hints").empty()) set_bool("show_hints", true); @@ -265,6 +273,10 @@ void AppConfig::set_defaults() set_bool("show_home_page", true); } + if (get("show_print_history").empty()) { + set_bool("show_print_history", true); + } + if (get("show_printable_box").empty()) { set_bool("show_printable_box", true); } @@ -312,7 +324,7 @@ void AppConfig::set_defaults() if (get("mouse_wheel").empty()) { set("mouse_wheel", "0"); } - + if (get("max_recent_count").empty()) { set("max_recent_count", "18"); } @@ -379,6 +391,17 @@ void AppConfig::set_defaults() if (get("print", "timelapse").empty()) { set_str("print", "timelapse", "1"); } + + if (get("enable_step_mesh_setting").empty()) { + set_bool("enable_step_mesh_setting", true); + } + if (get("linear_defletion").empty()) { + set("linear_defletion", "0.003"); + } + if (get("angle_defletion").empty()) { + set("angle_defletion", "0.5"); + } + // 10 if (get("machine_list_net").empty()) set("machine_list_net", "0"); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index d171564..fbcbced 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -80,6 +80,7 @@ public: { std::string value; this->get(section, key, value); return value; } std::string get(const std::string &key) const { std::string value; this->get("app", key, value); return value; } + bool get_bool(const std::string &key) const { return this->get(key) == "true" || this->get(key) == "1"; } void set(const std::string §ion, const std::string &key, const std::string &value) { #ifndef NDEBUG diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 8f60823..2ecb379 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -81,12 +81,11 @@ using ItemGroup = std::vector>; const double BIG_ITEM_TRESHOLD = 0.02; #define VITRIFY_TEMP_DIFF_THRSH 15 // bed temp can be higher than vitrify temp, but not higher than this thresh -void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig* print_cfg, const ArrangePolygons& selected) +void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig & print_cfg, const ArrangePolygons& selected) { - double skirt_distance = get_real_skirt_dist(*print_cfg); // Note: skirt_distance is now defined between outermost brim and skirt, not the object and skirt. // So we can't do max but do adding instead. - params.brim_skirt_distance = skirt_distance; + params.brim_skirt_distance = get_real_skirt_dist(print_cfg); params.bed_shrink_x += params.brim_skirt_distance; params.bed_shrink_y += params.brim_skirt_distance; if (params.is_seq_print) { @@ -97,14 +96,14 @@ void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig* prin } else params.min_obj_distance = std::max(params.min_obj_distance, scaled(params.cleareance_radius + 0.001)); // +0.001mm to avoid clearance check fail due to rounding error - + // for sequential print, we need to inflate the bed because cleareance_radius is so large params.bed_shrink_x -= unscale_(params.min_obj_distance / 2); params.bed_shrink_y -= unscale_(params.min_obj_distance / 2); } } -void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, ArrangeParams& params) { +void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPrintConfig & print_cfg, ArrangeParams& params) { // do not inflate brim_width. Objects are allowed to have overlapped brim. Points bedpts = get_shrink_bedpts(print_cfg, params); BoundingBox bedbb = Polygon(bedpts).bounding_box(); @@ -122,7 +121,7 @@ void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPri }); } -void update_unselected_items_inflation(ArrangePolygons& unselected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params) +void update_unselected_items_inflation(ArrangePolygons& unselected, const DynamicPrintConfig & print_cfg, const ArrangeParams& params) { coord_t exclusion_gap = scale_(1.f); if (params.is_seq_print) { @@ -130,7 +129,7 @@ void update_unselected_items_inflation(ArrangePolygons& unselected, const Dynami exclusion_gap = std::max(exclusion_gap, params.min_obj_distance / 2 + scaled(params.bed_shrink_x + 1.f)); // +1mm gap so the exclusion region is not too close // dont forget to move the excluded region for (auto& region : unselected) { - if (region.is_virt_object) region.poly.translate(scaled(params.bed_shrink_x), scaled(params.bed_shrink_y)); + if (region.is_virt_object) region.poly.translate(scaled(params.bed_shrink_x+1.f), scaled(params.bed_shrink_y+1.f)); } } // For occulusion regions, inflation should be larger to prevent genrating brim on them. @@ -143,114 +142,10 @@ void update_unselected_items_inflation(ArrangePolygons& unselected, const Dynami : (ap.is_extrusion_cali_object ? 0 : exclusion_gap); }); } -void update_selected_items_axis_align(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params) +//it will be accurate after call update_params +Points get_shrink_bedpts(const DynamicPrintConfig & print_cfg, const ArrangeParams& params) { - // now only need to consider "Align to x axis" - if (!params.align_to_y_axis) - return; - - for (ArrangePolygon& ap : selected) { - bool validResult = false; - double angle = 0.0; - { - const auto& pts = ap.transformed_poly().contour; - int lpt = pts.size(); - double a00 = 0, a10 = 0, a01 = 0, a20 = 0, a11 = 0, a02 = 0, a30 = 0, a21 = 0, a12 = 0, a03 = 0; - double xi, yi, xi2, yi2, xi_1, yi_1, xi_12, yi_12, dxy, xii_1, yii_1; - xi_1 = pts.back().x(); - yi_1 = pts.back().y(); - - xi_12 = xi_1 * xi_1; - yi_12 = yi_1 * yi_1; - - for (int i = 0; i < lpt; i++) { - xi = pts[i].x(); - yi = pts[i].y(); - - xi2 = xi * xi; - yi2 = yi * yi; - dxy = xi_1 * yi - xi * yi_1; - xii_1 = xi_1 + xi; - yii_1 = yi_1 + yi; - - a00 += dxy; - a10 += dxy * xii_1; - a01 += dxy * yii_1; - a20 += dxy * (xi_1 * xii_1 + xi2); - a11 += dxy * (xi_1 * (yii_1 + yi_1) + xi * (yii_1 + yi)); - a02 += dxy * (yi_1 * yii_1 + yi2); - a30 += dxy * xii_1 * (xi_12 + xi2); - a03 += dxy * yii_1 * (yi_12 + yi2); - a21 += dxy * (xi_12 * (3 * yi_1 + yi) + 2 * xi * xi_1 * yii_1 + xi2 * (yi_1 + 3 * yi)); - a12 += dxy * (yi_12 * (3 * xi_1 + xi) + 2 * yi * yi_1 * xii_1 + yi2 * (xi_1 + 3 * xi)); - xi_1 = xi; - yi_1 = yi; - xi_12 = xi2; - yi_12 = yi2; - } - - if (std::abs(a00) > EPSILON) { - double db1_2, db1_6, db1_12, db1_24, db1_20, db1_60; - double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03; - if (a00 > 0) { - db1_2 = 0.5; - db1_6 = 0.16666666666666666666666666666667; - db1_12 = 0.083333333333333333333333333333333; - db1_24 = 0.041666666666666666666666666666667; - db1_20 = 0.05; - db1_60 = 0.016666666666666666666666666666667; - } - else { - db1_2 = -0.5; - db1_6 = -0.16666666666666666666666666666667; - db1_12 = -0.083333333333333333333333333333333; - db1_24 = -0.041666666666666666666666666666667; - db1_20 = -0.05; - db1_60 = -0.016666666666666666666666666666667; - } - m00 = a00 * db1_2; - m10 = a10 * db1_6; - m01 = a01 * db1_6; - m20 = a20 * db1_12; - m11 = a11 * db1_24; - m02 = a02 * db1_12; - m30 = a30 * db1_20; - m21 = a21 * db1_60; - m12 = a12 * db1_60; - m03 = a03 * db1_20; - - double cx = m10 / m00; - double cy = m01 / m00; - - double a = m20 / m00 - cx * cx; - double b = m11 / m00 - cx * cy; - double c = m02 / m00 - cy * cy; - - //if a and c are close, there is no dominant axis, then do not rotate - // ratio is always no more than 1 - double ratio = std::abs(a) > std::abs(c) ? std::abs(c / a) : - std::abs(c) > 0 ? std::abs(a / c) : 0; - if (ratio>0.66) { - validResult = false; - } - else { - angle = std::atan2(2 * b, (a - c)) / 2; - angle = PI / 2 - angle; - // if the angle is close to PI or -PI, it means the object is vertical, then do not rotate - if (std::abs(std::abs(angle) - PI) < 0.01) - angle = 0; - validResult = true; - } - } - } - if (validResult) { ap.rotation += angle; } - } -} - -//it will bed accurate after call update_params -Points get_shrink_bedpts(const DynamicPrintConfig* print_cfg, const ArrangeParams& params) -{ - Points bedpts = get_bed_shape(*print_cfg); + Points bedpts = get_bed_shape(print_cfg); // shrink bed by moving to center by dist auto shrinkFun = [](Points& bedpts, double dist, int direction) { #define SGN(x) ((x) >= 0 ? 1 : -1) @@ -986,15 +881,25 @@ void _arrange( // Use the minimum bounding box rotation as a starting point. // TODO: This only works for convex hull. If we ever switch to concave // polygon nesting, a convex hull needs to be calculated. - if (params.allow_rotations) { + if (params.align_to_y_axis) { for (auto &itm : shapes) { - itm.rotation(min_area_boundingbox_rotation(itm.transformedShape())); + auto angle = min_area_boundingbox_rotation(itm.transformedShape()); + itm.rotate(angle + PI / 2); + } + } + else if (params.allow_rotations) { + for (auto &itm : shapes) { + auto angle = min_area_boundingbox_rotation(itm.transformedShape()); + BOOST_LOG_TRIVIAL(debug) << itm.name << " min_area_boundingbox_rotation=" << angle << ", original angle=" << itm.rotation(); + itm.rotate(angle); // If the item is too big, try to find a rotation that makes it fit if constexpr (std::is_same_v) { auto bb = itm.boundingBox(); - if (bb.width() >= bin.width() || bb.height() >= bin.height()) + if (bb.width() >= bin.width() || bb.height() >= bin.height()) { + BOOST_LOG_TRIVIAL(debug) << itm.name << " too big, rotate to " << fit_into_box_rotation(itm.transformedShape(), bin); itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin)); + } } } } diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 87be930..f239288 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -175,15 +175,13 @@ struct ArrangeParams { }; -void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig* print_cfg, const ArrangePolygons& selected); +void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig & print_cfg, const ArrangePolygons& selected); -void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, ArrangeParams& params); +void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPrintConfig & print_cfg, ArrangeParams& params); -void update_unselected_items_inflation(ArrangePolygons& unselected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params); +void update_unselected_items_inflation(ArrangePolygons& unselected, const DynamicPrintConfig & print_cfg, const ArrangeParams& params); -void update_selected_items_axis_align(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params); - -Points get_shrink_bedpts(const DynamicPrintConfig* print_cfg, const ArrangeParams& params); +Points get_shrink_bedpts(const DynamicPrintConfig & print_cfg, const ArrangeParams& params); /** * \brief Arranges the input polygons. diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index c6bddd6..c2a84bb 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -16,9 +16,9 @@ public: PointClass min; PointClass max; bool defined; - + BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {} - BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : + BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : min(p1), max(p1), defined(false) { merge(p2); merge(p3); } @@ -27,7 +27,7 @@ public: template> BoundingBoxBase(It from, It to) { - construct(*this, from, to); + construct(*this, from, to); } BoundingBoxBase(const std::vector &points) @@ -107,7 +107,7 @@ class BoundingBox3Base : public BoundingBoxBase { public: BoundingBox3Base() : BoundingBoxBase() {} - BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : + BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase(pmin, pmax) { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : @@ -206,7 +206,7 @@ public: // Align the min corner to a grid of cell_size x cell_size cells, // to encompass the original bounding box. void align_to_grid(const coord_t cell_size); - + BoundingBox() : BoundingBoxBase() {} BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {} BoundingBox(const Points &points) : BoundingBoxBase(points) {} @@ -216,7 +216,9 @@ public: friend BoundingBox get_extents_rotated(const Points &points, double angle); }; -class BoundingBox3 : public BoundingBox3Base +using BoundingBoxes = std::vector; + +class BoundingBox3 : public BoundingBox3Base { public: BoundingBox3() : BoundingBox3Base() {} @@ -224,7 +226,7 @@ public: BoundingBox3(const Points3& points) : BoundingBox3Base(points) {} }; -class BoundingBoxf : public BoundingBoxBase +class BoundingBoxf : public BoundingBoxBase { public: BoundingBoxf() : BoundingBoxBase() {} @@ -232,7 +234,7 @@ public: BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {} }; -class BoundingBoxf3 : public BoundingBox3Base +class BoundingBoxf3 : public BoundingBox3Base { public: using BoundingBox3Base::BoundingBox3Base; diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 6fff7e0..d738e4f 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -593,8 +594,8 @@ double getadhesionCoeff(const PrintObject* printObject) if (Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "PETG" || Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "PCTG") { adhesionCoeff = 2; - } - else if (Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "TPU") { + } else if (Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "TPU" || + Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "TPU-AMS") { adhesionCoeff = 0.5; } } @@ -755,6 +756,41 @@ double configBrimWidthByVolumes(double deltaT, double adhension, double maxSpeed return brim_width; } +static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim) +{ + ExPolygons mouse_ears_ex; + BrimPoints brim_ear_points = object->model_object()->brim_points; + if (brim_ear_points.size() <= 0) { + return mouse_ears_ex; + } + const Geometry::Transformation& trsf = object->model_object()->instances[0]->get_transformation(); + Transform3d model_trsf = trsf.get_matrix(true); + const Point ¢er_offset = object->center_offset(); + model_trsf = model_trsf.pretranslate(Vec3d(- unscale(center_offset.x()), - unscale(center_offset.y()), 0)); + for (auto &pt : brim_ear_points) { + Vec3f world_pos = pt.transform(trsf.get_matrix()); + if ( world_pos.z() > 0) continue; + Polygon point_round; + float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2; + if (is_outer_brim) { + double flowWidthScale = flowWidth / SCALING_FACTOR; + brim_width = floor(brim_width / flowWidthScale / 2) * flowWidthScale * 2; + } + coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing()); + for (size_t i = 0; i < POLY_SIDE_COUNT; i++) { + double angle = (2.0 * PI * i) / POLY_SIDE_COUNT; + point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle)); + } + mouse_ears_ex.emplace_back(); + mouse_ears_ex.back().contour = point_round; + Vec3f pos = pt.transform(model_trsf); + int32_t pt_x = scale_(pos.x()); + int32_t pt_y = scale_(pos.y()); + mouse_ears_ex.back().contour.translate(Point(pt_x, pt_y)); + } + return mouse_ears_ex; +} + //QDS: config brimwidth by group of volumes double configBrimWidthByVolumeGroups(double adhension, double maxSpeed, const std::vector modelVolumePtrs, const ExPolygons& expolys, double &groupHeight) { @@ -812,6 +848,7 @@ static ExPolygons outer_inner_brim_area(const Print& print, std::vector& printExtruders) { unsigned int support_material_extruder = printExtruders.front() + 1; + Flow flow = print.brim_flow(); ExPolygons brim_area; ExPolygons no_brim_area; @@ -841,7 +878,7 @@ static ExPolygons outer_inner_brim_area(const Print& print, } hole_index = -1; }; - + const float scaled_flow_width = print.brim_flow().scaled_spacing(); for (unsigned int extruderNo : printExtruders) { ++extruderNo; for (const auto& objectWithExtruder : objPrintVec) { @@ -850,10 +887,14 @@ static ExPolygons outer_inner_brim_area(const Print& print, float brim_offset = scale_(object->config().brim_object_gap.value); double flowWidth = print.brim_flow().scaled_spacing() * SCALING_FACTOR; float brim_width = scale_(floor(object->config().brim_width.value / flowWidth / 2) * flowWidth * 2); - const float scaled_flow_width = print.brim_flow().scaled_spacing(); const float scaled_additional_brim_width = scale_(floor(5 / flowWidth / 2) * flowWidth * 2); const float scaled_half_min_adh_length = scale_(1.1); bool has_brim_auto = object->config().brim_type == btAutoBrim; + bool use_brim_ears = object->config().brim_type == btBrimEars; + // if (object->model_object()->brim_points.size()>0 && has_brim_auto) + // use_brim_ears = true; + const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_brim_ears; + const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_brim_ears; ExPolygons brim_area_object; ExPolygons no_brim_area_object; @@ -919,7 +960,7 @@ static ExPolygons outer_inner_brim_area(const Print& print, Polygons ex_poly_holes_reversed = ex_poly.holes; polygons_reverse(ex_poly_holes_reversed); - if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner || brim_type == BrimType::btAutoBrim) { + if (has_outer_brim) { // QDS: to find whether an island is in a hole of its object int contour_hole_index = -1; @@ -927,12 +968,18 @@ static ExPolygons outer_inner_brim_area(const Print& print, // QDS: inner and outer boundary are offset from the same polygon incase of round off error. auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION); + ExPolygons outerExpoly; + if (use_brim_ears) { + outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true); + //outerExpoly = offset_ex(outerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION); + }else { + outerExpoly = offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION); + } - if (contour_hole_index < 0) - append(brim_area_object, diff_ex(offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION), innerExpoly)); - else - { - ExPolygons brimBeforeClip = diff_ex(offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION), innerExpoly); + if (contour_hole_index < 0) { + append(brim_area_object, diff_ex(outerExpoly, innerExpoly)); + }else { + ExPolygons brimBeforeClip = diff_ex(outerExpoly, innerExpoly); // QDS: an island's brim should not be outside of its belonging hole Polygons selectedHole = { holes_area[contour_hole_index] }; @@ -940,16 +987,23 @@ static ExPolygons outer_inner_brim_area(const Print& print, append(brim_area_object, clippedBrim); } } - if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) { - append(brim_area_object, diff_ex(offset_ex(ex_poly_holes_reversed, -brim_offset), offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset))); + if (has_inner_brim) { + ExPolygons outerExpoly; + auto innerExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset); + if (use_brim_ears) { + outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, false); + }else { + outerExpoly = offset_ex(ex_poly_holes_reversed, -brim_offset); + } + append(brim_area_object, diff_ex(outerExpoly, innerExpoly)); } - if (brim_type != BrimType::btInnerOnly && brim_type != BrimType::btOuterAndInner) { + if (!has_inner_brim) { // QDS: brim should be apart from holes append(no_brim_area_object, diff_ex(ex_poly_holes_reversed, offset_ex(ex_poly_holes_reversed, -scale_(5.)))); } - if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) + if (!has_outer_brim) append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly_holes_reversed)); - if (brim_type == BrimType::btNoBrim) + if (!has_inner_brim && !has_outer_brim) append(no_brim_area_object, offset_ex(ex_poly_holes_reversed, -no_brim_offset)); append(holes_object, ex_poly_holes_reversed); } @@ -978,18 +1032,12 @@ static ExPolygons outer_inner_brim_area(const Print& print, } if (support_material_extruder == extruderNo && brimToWrite.at(object->id()).sup) { - if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerNormal) { - for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing()) { + if (!object->support_layers().empty()) { + for (const auto &support_contour : object->support_layers().front()->support_islands) { no_brim_area_support.emplace_back(support_contour); } } - if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerTree) { - for (const ExPolygon& ex_poly : object->support_layers().front()->lslices) { - no_brim_area_support.emplace_back(ex_poly.contour); - } - } - brimToWrite.at(object->id()).sup = false; for (const PrintInstance& instance : object->instances()) { if (!brim_area_support.empty()) @@ -1005,6 +1053,7 @@ static ExPolygons outer_inner_brim_area(const Print& print, if (!bedExPoly.empty()){ no_brim_area.push_back(bedExPoly.front()); } + no_brim_area = offset2_ex(no_brim_area, scaled_flow_width, -scaled_flow_width); // connect scattered small areas to prevent generating very small brims for (const PrintObject* object : print.objects()) { if (brimAreaMap.find(object->id()) != brimAreaMap.end()) { @@ -1027,22 +1076,33 @@ static ExPolygons outer_inner_brim_area(const Print& print, } } - auto tempArea = brimAreaMap[object->id()]; + auto tempAreas = brimAreaMap[object->id()]; brimAreaMap[object->id()].clear(); + brimAreaMap[object->id()].reserve(tempAreas.size()); + brim_area.reserve(brim_area.size() + tempAreas.size()); - for (int ia = 0; ia != tempArea.size(); ++ia) { - // find this object's other brim area - ExPolygons otherExPoly; - for (int iao = 0; iao != tempArea.size(); ++iao) - if (iao != ia) otherExPoly.push_back(tempArea[iao]); + std::vector retained{}; + tbb::spin_mutex brimMutex; + tbb::parallel_for(tbb::blocked_range(0, tempAreas.size()), + [&tempAreas, &objectIslands, &print, &otherExPolys, &brimMutex, &retained](const tbb::blocked_range& range) { + for (auto ia = range.begin(); ia != range.end(); ++ia) { + tbb::spin_mutex::scoped_lock lock; + ExPolygons otherExPoly; - auto offsetedTa = offset_ex(tempArea[ia], print.brim_flow().scaled_spacing() * 2, jtRound, SCALED_RESOLUTION); - if (!intersection_ex(offsetedTa, objectIslands).empty() || - !intersection_ex(offsetedTa, otherExPoly).empty() || - !intersection_ex(offsetedTa, otherExPolys).empty()) - brimAreaMap[object->id()].push_back(tempArea[ia]); + auto offsetedTa = offset_ex(tempAreas[ia], print.brim_flow().scaled_spacing() * 2, jtRound, SCALED_RESOLUTION); + if (overlaps(offsetedTa, objectIslands) || + overlaps(offsetedTa, otherExPolys)) { + lock.acquire(brimMutex); + retained.push_back(ia); + lock.release(); + } + } + }); + + for (auto& index : retained) { + brimAreaMap[object->id()].push_back(tempAreas[index]); + brim_area.push_back(tempAreas[index]); } - expolygons_append(brim_area, brimAreaMap[object->id()]); } } return brim_area; diff --git a/src/libslic3r/BrimEarsPoint.hpp b/src/libslic3r/BrimEarsPoint.hpp new file mode 100644 index 0000000..d768d21 --- /dev/null +++ b/src/libslic3r/BrimEarsPoint.hpp @@ -0,0 +1,63 @@ +#ifndef BRIMEARSPOINT_HPP +#define BRIMEARSPOINT_HPP + +#include + + +namespace Slic3r { + +// An enum to keep track of where the current points on the ModelObject came from. +enum class PointsStatus { + NoPoints, // No points were generated so far. + Generating, // The autogeneration algorithm triggered, but not yet finished. + AutoGenerated, // Points were autogenerated (i.e. copied from the backend). + UserModified // User has done some edits. +}; + +struct BrimPoint +{ + Vec3f pos; + float head_front_radius; + + BrimPoint() + : pos(Vec3f::Zero()), head_front_radius(0.f) + {} + + BrimPoint(float pos_x, + float pos_y, + float pos_z, + float head_radius) + : pos(pos_x, pos_y, pos_z) + , head_front_radius(head_radius) + {} + + BrimPoint(Vec3f position, float head_radius) + : pos(position) + , head_front_radius(head_radius) + {} + + Vec3f transform(const Transform3d &trsf) + { + Vec3d result = trsf * pos.cast(); + return result.cast(); + } + + bool operator==(const BrimPoint &sp) const + { + float rdiff = std::abs(head_front_radius - sp.head_front_radius); + return (pos == sp.pos) && rdiff < float(EPSILON); + } + + bool operator!=(const BrimPoint &sp) const { return !(sp == (*this)); } + template void serialize(Archive &ar) + { + ar(pos, head_front_radius); + } +}; + +using BrimPoints = std::vector; + +} + + +#endif // BRIMEARSPOINT_HPP \ No newline at end of file diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3524607..847b884 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -60,9 +60,15 @@ set(lisbslic3r_sources EdgeGrid.hpp ElephantFootCompensation.cpp ElephantFootCompensation.hpp + Emboss.cpp + Emboss.hpp + EmbossShape.hpp enum_bitmask.hpp ExPolygon.cpp ExPolygon.hpp + ExPolygonSerialize.hpp + ExPolygonsIndex.cpp + ExPolygonsIndex.hpp Extruder.cpp Extruder.hpp ExtrusionEntity.cpp @@ -108,6 +114,8 @@ set(lisbslic3r_sources Fill/FillRectilinear.hpp Flow.cpp Flow.hpp + Frustum.cpp + Frustum.hpp FlushVolCalc.cpp FlushVolCalc.hpp format.hpp @@ -222,6 +230,8 @@ set(lisbslic3r_sources MutablePriorityQueue.hpp ObjectID.cpp ObjectID.hpp + NSVGUtils.cpp + NSVGUtils.hpp ParameterUtils.cpp ParameterUtils.hpp PerimeterGenerator.cpp @@ -279,6 +289,8 @@ set(lisbslic3r_sources SlicesToTriangleMesh.cpp SlicingAdaptive.cpp SlicingAdaptive.hpp + Support/SupportCommon.cpp + Support/SupportCommon.hpp Support/SupportMaterial.cpp Support/SupportMaterial.hpp Support/TreeSupport.hpp @@ -288,6 +300,7 @@ set(lisbslic3r_sources Support/TreeModelVolumes.hpp Support/TreeModelVolumes.cpp Support/TreeSupportCommon.hpp + Support/SupportParameters.hpp MinimumSpanningTree.hpp MinimumSpanningTree.cpp Surface.cpp @@ -311,6 +324,8 @@ set(lisbslic3r_sources Utils.hpp Time.cpp Time.hpp + Timer.cpp + Timer.hpp Thread.cpp Thread.hpp TriangleSelector.cpp @@ -411,8 +426,10 @@ set(lisbslic3r_sources RegionExpansion.hpp RegionExpansion.cpp ClipperZUtils.hpp - GCode/Thumbnails.cpp - GCode/Thumbnails.hpp + FlushVolPredictor.hpp + FlushVolPredictor.cpp + GCode/Thumbnails.cpp + GCode/Thumbnails.hpp ) if (APPLE) @@ -439,8 +456,12 @@ find_package(CGAL REQUIRED) find_package(OpenCV REQUIRED core) cmake_policy(POP) -add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp - TryCatchSignal.cpp) +add_library(libslic3r_cgal STATIC + CutSurface.hpp CutSurface.cpp + IntersectionPoints.hpp IntersectionPoints.cpp + MeshBoolean.hpp MeshBoolean.cpp + TryCatchSignal.hpp TryCatchSignal.cpp + Triangulation.hpp Triangulation.cpp) target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 84add38..13b7290 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -269,8 +269,8 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree) // Offset CCW contours outside, CW contours (holes) inside. // Don't calculate union of the output paths. -template -static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +template +static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType endType = ClipperLib::etClosedPolygon) { ClipperLib::ClipperOffset co; ClipperLib::Paths out; @@ -280,7 +280,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(offset * ClipperOffsetShortestEdgeFactor)); + co.ShortestEdgeLength = std::abs(offset * ClipperOffsetShortestEdgeFactor); for (const ClipperLib::Path &path : paths) { co.Clear(); // Execute reorients the contours so that the outer most contour has a positive area. Thus the output @@ -331,7 +331,7 @@ TResult clipper_do( { // Safety offset only allowed on intersection and difference. assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); - return do_safety_offset == ApplySafetyOffset::Yes ? + return do_safety_offset == ApplySafetyOffset::Yes ? clipper_do(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : clipper_do(clipType, std::forward(subject), std::forward(clip), fillType); } @@ -356,11 +356,11 @@ ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool return PolyTreeToExPolygons(clipper_union(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd)); } -template -static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +template +static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type = ClipperLib::etOpenButt) { assert(offset > 0); - return raw_offset(std::forward(paths), offset, joinType, miterLimit); + return raw_offset(std::forward(paths), offset, joinType, miterLimit, end_type); } template @@ -415,12 +415,25 @@ Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, Cli Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return PolyTreeToExPolygons(offset_paths(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); } -Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); } -Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); } +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type) + { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); } +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type) + { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); } -// returns number of expolygons collected (0 or 1). +Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type, double miter_limit) + { + assert(line_width > 1.f); + return to_polygons( + clipper_union(raw_offset(ClipperUtils::SinglePathProvider(polygon.points), line_width / 2, join_type, miter_limit, ClipperLib::etClosedLine))); + } +Polygons contour_to_polygons(const Polygons &polygons, const float line_width, ClipperLib::JoinType join_type, double miter_limit) + { + assert(line_width > 1.f); + return to_polygons( + clipper_union(raw_offset(ClipperUtils::PolygonsProvider(polygons), line_width / 2, join_type, miter_limit, ClipperLib::etClosedLine))); + } + + // returns number of expolygons collected (0 or 1). static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) { // 1) Offset the outer contour. @@ -468,8 +481,8 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d // No hole remaining after an offset. Just copy the outer contour. append(out, std::move(contours)); } else if (delta < 0) { - // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. - // Subtract the offsetted holes from the offsetted contours. + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Subtract the offsetted holes from the offsetted contours. if (auto output = clipper_do(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) { append(out, std::move(output)); } else { @@ -539,7 +552,7 @@ template static ClipperLib::PolyTree expolygons_offset_pt(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit); - // Unite the offsetted expolygons for both the + // Unite the offsetted expolygons for both the return clipper_union(output); } @@ -801,7 +814,12 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); } Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject) { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } -Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2) +{ + return PolyTreeToExPolygons( + clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ClipperLib::pftNonZero)); +} + Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } // QDS Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPolygons& poly2, bool safety_offset_) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 0262ad6..cb2b8ee 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -29,6 +29,7 @@ class BoundingBox; static constexpr const float ClipperSafetyOffset = 10.f; static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; +static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Slic3r::ClipperLib::etOpenButt; //FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2. // Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill. // However such a high limit causes issues with large positive or negative offsets, where a sharp corner @@ -342,8 +343,8 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp // offset Polylines // Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. -Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit); -Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit); +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType); +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType); Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); @@ -362,6 +363,10 @@ inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float return offset_ex(temp, delta, joinType, miterLimit); } +// convert stroke to path by offsetting of contour +Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit); +Polygons contour_to_polygons(const Polygons &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit); + inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); } inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); } inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); } @@ -548,6 +553,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero); Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2); Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPolygons& poly2, bool safety_offset_ = false); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 1714fe3..39efa53 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -31,6 +31,11 @@ namespace Slic3r { double value; bool percent; + FloatOrPercent() {} + FloatOrPercent(double value_, bool percent_) : value(value_), percent(percent_) {} + + double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } + private: friend class cereal::access; template void serialize(Archive& ar) { ar(this->value); ar(this->percent); } @@ -1793,7 +1798,8 @@ public: case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } - default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + case coFloatsOrPercents:{ auto opt = new ConfigOptionFloatsOrPercentsNullable();archive(*opt); return opt; } + default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1806,6 +1812,7 @@ public: case coPercent: { auto opt = new ConfigOptionPercent(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercents(); archive(*opt); return opt; } case coFloatOrPercent: { auto opt = new ConfigOptionFloatOrPercent(); archive(*opt); return opt; } + case coFloatsOrPercents: { auto opt = new ConfigOptionFloatsOrPercents(); archive(*opt); return opt; } case coPoint: { auto opt = new ConfigOptionPoint(); archive(*opt); return opt; } case coPoints: { auto opt = new ConfigOptionPoints(); archive(*opt); return opt; } case coPoint3: { auto opt = new ConfigOptionPoint3(); archive(*opt); return opt; } @@ -1826,7 +1833,8 @@ public: case coInts: archive(*static_cast(opt)); break; case coPercents: archive(*static_cast(opt));break; case coBools: archive(*static_cast(opt)); break; - default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + case coFloatsOrPercents: archive(*static_cast(opt)); break; + default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1838,7 +1846,8 @@ public: case coStrings: archive(*static_cast(opt)); break; case coPercent: archive(*static_cast(opt)); break; case coPercents: archive(*static_cast(opt)); break; - case coFloatOrPercent: archive(*static_cast(opt)); break; + case coFloatOrPercent: archive(*static_cast(opt)); break; + case coFloatsOrPercents: archive(*static_cast(opt)); break; case coPoint: archive(*static_cast(opt)); break; case coPoints: archive(*static_cast(opt)); break; case coPoint3: archive(*static_cast(opt)); break; diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp new file mode 100644 index 0000000..9400ecd --- /dev/null +++ b/src/libslic3r/CutSurface.cpp @@ -0,0 +1,4082 @@ +#include "CutSurface.hpp" + +/// models_input.obj - Check transormation of model to each others +/// projection_center.obj - circle representing center of projection with correct distance +/// {M} .. model index +/// model/model{M}.off - CGAL model created from index_triangle_set +/// model_neg/model{M}.off - CGAL model created for differenciate (multi volume object) +/// shape.off - CGAL model created from shapes +/// constrained/model{M}.off - Visualization of inside and outside triangles +/// Green - not along constrained edge +/// Red - sure that are inside +/// Purple - sure that are outside +/// (only along constrained edge) +/// filled/model{M}.off - flood fill green triangles inside of red area +/// - Same meaning of color as constrained +/// {N} .. Order of cutted Area of Interestmodel from model surface +/// model_AOIs/{M}/cutAOI{N}.obj - Extracted Area of interest from corefined model +/// model_AOIs/{M}/outline{N}.obj - Outline of Cutted Area +/// {O} .. Order number of patch +/// patches/patch{O}.off +/// result.obj - Merged result its +/// result_contours/{O}.obj - visualization of contours for result patches +//#define DEBUG_OUTPUT_DIR std::string("C:/data/temp/cutSurface/") + +using namespace Slic3r; +#include "ExPolygonsIndex.hpp" +#include +#include +#include +#include +#include +#include + +// libslic3r +#include "TriangleMesh.hpp" // its_merge +#include "Utils.hpp" // next_highest_power_of_2 +#include "ClipperUtils.hpp" // union_ex + offset_ex + +namespace priv { + +using Project = Emboss::IProjection; +using Project3d = Emboss::IProject3d; + +/// +/// Set true for indices out of area of interest +/// +/// Flag to convert triangle to cgal +/// model +/// Convert 2d point to pair of 3d points +/// 2d bounding box define AOI +void set_skip_for_out_of_aoi(std::vector &skip_indicies, + const indexed_triangle_set &its, + const Project &projection, + const BoundingBox &shapes_bb); + +/// +/// Set true for indicies outward and almost parallel together. +/// Note: internally calculate normals +/// +/// Flag to convert triangle to cgal +/// model +/// Direction to measure angle +/// Maximal allowed angle between opposit normal and +/// projection direction [in DEG] +void set_skip_by_angle(std::vector &skip_indicies, + const indexed_triangle_set &its, + const Project3d &projection, + double max_angle = 89.); + + +using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using CutMesh = CGAL::Surface_mesh; +using CutMeshes = std::vector; + +using VI = CGAL::SM_Vertex_index; +using HI = CGAL::SM_Halfedge_index; +using EI = CGAL::SM_Edge_index; +using FI = CGAL::SM_Face_index; +using P3 = CGAL::Epick::Point_3; + +inline Vec3d to_vec3d(const P3 &p) { return Vec3d(p.x(),p.y(),p.z()); } + +/// +/// Convert triangle mesh model to CGAL Surface_mesh +/// Filtrate out opposite triangles +/// Add property map for source face index +/// +/// Model +/// Flags that triangle should be skiped +/// When true triangle will flip normal +/// CGAL mesh - half edge mesh +CutMesh to_cgal(const indexed_triangle_set &its, + const std::vector &skip_indicies, + bool flip = false); + +/// +/// Covert 2d shape (e.g. Glyph) to CGAL model +/// NOTE: internaly create +/// edge_shape_map .. Property map to store conversion from edge to contour +/// face_shape_map .. Property map to store conversion from face to contour +/// +/// 2d shapes to project +/// Define transformation 2d point into 3d +/// CGAL model of extruded shape +CutMesh to_cgal(const ExPolygons &shapes, const Project &projection); +// function to check result of projection. 2d int32_t -> 3d double +bool exist_duplicit_vertex(const CutMesh& mesh); + + +/// +/// IntersectingElement +/// +/// Adress polygon inside of ExPolygon +/// Keep information about source of vertex: +/// - from face (one of 2 possible) +/// - from edge (one of 2 possible) +/// +/// V1~~~~~V2 +/// | f1 /: +/// | / : +/// e1| /e2: +/// | / : +/// |/ f2 : +/// V1'~~~~V2' +/// +/// | .. edge +/// / .. edge +/// : .. foreign edge - neighbor +/// ~ .. no care edge - idealy should not cross model +/// V1,V1' .. projected 2d point to 3d +/// V2,V2' .. projected 2d point to 3d +/// +/// Vertex indexing +/// V1 .. i (vertex_base + 2x index of point in polygon) +/// V1' .. i + 1 +/// V2 .. j = i + 2 || 0 (for last i in polygon) +/// V2' .. j + 1 +/// +/// f1 .. text_face_1 (triangle face made by side of shape contour) +/// f2 .. text_face_2 +/// e1 .. text_edge_1 (edge on side of face made by side of shape contour) +/// e2 .. text_edge_2 +/// +/// +struct IntersectingElement +{ + // identify source point in shapes + uint32_t shape_point_index{std::numeric_limits::max()}; + + // store together type, is_first, is_last + unsigned char attr{std::numeric_limits::max()}; + + // vertex or edge ID, where edge ID is the index of the source point. + // There are 4 consecutive indices generated for a single contour edge: + // 0th - 1st text edge (straight) + // 1th - 1st text face + // 2nd - 2nd text edge (diagonal) + // 3th - 2nd text face + // Type of intersecting element from extruded shape( 3d ) + // NOTE: type must be storable to 3bit -> max value is 7 + enum class Type: unsigned char { + edge_1 = 0, + face_1 = 1, + edge_2 = 2, + face_2 = 3, + undefined = 4 + }; + + IntersectingElement &set_type(Type t) + { + attr = static_cast( + attr + (int) t - (int) get_type()); + return *this; + } + void set_is_first(){ attr += 8; } + void set_is_last(){ attr += 16; } + Type get_type() const { return static_cast(attr % 8);} + bool is_first() const { return 8 <= attr && attr < 16; } + bool is_last() const { return attr >= 16; } +}; + +// stored in model made by shape +using EdgeShapeMap = CutMesh::Property_map; +using FaceShapeMap = CutMesh::Property_map; + +// stored in surface source - pointer to EdgeShapeMap | FaceShapeMap +using VertexShapeMap = CutMesh::Property_map; + +// stored in model made by shape +const std::string edge_shape_map_name = "e:IntersectingElement"; +const std::string face_shape_map_name = "f:IntersectingElement"; + +// stored in surface source +const std::string vert_shape_map_name = "v:IntersectingElement"; + +/// +/// Flag for faces in CGAL mesh +/// +enum class FaceType { + // face inside of the cutted shape + inside, + // face outside of the cutted shape + outside, + // face without constrained edge (In or Out) + not_constrained, + + // Helper flag that inside was processed + inside_processed +}; +using FaceTypeMap = CutMesh::Property_map; +const std::string face_type_map_name = "f:side"; + +// Conversion one vertex index to another +using CvtVI2VI = CutMesh::Property_map; +// Each Patch track outline vertex conversion to tource model +const std::string patch_source_name = "v:patch_source"; + +// For VI that should be reduced, contain VI to use instead of reduced +// Other VI are invalid +using ReductionMap = CvtVI2VI; +const std::string vertex_reduction_map_name = "v:reduction"; + +// A property map containing the constrained-or-not status of each edge +using EdgeBoolMap = CutMesh::Property_map; +const std::string is_constrained_edge_name = "e:is_constrained"; + +/// +/// Create map to reduce unnecesary triangles, +/// Triangles are made by divided quad to two triangles +/// on side of cutting shape mesh +/// Note: also use from mesh (have to be created) +/// face_type_map .. Type of shape inside / outside +/// vert_shape_map .. Source of outline vertex +/// +/// Reduction map from vertex to vertex, +/// when key == value than no reduction +/// Faces of one +/// Input object +void create_reduce_map(ReductionMap &reduction_map, const CutMesh &meshes); + +// Patch made by Cut area of interest from model +// connected faces(triangles) and outlines(halfEdges) for one surface cut +using CutAOI = std::pair, std::vector>; +// vector of Cutted Area of interest cutted from one CGAL model +using CutAOIs = std::vector; +// vector of CutAOIs for each model +using VCutAOIs = std::vector; + +/// +/// Create AOIs(area of interest) on model surface +/// +/// Input model converted to CGAL +/// NOTE: will be extended by corefine edge +/// 2d contours +/// [const]Model made by shapes +/// NOTE: Can't be definde as const because of corefine function input definition, +/// but it is. +/// Wanted projection distance +/// Convert index to shape point from ExPolygons +/// Patches from model surface +CutAOIs cut_from_model(CutMesh &cgal_model, + const ExPolygons &shapes, + /*const*/ CutMesh &cgal_shape, + float projection_ratio, + const ExPolygonsIndices &s2i); + +using Loop = std::vector; +using Loops = std::vector; + +/// +/// Create closed loops of contour vertices created from open half edges +/// +/// Unsorted half edges +/// Source mesh for half edges +/// Closed loops +Loops create_loops(const std::vector &outlines, const CutMesh &mesh); + +// To track during diff_models, +// what was cutted off, from CutAOI +struct SurfacePatch +{ + // converted cut to CGAL mesh + // Mesh is reduced. + // (do not contain divided triangles on contour - created by side Quad) + CutMesh mesh; + // CvtVI2VI cvt = mesh.property_map(patch_source_name); + // Conversion VI from this patch to source VI(model) is stored in mesh property + + // Outlines - converted CutAOI.second (half edges) + // to loops (vertex indicies) by function create_loops + Loops loops; + + // bounding box of mesh + BoundingBoxf3 bb; + + //// Data needed to find best projection distances + // index of source model in models + size_t model_id; + // index of source CutAOI + size_t aoi_id; + // index of shape from ExPolygons + size_t shape_id = 0; + + // flag that this patch contain whole CutAOI + bool is_whole_aoi = true; +}; +using SurfacePatches = std::vector; + +struct ModelCutId +{ + // index of model + uint32_t model_index; + // index of cut inside model + uint32_t cut_index; +}; + +/// +/// Keep conversion from VCutAOIs to Index and vice versa +/// Model_index .. contour(or hole) poin from ExPolygons +/// Index .. continous number +/// +class ModelCut2index +{ + std::vector m_offsets; + // for check range of index + uint32_t m_count; + +public: + ModelCut2index(const VCutAOIs &cuts); + uint32_t calc_index(const ModelCutId &id) const; + ModelCutId calc_id(uint32_t index) const; + uint32_t get_count() const { return m_count; }; + const std::vector &get_offsets() const { return m_offsets; } +}; + +/// +/// Differenciate other models +/// +/// Patches from meshes +/// Source points for Cutted AOIs +/// NOTE: Create Reduction map as mesh property - clean on end +/// Original models without cut modifications +/// used for differenciation +/// NOTE: Clip function modify Mesh +/// Define projection direction +/// Cuts differenciate by models - Patch +SurfacePatches diff_models(VCutAOIs &cuts, + /*const*/ CutMeshes &cut_models, + /*const*/ CutMeshes &models, + const Project3d &projection); + +/// +/// Checking whether patch is uninterrupted cover of whole expolygon it belongs. +/// +/// Part of surface to check +/// Source shape +/// Source of cut +/// True when cover whole expolygon otherwise false +bool is_over_whole_expoly(const CutAOI &cutAOI, + const ExPolygon &shape, + const CutMesh &mesh); + +/// +/// Checking whether patch is uninterrupted cover of whole expolygon it belongs. +/// +/// Part of surface to check +/// Source shape +/// True when cover whole expolygon otherwise false +bool is_over_whole_expoly(const SurfacePatch &patch, + const ExPolygons &shapes, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes); +/// +/// Unptoject points from outline loops of patch +/// +/// Contain loops and vertices +/// Know how to project from 3d to 2d +/// Range of unprojected points x .. min, y .. max value +/// Unprojected points in loops +Polygons unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range); + +/// +/// Unproject points from loops and create expolygons +/// +/// Patch to convert on expolygon +/// Convert 3d point to 2d +/// Range of unprojected points x .. min, y .. max value +/// Expolygon represent patch in 2d +ExPolygon to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range); + +/// +/// To select surface near projection distance +/// +struct ProjectionDistance +{ + // index of source model + uint32_t model_index = std::numeric_limits::max(); + + // index of CutAOI + uint32_t aoi_index = std::numeric_limits::max(); + + // index of Patch + uint32_t patch_index = std::numeric_limits::max(); + + // signed distance to projection + float distance = std::numeric_limits::max(); +}; +// addresed by ExPolygonsIndices +using ProjectionDistances = std::vector; + +// each point in shapes has its ProjectionDistances +using VDistances = std::vector; + +/// +/// Calculate distances for SurfacePatches outline points +/// NOTE: +/// each model has to have "vert_shape_map" .. Know source of new vertices +/// +/// Part of surface +/// Vertices position +/// Mesh created by shapes +/// Count of contour points in shapes +/// Define best distnace +/// Projection distances of cutted shape points +VDistances calc_distances(const SurfacePatches &patches, + const CutMeshes &models, + const CutMesh &shapes_mesh, + size_t count_shapes_points, + float projection_ratio); + +/// +/// Select distances in similar depth between expolygons +/// +/// All distances - Vector distances for each shape point +/// Vector of letters +/// Pivot for start projection in 2d +/// Convert index to addresss inside of shape +/// Cutted parts from surface +/// Closest distance projection indexed by points in shapes(see s2i) +ProjectionDistances choose_best_distance( + const VDistances &distances, + const ExPolygons &shapes, + const Point &start, + const ExPolygonsIndices &s2i, + const SurfacePatches &patches); + +/// +/// Create mask for patches +/// +/// For each point selected closest distance +/// All patches +/// Shape to cut +/// Bound of shapes +/// +/// +/// +/// +/// Mask of used patch +std::vector select_patches(const ProjectionDistances &best_distances, + const SurfacePatches &patches, + const ExPolygons &shapes, + const BoundingBox &shapes_bb, + const ExPolygonsIndices &s2i, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes, + const Project &projection); + +/// +/// Merge two surface cuts together +/// Added surface cut will be consumed +/// +/// Surface cut to extend +/// Surface cut to consume +void append(SurfaceCut &sc, SurfaceCut &&sc_add); + +/// +/// Convert patch to indexed_triangle_set +/// +/// Part of surface +/// Converted patch +SurfaceCut patch2cut(SurfacePatch &patch); + +/// +/// Merge masked patches to one surface cut +/// +/// All patches +/// NOTE: Not const because One needs to add property for Convert indices +/// Mash for using patch +/// Result surface cut +SurfaceCut merge_patches(/*const*/ SurfacePatches &patches, + const std::vector &mask); + +#ifdef DEBUG_OUTPUT_DIR +void prepare_dir(const std::string &dir); +void initialize_store(const std::string &dir_to_clear); +/// +/// Debug purpose store of mesh with colored face by face type +/// +/// Input mesh, could add property color +/// NOTE: Not const because need to [optionaly] append color property map +/// Color source +/// File to store +void store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string &dir, bool is_filled = false); +void store(const ExPolygons &shapes, const std::string &svg_file); +void store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string &dir); +void store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir); +void store(const SurfacePatches &patches, const std::string &dir); +void store(const Vec3f &vertex, const Vec3f &normal, const std::string &file, float size = 2.f); +//void store(const ProjectionDistances &pds, const VCutAOIs &aois, const CutMeshes &meshes, const std::string &file, float width = 0.2f/* [in mm] */); +using Connection = std::pair; using Connections = std::vector; +void store(const ExPolygons &shapes, const std::vector &mask_distances, const Connections &connections, const std::string &file_svg); +void store(const SurfaceCut &cut, const std::string &file, const std::string &contour_dir); +void store(const std::vector &models, const std::string &obj_filename); +void store(const std::vector&models, const std::string &dir); +void store(const Emboss::IProjection &projection, const Point &point_to_project, float projection_ratio, const std::string &obj_filename); +#endif // DEBUG_OUTPUT_DIR +} // namespace privat + +#ifdef DEBUG_OUTPUT_DIR +#include "libslic3r/SVG.hpp" +#include +#include +#endif // DEBUG_OUTPUT_DIR + +SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes, + const std::vector &models, + const Emboss::IProjection &projection, + float projection_ratio) +{ + assert(!models.empty()); + assert(!shapes.empty()); + if (models.empty() || shapes.empty() ) return {}; + +#ifdef DEBUG_OUTPUT_DIR + priv::initialize_store(DEBUG_OUTPUT_DIR); + priv::store(models, DEBUG_OUTPUT_DIR + "models_input.obj"); + priv::store(shapes, DEBUG_OUTPUT_DIR + "shapes.svg"); +#endif // DEBUG_OUTPUT_DIR + + // for filter out triangles out of bounding box + BoundingBox shapes_bb = get_extents(shapes); +#ifdef DEBUG_OUTPUT_DIR + priv::store(projection, shapes_bb.center(), projection_ratio, DEBUG_OUTPUT_DIR + "projection_center.obj"); +#endif // DEBUG_OUTPUT_DIR + + // for filttrate opposite triangles and a little more + const float max_angle = 89.9f; + priv::CutMeshes cgal_models; // source for patch + priv::CutMeshes cgal_neg_models; // model used for differenciate patches + cgal_models.reserve(models.size()); + for (const indexed_triangle_set &its : models) { + std::vector skip_indicies(its.indices.size(), {false}); + priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, shapes_bb); + + // create model for differenciate cutted patches + bool flip = true; + cgal_neg_models.push_back(priv::to_cgal(its, skip_indicies, flip)); + + // cut out more than only opposit triangles + priv::set_skip_by_angle(skip_indicies, its, projection, max_angle); + cgal_models.push_back(priv::to_cgal(its, skip_indicies)); + } +#ifdef DEBUG_OUTPUT_DIR + priv::store(cgal_models, DEBUG_OUTPUT_DIR + "model/");// model[0-N].off + priv::store(cgal_neg_models, DEBUG_OUTPUT_DIR + "model_neg/"); // model[0-N].off +#endif // DEBUG_OUTPUT_DIR + + priv::CutMesh cgal_shape = priv::to_cgal(shapes, projection); +#ifdef DEBUG_OUTPUT_DIR + CGAL::IO::write_OFF(DEBUG_OUTPUT_DIR + "shape.off", cgal_shape); // only debug +#endif // DEBUG_OUTPUT_DIR + + // create tool for convert index to shape Point adress and vice versa + ExPolygonsIndices s2i(shapes); + priv::VCutAOIs model_cuts; + // cut shape from each cgal model + for (priv::CutMesh &cgal_model : cgal_models) { + priv::CutAOIs cutAOIs = priv::cut_from_model( + cgal_model, shapes, cgal_shape, projection_ratio, s2i); +#ifdef DEBUG_OUTPUT_DIR + size_t index = &cgal_model - &cgal_models.front(); + priv::store(cutAOIs, cgal_model, DEBUG_OUTPUT_DIR + "model_AOIs/" + std::to_string(index) + "/"); // only debug +#endif // DEBUG_OUTPUT_DIR + model_cuts.push_back(std::move(cutAOIs)); + } + + priv::SurfacePatches patches = priv::diff_models(model_cuts, cgal_models, cgal_neg_models, projection); +#ifdef DEBUG_OUTPUT_DIR + priv::store(patches, DEBUG_OUTPUT_DIR + "patches/"); +#endif // DEBUG_OUTPUT_DIR + if (patches.empty()) return {}; + + // fix - convert shape_point_id to expolygon index + // save 1 param(s2i) from diff_models call + for (priv::SurfacePatch &patch : patches) + patch.shape_id = s2i.cvt(patch.shape_id).expolygons_index; + + // calc distance to projection for all outline points of cutAOI(shape) + // it is used for distiguish the top one + uint32_t shapes_points = s2i.get_count(); + // for each point collect all projection distances + priv::VDistances distances = priv::calc_distances(patches, cgal_models, cgal_shape, shapes_points, projection_ratio); + + Point start = shapes_bb.center(); // only align center + + // Use only outline points + // for each point select best projection + priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, start, s2i, patches); + std::vector use_patch = priv::select_patches(best_projection, patches, shapes, shapes_bb, s2i, model_cuts, cgal_models, projection); + SurfaceCut result = merge_patches(patches, use_patch); + //*/ + +#ifdef DEBUG_OUTPUT_DIR + priv::store(result, DEBUG_OUTPUT_DIR + "result.obj", DEBUG_OUTPUT_DIR + "result_contours/"); +#endif // DEBUG_OUTPUT_DIR + return result; +} + +indexed_triangle_set Slic3r::cut2model(const SurfaceCut &cut, + const Emboss::IProject3d &projection) +{ + assert(!cut.empty()); + size_t count_vertices = cut.vertices.size() * 2; + size_t count_indices = cut.indices.size() * 2; + + // indices from from zig zag + for (const auto &c : cut.contours) { + assert(!c.empty()); + count_indices += c.size() * 2; + } + + indexed_triangle_set result; + result.vertices.reserve(count_vertices); + result.indices.reserve(count_indices); + + // front + result.vertices.insert(result.vertices.end(), + cut.vertices.begin(), cut.vertices.end()); + result.indices.insert(result.indices.end(), + cut.indices.begin(), cut.indices.end()); + + // back + for (const Vec3f &v : cut.vertices) { + Vec3d vd = v.cast(); + Vec3d vd2 = projection.project(vd); + result.vertices.push_back(vd2.cast()); + } + + size_t back_offset = cut.vertices.size(); + for (const auto &i : cut.indices) { + // check range of indices in cut + assert(i.x() + back_offset < result.vertices.size()); + assert(i.y() + back_offset < result.vertices.size()); + assert(i.z() + back_offset < result.vertices.size()); + assert(i.x() >= 0 && i.x() < cut.vertices.size()); + assert(i.y() >= 0 && i.y() < cut.vertices.size()); + assert(i.z() >= 0 && i.z() < cut.vertices.size()); + // Y and Z is swapped CCW triangles for back side + result.indices.emplace_back(i.x() + back_offset, + i.z() + back_offset, + i.y() + back_offset); + } + + // zig zag indices + for (const auto &contour : cut.contours) { + size_t prev_front_index = contour.back(); + size_t prev_back_index = back_offset + prev_front_index; + for (size_t front_index : contour) { + assert(front_index < cut.vertices.size()); + size_t back_index = back_offset + front_index; + result.indices.emplace_back(front_index, prev_front_index, back_index); + result.indices.emplace_back(prev_front_index, prev_back_index, back_index); + prev_front_index = front_index; + prev_back_index = back_index; + } + } + + assert(count_vertices == result.vertices.size()); + assert(count_indices == result.indices.size()); + return result; +} + +// set_skip_for_out_of_aoi helping functions +namespace priv { +// define plane +using PointNormal = std::pair; +using PointNormals = std::array; + +/// +/// Check +/// +/// +/// +/// +/// +bool is_out_of(const Vec3d &v, const PointNormal &point_normal); + +using IsOnSides = std::vector>; +/// +/// Check if triangle t has all vertices out of any plane +/// +/// Triangle +/// Flag is vertex index out of plane +/// True when triangle is out of one of plane +bool is_all_on_one_side(const Vec3i32 &t, const IsOnSides& is_on_sides); + +} // namespace priv + +bool priv::is_out_of(const Vec3d &v, const PointNormal &point_normal) +{ + const Vec3d& p = point_normal.first; + const Vec3d& n = point_normal.second; + double signed_distance = (v - p).dot(n); + return signed_distance > 1e-5; +}; + +bool priv::is_all_on_one_side(const Vec3i32 &t, const IsOnSides& is_on_sides) { + for (size_t side = 0; side < 4; side++) { + bool result = true; + for (auto vi : t) { + if (!is_on_sides[vi][side]) { + result = false; + break; + } + } + if (result) return true; + } + return false; +} + +void priv::set_skip_for_out_of_aoi(std::vector &skip_indicies, + const indexed_triangle_set &its, + const Project &projection, + const BoundingBox &shapes_bb) +{ + assert(skip_indicies.size() == its.indices.size()); + // 1`*----* 2` + // / 2 /| + // 1 *----* | + // | | * 3` + // | |/ + // 0 *----* 3 + ////////////////// + std::array, 4> bb; + int index = 0; + for (Point v : + {shapes_bb.min, Point{shapes_bb.min.x(), shapes_bb.max.y()}, + shapes_bb.max, Point{shapes_bb.max.x(), shapes_bb.min.y()}}) + bb[index++] = projection.create_front_back(v); + + // define planes to test + // 0 .. under + // 1 .. left + // 2 .. above + // 3 .. right + size_t prev_i = 3; + // plane is defined by point and normal + PointNormals point_normals; + for (size_t i = 0; i < 4; i++) { + const Vec3d &p1 = bb[i].first; + const Vec3d &p2 = bb[i].second; + const Vec3d &p3 = bb[prev_i].first; + prev_i = i; + + Vec3d v1 = p2 - p1; + v1.normalize(); + Vec3d v2 = p3 - p1; + v2.normalize(); + + Vec3d normal = v2.cross(v1); + normal.normalize(); + + point_normals[i] = {p1, normal}; + } + + // check that projection is not left handed + // Fix for reflected projection + if (is_out_of(point_normals[2].first, point_normals[0])) { + // projection is reflected so normals are reflected + for (auto &pn : point_normals) + pn.second *= -1; + } + + // same meaning as point normal + IsOnSides is_on_sides(its.vertices.size(), {false,false,false,false}); + + // inspect all vertices when it is out of bounding box + tbb::parallel_for(tbb::blocked_range(0, its.vertices.size()), + [&its, &point_normals, &is_on_sides](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + Vec3d v = its.vertices[i].cast(); + // under + above + for (int side : {0, 2}) { + if (is_out_of(v, point_normals[side])) { + is_on_sides[i][side] = true; + // when it is under it can't be above + break; + } + } + // left + right + for (int side : {1, 3}) { + if (is_out_of(v, point_normals[side])) { + is_on_sides[i][side] = true; + // when it is on left side it can't be on right + break; + } + } + } + }); // END parallel for + + // inspect all triangles, when it is out of bounding box + tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), + [&its, &is_on_sides, &skip_indicies](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + if (is_all_on_one_side(its.indices[i], is_on_sides)) + skip_indicies[i] = true; + } + }); // END parallel for +} + +indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its, + const std::vector &mask) +{ + if (its.indices.size() != mask.size()) { + assert(false); + return {}; + } + + std::vector cvt_vetices(its.vertices.size(), {std::numeric_limits::max()}); + size_t vertices_count = 0; + size_t faces_count = 0; + for (const auto &t : its.indices) { + size_t index = &t - &its.indices.front(); + if (!mask[index]) continue; + ++faces_count; + for (const auto vi : t) { + uint32_t &cvt = cvt_vetices[vi]; + if (cvt == std::numeric_limits::max()) + cvt = vertices_count++; + } + } + if (faces_count == 0) return {}; + + indexed_triangle_set result; + result.indices.reserve(faces_count); + result.vertices = std::vector(vertices_count); + for (size_t i = 0; i < its.vertices.size(); ++i) { + uint32_t index = cvt_vetices[i]; + if (index == std::numeric_limits::max()) continue; + result.vertices[index] = its.vertices[i]; + } + + for (const stl_triangle_vertex_indices &f : its.indices) + if (mask[&f - &its.indices.front()]) + result.indices.push_back(stl_triangle_vertex_indices( + cvt_vetices[f[0]], cvt_vetices[f[1]], cvt_vetices[f[2]])); + + return result; +} + +indexed_triangle_set Slic3r::its_cut_AoI(const indexed_triangle_set &its, + const BoundingBox &bb, + const Emboss::IProjection &projection) +{ + std::vector skip_indicies(its.indices.size(), false); + priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, bb); + // invert values in vector of bool + skip_indicies.flip(); + return its_mask(its, skip_indicies); +} + +void priv::set_skip_by_angle(std::vector &skip_indicies, + const indexed_triangle_set &its, + const Project3d &projection, + double max_angle) +{ + assert(max_angle < 90. && max_angle > 89.); + assert(skip_indicies.size() == its.indices.size()); + float threshold = static_cast(cos(max_angle / 180. * M_PI)); + for (const stl_triangle_vertex_indices& face : its.indices) { + size_t index = &face - &its.indices.front(); + if (skip_indicies[index]) continue; + Vec3f n = its_face_normal(its, face); + const Vec3f& v = its.vertices[face[0]]; + const Vec3d vd = v.cast(); + // Improve: For Orthogonal Projection it is same for each vertex + Vec3d projectedd = projection.project(vd); + Vec3f projected = projectedd.cast(); + Vec3f project_dir = projected - v; + project_dir.normalize(); + float cos_alpha = project_dir.dot(n); + if (cos_alpha > threshold) continue; + skip_indicies[index] = true; + } +} + +priv::CutMesh priv::to_cgal(const indexed_triangle_set &its, + const std::vector &skip_indicies, + bool flip) +{ + const std::vector &vertices = its.vertices; + const std::vector &indices = its.indices; + + std::vector use_vetices(vertices.size(), {false}); + + size_t vertices_count = 0; + size_t faces_count = 0; + size_t edges_count = 0; + + for (const auto &t : indices) { + size_t index = &t - &indices.front(); + if (skip_indicies[index]) continue; + ++faces_count; + size_t count_used_vertices = 0; + for (const auto vi : t) { + if (!use_vetices[vi]) { + ++vertices_count; + use_vetices[vi] = true; + } else { + ++count_used_vertices; + } + } + switch (count_used_vertices) { + case 3: break; // all edges are already counted + case 2: edges_count += 2; break; + case 1: + case 0: edges_count += 3; break; + default: assert(false); + } + } + assert(vertices_count <= vertices.size()); + assert(edges_count <= (indices.size() * 3)); + assert(faces_count <= indices.size()); + + CutMesh result; + result.reserve(vertices_count, edges_count, faces_count); + + std::vector to_filtrated_vertices_index(vertices.size()); + size_t filtrated_vertices_index = 0; + for (size_t i = 0; i < vertices.size(); ++i) + if (use_vetices[i]) { + to_filtrated_vertices_index[i] = VI(filtrated_vertices_index); + ++filtrated_vertices_index; + } + + for (const stl_vertex& v : vertices) { + if (!use_vetices[&v - &vertices.front()]) continue; + result.add_vertex(CutMesh::Point{v.x(), v.y(), v.z()}); + } + + if (!flip) { + for (const stl_triangle_vertex_indices &f : indices) { + if (skip_indicies[&f - &indices.front()]) continue; + result.add_face(to_filtrated_vertices_index[f[0]], + to_filtrated_vertices_index[f[1]], + to_filtrated_vertices_index[f[2]]); + } + } else { + for (const stl_triangle_vertex_indices &f : indices) { + if (skip_indicies[&f - &indices.front()]) continue; + result.add_face(to_filtrated_vertices_index[f[2]], + to_filtrated_vertices_index[f[1]], + to_filtrated_vertices_index[f[0]]); + } + } + + return result; +} + +bool priv::exist_duplicit_vertex(const CutMesh &mesh) { + std::vector points; + points.reserve(mesh.vertices().size()); + // copy points + for (VI vi : mesh.vertices()) { + const P3 &p = mesh.point(vi); + points.emplace_back(p.x(), p.y(), p.z()); + } + std::sort(points.begin(), points.end(), [](const Vec3d &v1, const Vec3d &v2) { + return v1.x() < v2.x() || + (v1.x() == v2.x() && + (v1.y() < v2.y() || + (v1.y() == v2.y() && + v1.z() < v2.z()))); + }); + // find first duplicit + auto it = std::adjacent_find(points.begin(), points.end()); + return it != points.end(); +} + +priv::CutMesh priv::to_cgal(const ExPolygons &shapes, + const Project &projection) +{ + if (shapes.empty()) return {}; + + CutMesh result; + EdgeShapeMap edge_shape_map = result.add_property_map(edge_shape_map_name).first; + FaceShapeMap face_shape_map = result.add_property_map(face_shape_map_name).first; + + std::vector indices; + auto insert_contour = [&projection, &indices, &result, + &edge_shape_map, &face_shape_map] + (const Polygon &polygon) { + indices.clear(); + indices.reserve(polygon.points.size() * 2); + size_t num_vertices_old = result.number_of_vertices(); + for (const Point &polygon_point : polygon.points) { + auto [front, back] = projection.create_front_back(polygon_point); + P3 v_front{front.x(), front.y(), front.z()}; + VI vi1 = result.add_vertex(v_front); + assert(vi1.idx() == (indices.size() + num_vertices_old)); + indices.push_back(vi1); + + P3 v_back{back.x(), back.y(), back.z()}; + VI vi2 = result.add_vertex(v_back); + assert(vi2.idx() == (indices.size() + num_vertices_old)); + indices.push_back(vi2); + } + + auto find_edge = [&result](FI fi, VI from, VI to) { + HI hi = result.halfedge(fi); + for (; result.target(hi) != to; hi = result.next(hi)); + assert(result.source(hi) == from); + assert(result.target(hi) == to); + return result.edge(hi); + }; + + uint32_t contour_index = static_cast(num_vertices_old / 2); + for (int32_t i = 0; i < int32_t(indices.size()); i += 2) { + bool is_first = i == 0; + bool is_last = size_t(i + 2) >= indices.size(); + int32_t j = is_last ? 0 : (i + 2); + + FI fi1 = result.add_face(indices[i], indices[j], indices[i + 1]); + EI ei1 = find_edge(fi1, indices[i + 1], indices[i]); + EI ei2 = find_edge(fi1, indices[j], indices[i + 1]); + FI fi2 = result.add_face(indices[j], indices[j + 1], indices[i + 1]); + IntersectingElement element {contour_index, (unsigned char)IntersectingElement::Type::undefined}; + if (is_first) element.set_is_first(); + if (is_last) element.set_is_last(); + edge_shape_map[ei1] = element.set_type(IntersectingElement::Type::edge_1); + face_shape_map[fi1] = element.set_type(IntersectingElement::Type::face_1); + edge_shape_map[ei2] = element.set_type(IntersectingElement::Type::edge_2); + face_shape_map[fi2] = element.set_type(IntersectingElement::Type::face_2); + ++contour_index; + } + }; + + size_t count_point = count_points(shapes); + result.reserve(result.number_of_vertices() + 2 * count_point, + result.number_of_edges() + 4 * count_point, + result.number_of_faces() + 2 * count_point); + + // Identify polygon + for (const ExPolygon &shape : shapes) { + insert_contour(shape.contour); + for (const Polygon &hole : shape.holes) + insert_contour(hole); + } + assert(!exist_duplicit_vertex(result)); + return result; +} + +priv::ModelCut2index::ModelCut2index(const VCutAOIs &cuts) +{ + // prepare offsets + m_offsets.reserve(cuts.size()); + uint32_t offset = 0; + for (const CutAOIs &model_cuts: cuts) { + m_offsets.push_back(offset); + offset += model_cuts.size(); + } + m_count = offset; +} + +uint32_t priv::ModelCut2index::calc_index(const ModelCutId &id) const +{ + assert(id.model_index < m_offsets.size()); + uint32_t offset = m_offsets[id.model_index]; + uint32_t res = offset + id.cut_index; + assert(((id.model_index+1) < m_offsets.size() && res < m_offsets[id.model_index+1]) || + ((id.model_index+1) == m_offsets.size() && res < m_count)); + return res; +} + +priv::ModelCutId priv::ModelCut2index::calc_id(uint32_t index) const +{ + assert(index < m_count); + ModelCutId result{0,0}; + // find shape index + for (size_t model_index = 1; model_index < m_offsets.size(); ++model_index) { + if (m_offsets[model_index] > index) break; + result.model_index = model_index; + } + result.cut_index = index - m_offsets[result.model_index]; + return result; +} + +// cut_from_model help functions +namespace priv { + +/// +/// Track source of intersection +/// Help for anotate inner and outer faces +/// +struct Visitor : public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor { + Visitor(const CutMesh &object, const CutMesh &shape, EdgeShapeMap edge_shape_map, + FaceShapeMap face_shape_map, VertexShapeMap vert_shape_map, bool* is_valid) : + object(object), shape(shape), edge_shape_map(edge_shape_map), face_shape_map(face_shape_map), + vert_shape_map(vert_shape_map), is_valid(is_valid) + {} + + const CutMesh &object; + const CutMesh &shape; + + // Properties of the shape mesh: + EdgeShapeMap edge_shape_map; + FaceShapeMap face_shape_map; + + // Properties of the object mesh. + VertexShapeMap vert_shape_map; + + // check for anomalities + bool* is_valid; + + // keep source of intersection for each intersection + // used to copy data into vert_shape_map + std::vector intersections; + + /// + /// Called when a new intersection point is detected. + /// The intersection is detected using a face of tm_f and an edge of tm_e. + /// Intersecting an edge hh_edge from tm_f with a face h_e of tm_e. + /// https://doc.cgal.org/latest/Polygon_mesh_processing/classPMPCorefinementVisitor.html#a00ee0ca85db535a48726a92414acda7f + /// + /// The id of the intersection point, starting at 0. Ids are consecutive. + /// Dimension of a simplex part of face(h_e) that is intersected by edge(h_f): + /// 0 for vertex: target(h_e) + /// 1 for edge: h_e + /// 2 for the interior of face: face(h_e) + /// + /// A halfedge from tm_f indicating the simplex intersected: + /// if sdim==0 the target of h_f is the intersection point, + /// if sdim==1 the edge of h_f contains the intersection point in its interior, + /// if sdim==2 the face of h_f contains the intersection point in its interior. + /// @Vojta: Edge of tm_f, see is_target_coplanar & is_source_coplanar whether any vertex of h_f is coplanar with face(h_e). + /// + /// A halfedge from tm_e + /// @Vojta: Vertex, halfedge or face of tm_e intersected by h_f, see comment at sdim. + /// + /// Mesh containing h_f + /// Mesh containing h_e + /// True if the target of h_e is the intersection point + /// @Vojta: source(h_f) is coplanar with face(made by h_e). + /// True if the source of h_e is the intersection point + /// @Vojta: target(h_f) is coplanar with face(h_e). + void intersection_point_detected(std::size_t i_id, + int sdim, + HI h_f, + HI h_e, + const CutMesh &tm_f, + const CutMesh &tm_e, + bool is_target_coplanar, + bool is_source_coplanar); + + /// + /// Called when a new vertex is added in tm (either an edge split or a vertex inserted in the interior of a face). + /// Fill vertex_shape_map by intersections + /// + /// Order number of intersection point + /// New added vertex + /// Affected mesh + void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm); +}; + +/// +/// Distiquish face type for half edge +/// +/// Define face +/// Mesh to process +/// Vertices of mesh made by shapes +/// Keep information about source of created vertex +/// +/// Convert index to shape point from ExPolygons +/// Face type defined by hi +bool is_face_inside(HI hi, + const CutMesh &mesh, + const CutMesh &shape_mesh, + const VertexShapeMap &vertex_shape_map, + const ExPolygonsIndices &shape2index); + +/// +/// Face with constrained edge are inside/outside by type of intersection +/// Other set to not_constrained(still it could be inside/outside) +/// +/// [Output] property map with type of faces +/// Mesh to process +/// Keep information about source of created vertex +/// Dynamic Edge Constrained Map of bool +/// Vertices of mesh made by shapes +/// Convert index to shape point from ExPolygons +void set_face_type(FaceTypeMap &face_type_map, + const CutMesh &mesh, + const VertexShapeMap &vertex_shape_map, + const EdgeBoolMap &ecm, + const CutMesh &shape_mesh, + const ExPolygonsIndices &shape2index); + +/// +/// Change FaceType from not_constrained to inside +/// For neighbor(or neighbor of neighbor of ...) of inside triangles. +/// Process only not_constrained triangles +/// +/// Corefined mesh +/// In/Out map with faces type +void flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map); + +/// +/// Collect connected inside faces +/// Collect outline half edges +/// +/// Queue of face to process - find connected +/// [Output] collected Face indices from mesh +/// [Output] collected Halfedge indices from mesh +/// Use flag inside / outside +/// NOTE: Modify in function: inside -> inside_processed +/// mesh to process +void collect_surface_data(std::queue &process, + std::vector &faces, + std::vector &outlines, + FaceTypeMap &face_type_map, + const CutMesh &mesh); + +/// +/// Create areas from mesh surface +/// +/// Model +/// Cutted shapes +/// Define Triangles of interest. +/// Edge between inside / outside. +/// NOTE: Not const because it need to flag proccessed faces +/// Areas of interest from mesh +CutAOIs create_cut_area_of_interests(const CutMesh &mesh, + const ExPolygons &shapes, + FaceTypeMap &face_type_map); + +} // namespace priv + +void priv::Visitor::intersection_point_detected(std::size_t i_id, + int sdim, + HI h_f, + HI h_e, + const CutMesh &tm_f, + const CutMesh &tm_e, + bool is_target_coplanar, + bool is_source_coplanar) +{ + if (i_id >= intersections.size()) { + size_t capacity = Slic3r::next_highest_power_of_2(i_id + 1); + intersections.reserve(capacity); + intersections.resize(capacity); + } + + const IntersectingElement *intersection_ptr = nullptr; + if (&tm_e == &shape) { + assert(&tm_f == &object); + switch (sdim) { + case 1: + // edge x edge intersection + intersection_ptr = &edge_shape_map[shape.edge(h_e)]; + break; + case 2: + // edge x face intersection + intersection_ptr = &face_shape_map[shape.face(h_e)]; + break; + default: assert(false); + } + if (is_target_coplanar) + vert_shape_map[object.source(h_f)] = intersection_ptr; + if (is_source_coplanar) + vert_shape_map[object.target(h_f)] = intersection_ptr; + } else { + assert(&tm_f == &shape && &tm_e == &object); + assert(!is_target_coplanar); + assert(!is_source_coplanar); + if (is_target_coplanar || is_source_coplanar) + *is_valid = false; + intersection_ptr = &edge_shape_map[shape.edge(h_f)]; + if (sdim == 0) vert_shape_map[object.target(h_e)] = intersection_ptr; + } + + if (intersection_ptr->shape_point_index == std::numeric_limits::max()) { + // there is unexpected intersection + // Top (or Bottom) shape contour edge (or vertex) intersection + // Suggest to change projection min/max limits + *is_valid = false; + } + intersections[i_id] = intersection_ptr; +} + +void priv::Visitor::new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm) +{ + assert(&tm == &object); + assert(i_id < intersections.size()); + const IntersectingElement *intersection_ptr = intersections[i_id]; + assert(intersection_ptr != nullptr); + // intersection was not filled in function intersection_point_detected + //assert(intersection_ptr->point_index != std::numeric_limits::max()); + vert_shape_map[v] = intersection_ptr; +} + +bool priv::is_face_inside(HI hi, + const CutMesh &mesh, + const CutMesh &shape_mesh, + const VertexShapeMap &vertex_shape_map, + const ExPolygonsIndices &shape2index) +{ + VI vi_from = mesh.source(hi); + VI vi_to = mesh.target(hi); + // This face has a constrained edge. + const IntersectingElement &shape_from = *vertex_shape_map[vi_from]; + const IntersectingElement &shape_to = *vertex_shape_map[vi_to]; + assert(shape_from.shape_point_index != std::numeric_limits::max()); + assert(shape_from.attr != (unsigned char) IntersectingElement::Type::undefined); + assert(shape_to.shape_point_index != std::numeric_limits::max()); + assert(shape_to.attr != (unsigned char) IntersectingElement::Type::undefined); + + // index into contour + uint32_t i_from = shape_from.shape_point_index; + uint32_t i_to = shape_to.shape_point_index; + IntersectingElement::Type type_from = shape_from.get_type(); + IntersectingElement::Type type_to = shape_to.get_type(); + if (i_from == i_to && type_from == type_to) { + // intersecting element must be face + assert(type_from == IntersectingElement::Type::face_1 || + type_from == IntersectingElement::Type::face_2); + + // count of vertices is twice as count of point in the contour + uint32_t i = i_from * 2; + // j is next contour point in vertices + uint32_t j = i + 2; + if (shape_from.is_last()) { + ExPolygonsIndex point_id = shape2index.cvt(i_from); + point_id.point_index = 0; + j = shape2index.cvt(point_id)*2; + } + + // opposit point(in triangle face) to edge + const P3 &p = mesh.point(mesh.target(mesh.next(hi))); + + // abc is source triangle face + CGAL::Sign abcp = type_from == IntersectingElement::Type::face_1 ? + CGAL::orientation(shape_mesh.point(VI(i)), + shape_mesh.point(VI(i + 1)), + shape_mesh.point(VI(j)), p) : + // type_from == IntersectingElement::Type::face_2 + CGAL::orientation(shape_mesh.point(VI(j)), + shape_mesh.point(VI(i + 1)), + shape_mesh.point(VI(j + 1)), p); + return abcp == CGAL::POSITIVE; + } else if (i_from < i_to || (i_from == i_to && type_from < type_to)) { + bool is_last = shape_to.is_last() && shape_from.is_first(); + // check continuity of indicies + assert(i_from == i_to || is_last || (i_from + 1) == i_to); + return !is_last; + } else { + assert(i_from > i_to || (i_from == i_to && type_from > type_to)); + bool is_last = shape_to.is_first() && shape_from.is_last(); + // check continuity of indicies + assert(i_from == i_to || is_last || (i_to + 1) == i_from); + return is_last; + } + + assert(false); + return false; +} + +void priv::set_face_type(FaceTypeMap &face_type_map, + const CutMesh &mesh, + const VertexShapeMap &vertex_shape_map, + const EdgeBoolMap &ecm, + const CutMesh &shape_mesh, + const ExPolygonsIndices &shape2index) +{ + for (EI ei : mesh.edges()) { + if (!ecm[ei]) continue; + HI hi = mesh.halfedge(ei); + FI fi = mesh.face(hi); + bool is_inside = is_face_inside(hi, mesh, shape_mesh, vertex_shape_map, shape2index); + face_type_map[fi] = is_inside ? FaceType::inside : FaceType::outside; + HI hi_op = mesh.opposite(hi); + assert(hi_op.is_valid()); + if (!hi_op.is_valid()) continue; + FI fi_op = mesh.face(hi_op); + assert(fi_op.is_valid()); + if (!fi_op.is_valid()) continue; + face_type_map[fi_op] = (!is_inside) ? FaceType::inside : FaceType::outside; + } +} + +priv::CutAOIs priv::cut_from_model(CutMesh &cgal_model, + const ExPolygons &shapes, + CutMesh &cgal_shape, + float projection_ratio, + const ExPolygonsIndices &s2i) +{ + // pointer to edge or face shape_map + VertexShapeMap vert_shape_map = cgal_model.add_property_map(vert_shape_map_name, nullptr).first; + + // detect anomalities in visitor. + bool is_valid = true; + // NOTE: map are created when convert shapes to cgal model + const EdgeShapeMap& edge_shape_map = cgal_shape.property_map(edge_shape_map_name).first; + const FaceShapeMap& face_shape_map = cgal_shape.property_map(face_shape_map_name).first; + Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map, &is_valid}; + + // a property map containing the constrained-or-not status of each edge + EdgeBoolMap ecm = cgal_model.add_property_map(is_constrained_edge_name, false).first; + const auto &p = CGAL::parameters::visitor(visitor) + .edge_is_constrained_map(ecm) + .throw_on_self_intersection(false); + const auto& q = CGAL::parameters::do_not_modify(true); + CGAL::Polygon_mesh_processing::corefine(cgal_model, cgal_shape, p, q); + + if (!is_valid) return {}; + + FaceTypeMap face_type_map = cgal_model.add_property_map(face_type_map_name, FaceType::not_constrained).first; + + // Select inside and outside face in model + set_face_type(face_type_map, cgal_model, vert_shape_map, ecm, cgal_shape, s2i); +#ifdef DEBUG_OUTPUT_DIR + store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "constrained/"); // only debug +#endif // DEBUG_OUTPUT_DIR + + // flood fill the other faces inside the region. + flood_fill_inner(cgal_model, face_type_map); + +#ifdef DEBUG_OUTPUT_DIR + store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "filled/", true); // only debug +#endif // DEBUG_OUTPUT_DIR + + // IMPROVE: AOIs area could be created during flood fill + return create_cut_area_of_interests(cgal_model, shapes, face_type_map); +} + +void priv::flood_fill_inner(const CutMesh &mesh, + FaceTypeMap &face_type_map) +{ + std::vector process; + // guess count of connected not constrained triangles + size_t guess_size = 128; + process.reserve(guess_size); + + // check if neighbor(one of three in triangle) has type inside + auto has_inside_neighbor = [&mesh, &face_type_map](FI fi) { + HI hi = mesh.halfedge(fi); + HI hi_end = hi; + auto exist_next = [&hi, &hi_end, &mesh]() -> bool { + hi = mesh.next(hi); + return hi != hi_end; + }; + // loop over 3 half edges of face + do { + HI hi_opposite = mesh.opposite(hi); + // open edge doesn't have opposit half edge + if (!hi_opposite.is_valid()) continue; + FI fi_opposite = mesh.face(hi_opposite); + if (!fi_opposite.is_valid()) continue; + if (face_type_map[fi_opposite] == FaceType::inside) return true; + } while (exist_next()); + return false; + }; + + for (FI fi : mesh.faces()) { + FaceType type = face_type_map[fi]; + if (type != FaceType::not_constrained) continue; + if (!has_inside_neighbor(fi)) continue; + assert(process.empty()); + process.push_back(fi); + //store(mesh, face_type_map, DEBUG_OUTPUT_DIR + "progress.off"); + + while (!process.empty()) { + FI process_fi = process.back(); + process.pop_back(); + // Do not fill twice + FaceType& process_type = face_type_map[process_fi]; + if (process_type == FaceType::inside) continue; + process_type = FaceType::inside; + + // check neighbor triangle + HI hi = mesh.halfedge(process_fi); + HI hi_end = hi; + auto exist_next = [&hi, &hi_end, &mesh]() -> bool { + hi = mesh.next(hi); + return hi != hi_end; + }; + do { + HI hi_opposite = mesh.opposite(hi); + // open edge doesn't have opposit half edge + if (!hi_opposite.is_valid()) continue; + FI fi_opposite = mesh.face(hi_opposite); + if (!fi_opposite.is_valid()) continue; + FaceType type_opposite = face_type_map[fi_opposite]; + if (type_opposite == FaceType::not_constrained) + process.push_back(fi_opposite); + } while (exist_next()); + } + } +} + +void priv::collect_surface_data(std::queue &process, + std::vector &faces, + std::vector &outlines, + FaceTypeMap &face_type_map, + const CutMesh &mesh) +{ + assert(!process.empty()); + assert(faces.empty()); + assert(outlines.empty()); + while (!process.empty()) { + FI fi = process.front(); + process.pop(); + + FaceType &fi_type = face_type_map[fi]; + // Do not process twice + if (fi_type == FaceType::inside_processed) continue; + assert(fi_type == FaceType::inside); + // flag face as processed + fi_type = FaceType::inside_processed; + faces.push_back(fi); + + // check neighbor triangle + HI hi = mesh.halfedge(fi); + HI hi_end = hi; + do { + HI hi_opposite = mesh.opposite(hi); + // open edge doesn't have opposit half edge + if (!hi_opposite.is_valid()) { + outlines.push_back(hi); + hi = mesh.next(hi); + continue; + } + FI fi_opposite = mesh.face(hi_opposite); + if (!fi_opposite.is_valid()) { + outlines.push_back(hi); + hi = mesh.next(hi); + continue; + } + FaceType side = face_type_map[fi_opposite]; + if (side == FaceType::inside) { + process.emplace(fi_opposite); + } else if (side == FaceType::outside) { + // store outlines + outlines.push_back(hi); + } + hi = mesh.next(hi); + } while (hi != hi_end); + } +} + +void priv::create_reduce_map(ReductionMap &reduction_map, const CutMesh &mesh) +{ + const VertexShapeMap &vert_shape_map = mesh.property_map(vert_shape_map_name).first; + const EdgeBoolMap &ecm = mesh.property_map(is_constrained_edge_name).first; + + // check if vertex was made by edge_2 which is diagonal of quad + auto is_reducible_vertex = [&vert_shape_map](VI reduction_from) -> bool { + const IntersectingElement *ie = vert_shape_map[reduction_from]; + if (ie == nullptr) return false; + IntersectingElement::Type type = ie->get_type(); + return type == IntersectingElement::Type::edge_2; + }; + + /// + /// Append reduction or change existing one. + /// + /// HalEdge between outside and inside face. + /// Target vertex will be reduced, source vertex left + /// [[maybe_unused]] &face_type_map, &is_reducible_vertex are need only in debug + auto add_reduction = [&] //&reduction_map, &mesh, &face_type_map, &is_reducible_vertex + (HI hi) { + VI erase = mesh.target(hi); + VI left = mesh.source(hi); + assert(is_reducible_vertex(erase)); + assert(!is_reducible_vertex(left)); + VI &vi = reduction_map[erase]; + // check if it is first add + if (vi.is_valid()) + return; + + // check that all triangles after reduction has 'erase' and 'left' vertex + // on same side of opposite line of vertex in triangle + Vec3d v_erase = to_vec3d(mesh.point(erase)); + Vec3d v_left = to_vec3d(mesh.point(left)); + for (FI fi : mesh.faces_around_target(hi)) { + if (!fi.is_valid()) + continue; + // get vertices of rest + VI vi_a, vi_b; + for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) { + if (!vi.is_valid()) + continue; + if (vi == erase) + continue; + if (!vi_a.is_valid()) + vi_a = vi; + else { + assert(!vi_b.is_valid()); + vi_b = vi; + } + } + assert(vi_b.is_valid()); + // do not check triangle, which will be removed + if (vi_a == left || vi_b == left) + continue; + + Vec3d v_a = to_vec3d(mesh.point(vi_a)); + Vec3d v_b = to_vec3d(mesh.point(vi_b)); + // Vectors of triangle edges + Vec3d v_ab = v_b - v_a; + Vec3d v_ae = v_erase - v_a; + Vec3d v_al = v_left - v_a; + + Vec3d n1 = v_ab.cross(v_ae); + Vec3d n2 = v_ab.cross(v_al); + // check that normal has same direction + if (((n1.x() > 0) != (n2.x() > 0)) || + ((n1.y() > 0) != (n2.y() > 0)) || + ((n1.z() > 0) != (n2.z() > 0))) + return; // this reduction will create CCW triangle + } + + reduction_map[erase] = left; + // I have no better rule than take the first + // for decide which reduction will be better + // But it could be use only one of them + }; + + for (EI ei : mesh.edges()) { + if (!ecm[ei]) continue; + HI hi = mesh.halfedge(ei); + VI vi = mesh.target(hi); + if (is_reducible_vertex(vi)) add_reduction(hi); + + HI hi_op = mesh.opposite(hi); + VI vi_op = mesh.target(hi_op); + if (is_reducible_vertex(vi_op)) add_reduction(hi_op); + } +#ifdef DEBUG_OUTPUT_DIR + store(mesh, reduction_map, DEBUG_OUTPUT_DIR + "reduces/"); +#endif // DEBUG_OUTPUT_DIR +} + +priv::CutAOIs priv::create_cut_area_of_interests(const CutMesh &mesh, + const ExPolygons &shapes, + FaceTypeMap &face_type_map) +{ + // IMPROVE: Create better heuristic for count. + size_t faces_per_cut = mesh.faces().size() / shapes.size(); + size_t outlines_per_cut = faces_per_cut / 2; + size_t cuts_per_model = shapes.size() * 2; + + CutAOIs result; + result.reserve(cuts_per_model); + + // It is faster to use one queue for all cuts + std::queue process; + for (FI fi : mesh.faces()) { + if (face_type_map[fi] != FaceType::inside) continue; + + CutAOI cut; + std::vector &faces = cut.first; + std::vector &outlines = cut.second; + + // faces for one surface cut + faces.reserve(faces_per_cut); + // outline for one surface cut + outlines.reserve(outlines_per_cut); + + assert(process.empty()); + // Process queue of faces to separate to surface_cut + process.push(fi); + collect_surface_data(process, faces, outlines, face_type_map, mesh); + assert(!faces.empty()); + assert(!outlines.empty()); + result.emplace_back(std::move(cut)); + } + return result; +} + +namespace priv { + +/// +/// Calculate projection distance of point [in mm] +/// +/// Point to calc distance +/// Index of point on contour +/// Model of cutting shape +/// Ratio for best projection distance +/// Distance of point from best projection +float calc_distance(const P3 &p, + uint32_t pi, + const CutMesh &shapes_mesh, + float projection_ratio); + +} + +float priv::calc_distance(const P3 &p, + uint32_t pi, + const CutMesh &shapes_mesh, + float projection_ratio) +{ + // It is known because shapes_mesh is created inside of private space + VI vi_start(2 * pi); + VI vi_end(2 * pi + 1); + + // Get range for intersection + const P3 &start = shapes_mesh.point(vi_start); + const P3 &end = shapes_mesh.point(vi_end); + + // find index in vector with biggest difference + size_t max_i = 0; + float max_val = 0.f; + for (size_t i = 0; i < 3; i++) { + float val = start[i] - end[i]; + // abs value + if (val < 0.f) val *= -1.f; + if (max_val < val) { + max_val = val; + max_i = i; + } + } + + float from_start = p[max_i] - start[max_i]; + float best_distance = projection_ratio * (end[max_i] - start[max_i]); + return from_start - best_distance; +} + +priv::VDistances priv::calc_distances(const SurfacePatches &patches, + const CutMeshes &models, + const CutMesh &shapes_mesh, + size_t count_shapes_points, + float projection_ratio) +{ + priv::VDistances result(count_shapes_points); + for (const SurfacePatch &patch : patches) { + // map is created during intersection by corefine visitor + const VertexShapeMap &vert_shape_map = + models[patch.model_id].property_map(vert_shape_map_name).first; + uint32_t patch_index = &patch - &patches.front(); + // map is created during patch creation / dividing + const CvtVI2VI& cvt = patch.mesh.property_map(patch_source_name).first; + // for each point on outline + for (const Loop &loop : patch.loops) + for (const VI &vi_patch : loop) { + VI vi_model = cvt[vi_patch]; + if (!vi_model.is_valid()) continue; + const IntersectingElement *ie = vert_shape_map[vi_model]; + if (ie == nullptr) continue; + assert(ie->shape_point_index != std::numeric_limits::max()); + assert(ie->attr != (unsigned char) IntersectingElement::Type::undefined); + uint32_t pi = ie->shape_point_index; + assert(pi <= count_shapes_points); + std::vector &pds = result[pi]; + uint32_t model_index = patch.model_id; + uint32_t aoi_index = patch.aoi_id; + //uint32_t hi_index = &hi - &patch.outline.front(); + const P3 &p = patch.mesh.point(vi_patch); + float distance = calc_distance(p, pi, shapes_mesh, projection_ratio); + pds.push_back({model_index, aoi_index, patch_index, distance}); + } + } + return result; +} + + +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/Line.hpp" +// functions for choose_best_distance +namespace priv { + +// euler square size of vector stored in Point +float calc_size_sq(const Point &p); + +// structure to store index and distance together +struct ClosePoint +{ + // index of closest point from another shape + uint32_t index = std::numeric_limits::max(); + // squere distance to index + float dist_sq = std::numeric_limits::max(); +}; + +struct SearchData{ +// IMPROVE: float lines are enough +std::vector lines; +// convert line index into Shape point index +std::vector cvt; +// contain lines from prev point to Point index +AABBTreeIndirect::Tree<2, double> tree; +}; + +SearchData create_search_data(const ExPolygons &shapes, const std::vector& mask); +uint32_t get_closest_point_index(const SearchData &sd, size_t line_idx, const Vec2d &hit_point, const ExPolygons &shapes, const ExPolygonsIndices &s2i); + +// use AABB Tree Lines to find closest point +uint32_t find_closest_point_index(const Point &p, const ExPolygons &shapes, const ExPolygonsIndices &s2i, const std::vector &mask); + +std::pair find_closest_point_pair(const ExPolygons &shapes, const std::vector &done_shapes, const ExPolygonsIndices &s2i, const std::vector &mask); + +// Search for closest projection to wanted distance +const ProjectionDistance *get_closest_projection(const ProjectionDistances &distance, float wanted_distance); + +// fill result around known index inside one polygon +void fill_polygon_distances(const ProjectionDistance &pd, uint32_t index, const ExPolygonsIndex &id, ProjectionDistances & result, const ExPolygon &shape, const VDistances &distances); + +// search for closest projection for expolygon +// choose correct cut by source point +void fill_shape_distances(uint32_t start_index, const ProjectionDistance *start_pd, ProjectionDistances &result, const ExPolygonsIndices &s2i, const ExPolygon &shape, const VDistances &distances); + +// find close points between finished and unfinished ExPolygons +ClosePoint find_close_point(const Point &p, ProjectionDistances &result, std::vector& finished_shapes,const ExPolygonsIndices &s2i, const ExPolygons &shapes); + +} + +float priv::calc_size_sq(const Point &p){ + // NOTE: p.squaredNorm() can't be use due to overflow max int value + return (float) p.x() * p.x() + (float) p.y() * p.y(); +} + +priv::SearchData priv::create_search_data(const ExPolygons &shapes, + const std::vector &mask) +{ + // IMPROVE: Use float precission (it is enough) + SearchData sd; + sd.lines.reserve(mask.size()); + sd.cvt.reserve(mask.size()); + size_t index = 0; + auto add_lines = [&sd, &index, &mask] + (const Polygon &poly) { + Vec2d prev = poly.back().cast(); + bool use_point = mask[index + poly.points.size() - 1]; + for (const Point &p : poly.points) { + if (!use_point) { + use_point = mask[index]; + if (use_point) prev = p.cast(); + } else if (!mask[index]) { + use_point = false; + } else { + Vec2d p_d = p.cast(); + sd.lines.emplace_back(prev, p_d); + sd.cvt.push_back(index); + prev = p_d; + } + ++index; + } + }; + + for (const ExPolygon &shape : shapes) { + add_lines(shape.contour); + for (const Polygon &hole : shape.holes) add_lines(hole); + } + sd.tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(sd.lines); + return sd; +} + +uint32_t priv::get_closest_point_index(const SearchData &sd, + size_t line_idx, + const Vec2d &hit_point, + const ExPolygons &shapes, + const ExPolygonsIndices &s2i) +{ + const Linef &line = sd.lines[line_idx]; + Vec2d dir = line.a - line.b; + Vec2d dir_abs = dir.cwiseAbs(); + // use x coordinate + int i = (dir_abs.x() > dir_abs.y())? 0 :1; + + bool use_index = abs(line.a[i] - hit_point[i]) > + abs(line.b[i] - hit_point[i]); + size_t point_index = sd.cvt[line_idx]; + + // Lambda used only for check result + [[maybe_unused]] auto is_same = [&s2i, &shapes] + (const Vec2d &p, size_t i) -> bool { + auto id = s2i.cvt(i); + const ExPolygon &shape = shapes[id.expolygons_index]; + const Polygon &poly = (id.polygon_index == 0) ? + shape.contour : + shape.holes[id.polygon_index - 1]; + auto p_ = p.cast(); + return p_ == poly[id.point_index]; + }; + + if (use_index) { + assert(is_same(line.b, point_index)); + return point_index; + } + auto id = s2i.cvt(point_index); + if (id.point_index != 0) { + assert(is_same(line.a, point_index - 1)); + return point_index - 1; + } + const ExPolygon &shape = shapes[id.expolygons_index]; + size_t count_polygon_points = (id.polygon_index == 0) ? + shape.contour.size() : + shape.holes[id.polygon_index - 1].size(); + size_t prev_point_index = point_index + (count_polygon_points - 1); + assert(is_same(line.a, prev_point_index)); + // return previous point index + return prev_point_index; +} + +// use AABB Tree Lines +uint32_t priv::find_closest_point_index(const Point &p, + const ExPolygons &shapes, + const ExPolygonsIndices &s2i, + const std::vector &mask) +{ + SearchData sd = create_search_data(shapes, mask); + if (sd.tree.nodes().size() == 0){ + // no lines in expolygon, check whether exist point to start + double closest_square_distance = INFINITY; + uint32_t closest_id = -1; + for (uint32_t i = 0; i < mask.size(); i++) + if (mask[i]){ + ExPolygonsIndex ei = s2i.cvt(i); + const Point& s_p = ei.is_contour()? + shapes[ei.expolygons_index].contour[ei.point_index]: + shapes[ei.expolygons_index].holes[ei.hole_index()][ei.point_index]; + double square_distance = (p - s_p).cast().squaredNorm(); + if (closest_id >= mask.size() || + closest_square_distance > square_distance) { + closest_id = i; + closest_square_distance = square_distance; + } + } + assert(closest_id < mask.size()); + return closest_id; + } + size_t line_idx = std::numeric_limits::max(); + Vec2d hit_point; + Vec2d p_d = p.cast(); + [[maybe_unused]] double distance_sq = + AABBTreeLines::squared_distance_to_indexed_lines( + sd.lines, sd.tree, p_d, line_idx, hit_point); + assert(distance_sq > 0); + + // IMPROVE: one could use line ratio to find closest point + return get_closest_point_index(sd, line_idx, hit_point, shapes, s2i); +} + +std::pair priv::find_closest_point_pair( + const ExPolygons &shapes, + const std::vector &done_shapes, + const ExPolygonsIndices &s2i, + const std::vector &mask) +{ + assert(mask.size() == s2i.get_count()); + assert(done_shapes.size() == shapes.size()); + std::vector unfinished_mask = mask; // copy + + size_t index = 0; + for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) { + size_t count = count_points(shapes[shape_index]); + if (done_shapes[shape_index]) { + for (size_t i = 0; i < count; ++i, ++index) + unfinished_mask[index] = false; + } else { + index += count; + } + } + assert(index == s2i.get_count()); + SearchData sd = create_search_data(shapes, unfinished_mask); + + struct ClosestPair + { + size_t finish_idx = std::numeric_limits::max(); + size_t unfinished_line_idx = std::numeric_limits::max(); + Vec2d hit_point = Vec2d(); + double distance_sq = std::numeric_limits::max(); + } cp; + + index = 0; + for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) { + const ExPolygon shape = shapes[shape_index]; + if (!done_shapes[shape_index]) { + index += count_points(shape); + continue; + } + + auto search_in_polygon = [&index, &cp, &sd, &mask](const Polygon& polygon) { + for (size_t i = 0; i < polygon.size(); ++i, ++index) { + if (mask[index] == false) continue; + Vec2d p_d = polygon[i].cast(); + size_t line_idx = std::numeric_limits::max(); + Vec2d hit_point; + double distance_sq = AABBTreeLines::squared_distance_to_indexed_lines( + sd.lines, sd.tree, p_d, line_idx, hit_point, cp.distance_sq); + if (distance_sq < 0 || + distance_sq >= cp.distance_sq) continue; + assert(line_idx < sd.lines.size()); + cp.distance_sq = distance_sq; + cp.unfinished_line_idx = line_idx; + cp.hit_point = hit_point; + cp.finish_idx = index; + } + }; + search_in_polygon(shape.contour); + for (const Polygon& hole: shape.holes) + search_in_polygon(hole); + } + assert(index == s2i.get_count()); + // check that exists result + if (cp.finish_idx == std::numeric_limits::max()) { + return std::make_pair(std::numeric_limits::max(), + std::numeric_limits::max()); + } + + size_t unfinished_idx = get_closest_point_index(sd, cp.unfinished_line_idx, cp.hit_point, shapes, s2i); + return std::make_pair(cp.finish_idx, unfinished_idx); +} + +const priv::ProjectionDistance *priv::get_closest_projection( + const ProjectionDistances &distance, float wanted_distance) +{ + // minimal distance + float min_d = std::numeric_limits::max(); + const ProjectionDistance *min_pd = nullptr; + for (const ProjectionDistance &pd : distance) { + float d = std::fabs(pd.distance - wanted_distance); + // There should be limit for maximal distance + if (min_d > d) { + min_d = d; + min_pd = &pd; + } + } + return min_pd; +} + +void priv::fill_polygon_distances(const ProjectionDistance &pd, + uint32_t index, + const ExPolygonsIndex &id, + ProjectionDistances &result, + const ExPolygon &shape, + const VDistances &distances) +{ + const Points& points = (id.polygon_index == 0) ? + shape.contour.points : + shape.holes[id.polygon_index - 1].points; + // border of indexes for Polygon + uint32_t first_index = index - id.point_index; + uint32_t last_index = first_index + points.size(); + + uint32_t act_index = index; + const ProjectionDistance* act_pd = &pd; + + // Copy starting pd to result + result[act_index] = pd; + + auto exist_next = [&distances, &act_index, &act_pd, &result] + (uint32_t nxt_index) { + const ProjectionDistance *nxt_pd = get_closest_projection(distances[nxt_index] ,act_pd->distance); + // exist next projection distance ? + if (nxt_pd == nullptr) return false; + + // check no rewrite result + assert(result[nxt_index].aoi_index == std::numeric_limits::max()); + // copy founded projection to result + result[nxt_index] = *nxt_pd; // copy + + // next + act_index = nxt_index; + act_pd = &result[nxt_index]; + return true; + }; + + // last index in circle + uint32_t finish_index = (index == first_index) ? (last_index - 1) : + (index - 1); + // Positive iteration inside polygon + do { + uint32_t nxt_index = act_index + 1; + // close loop of indexes inside of contour + if (nxt_index == last_index) nxt_index = first_index; + // check that exist next + if (!exist_next(nxt_index)) break; + } while (act_index != finish_index); + + // when all results for polygon are set no neccessary to iterate negative + if (act_index == finish_index) return; + + act_index = index; + act_pd = &pd; + // Negative iteration inside polygon + do { + uint32_t nxt_index = (act_index == first_index) ? + (last_index-1) : (act_index - 1); + // When iterate negative it must be split to parts + // and can't iterate in circle + assert(nxt_index != index); + // check that exist next + if (!exist_next(nxt_index)) break; + } while (true); +} + +// IMPROVE: when select distance fill in all distances from Patch +void priv::fill_shape_distances(uint32_t start_index, + const ProjectionDistance *start_pd, + ProjectionDistances &result, + const ExPolygonsIndices &s2i, + const ExPolygon &shape, + const VDistances &distances) +{ + uint32_t expolygons_index = s2i.cvt(start_index).expolygons_index; + uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0}); + do { + fill_polygon_distances(*start_pd, start_index, s2i.cvt(start_index),result, shape, distances); + // seaching only inside shape, return index of closed finished point + auto find_close_finished_point = [&first_shape_index, &shape, &result] + (const Point &p) -> ClosePoint { + uint32_t index = first_shape_index; + ClosePoint cp; + auto check_finished_points = [&cp, &result, &index, &p] + (const Points& pts) { + for (const Point &p_ : pts) { + // finished point with some distances + if (result[index].aoi_index == std::numeric_limits::max()) { + ++index; + continue; + } + float distance = calc_size_sq(p_ - p); + if (cp.dist_sq > distance) { + cp.dist_sq = distance; + cp.index = index; + } + ++index; + } + }; + check_finished_points(shape.contour.points); + for (const Polygon &h : shape.holes) + check_finished_points(h.points); + return cp; + }; + + // find next closest pair of points + // (finished + unfinished) in ExPolygon + start_index = std::numeric_limits::max(); // unfinished_index + uint32_t finished_index = std::numeric_limits::max(); + float dist_sq = std::numeric_limits::max(); + + // first index in shape + uint32_t index = first_shape_index; + auto check_unfinished_points = [&index, &result, &distances, &find_close_finished_point, &dist_sq, &start_index, &finished_index] + (const Points& pts) { + for (const Point &p : pts) { + // try find unfinished + if (result[index].aoi_index != + std::numeric_limits::max() || + distances[index].empty()) { + ++index; + continue; + } + ClosePoint cp = find_close_finished_point(p); + if (dist_sq > cp.dist_sq) { + dist_sq = cp.dist_sq; + start_index = index; + finished_index = cp.index; + } + ++index; + } + }; + // for each unfinished points + check_unfinished_points(shape.contour.points); + for (const Polygon &h : shape.holes) + check_unfinished_points(h.points); + } while (start_index != std::numeric_limits::max()); +} + +priv::ClosePoint priv::find_close_point(const Point &p, + ProjectionDistances &result, + std::vector &finished_shapes, + const ExPolygonsIndices &s2i, + const ExPolygons &shapes) +{ + // result + ClosePoint cp; + // for all finished points + for (uint32_t shape_index = 0; shape_index < shapes.size(); ++shape_index) { + if (!finished_shapes[shape_index]) continue; + const ExPolygon &shape = shapes[shape_index]; + uint32_t index = s2i.cvt({shape_index, 0, 0}); + auto find_close_point_in_points = [&p, &cp, &index, &result] + (const Points &pts){ + for (const Point &p_ : pts) { + // Exist result (is finished) ? + if (result[index].aoi_index == + std::numeric_limits::max()) { + ++index; + continue; + } + float distance_sq = calc_size_sq(p - p_); + if (cp.dist_sq > distance_sq) { + cp.dist_sq = distance_sq; + cp.index = index; + } + ++index; + } + }; + find_close_point_in_points(shape.contour.points); + // shape could be inside of another shape's hole + for (const Polygon& h:shape.holes) + find_close_point_in_points(h.points); + } + return cp; +} + +// IMPROVE: when select distance fill in all distances from Patch +priv::ProjectionDistances priv::choose_best_distance( + const VDistances &distances, const ExPolygons &shapes, const Point &start, const ExPolygonsIndices &s2i, const SurfacePatches &patches) +{ + assert(distances.size() == count_points(shapes)); + + // vector of patches for shape + std::vector> shapes_patches(shapes.size()); + for (const SurfacePatch &patch : patches) + shapes_patches[patch.shape_id].push_back(&patch-&patches.front()); + + // collect one closest projection for each outline point + ProjectionDistances result(distances.size()); + + // store info about finished shapes + std::vector finished_shapes(shapes.size(), {false}); + + // wanted distance from ideal projection + // Distances are relative to projection distance + // so first wanted distance is the closest one (ZERO) + float wanted_distance = 0.f; + + std::vector mask_distances(s2i.get_count(), {true}); + for (const auto &d : distances) + if (d.empty()) mask_distances[&d - &distances.front()] = false; + + // Select point from shapes(text contour) which is closest to center (all in 2d) + uint32_t unfinished_index = find_closest_point_index(start, shapes, s2i, mask_distances); + assert(unfinished_index < s2i.get_count()); + if (unfinished_index >= s2i.get_count()) + // no point to select + return result; + +#ifdef DEBUG_OUTPUT_DIR + Connections connections; + connections.reserve(shapes.size()); + connections.emplace_back(unfinished_index, unfinished_index); +#endif // DEBUG_OUTPUT_DIR + + do { + const ProjectionDistance* pd = get_closest_projection(distances[unfinished_index], wanted_distance); + // selection of closest_id should proove that pd has value + // (functions: get_closest_point_index and find_close_point_in_points) + assert(pd != nullptr); + uint32_t expolygons_index = s2i.cvt(unfinished_index).expolygons_index; + const ExPolygon &shape = shapes[expolygons_index]; + std::vector &shape_patches = shapes_patches[expolygons_index]; + if (shape_patches.size() == 1){ + // Speed up, only one patch so copy distance from patch + uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0}); + uint32_t laset_shape_index = first_shape_index + count_points(shape); + for (uint32_t i = first_shape_index; i < laset_shape_index; ++i) { + const ProjectionDistances &pds = distances[i]; + if (pds.empty()) continue; + // check that index belongs to patch + assert(pds.front().patch_index == shape_patches.front()); + result[i] = pds.front(); + if (pds.size() == 1) continue; + + float relative_distance = fabs(result[i].distance - pd->distance); + // patch could contain multiple value for one outline point + // so choose closest to start point + for (uint32_t pds_index = 1; pds_index < pds.size(); ++pds_index) { + // check that index still belongs to same patch + assert(pds[pds_index].patch_index == shape_patches.front()); + float relative_distance2 = fabs(pds[pds_index].distance - pd->distance); + if (relative_distance > relative_distance2) { + relative_distance = relative_distance2; + result[i] = pds[pds_index]; + } + } + } + } else { + // multiple patches for expolygon + // check that exist patch to fill shape + assert(!shape_patches.empty()); + fill_shape_distances(unfinished_index, pd, result, s2i, shape, distances); + } + + finished_shapes[expolygons_index] = true; + // The most close points between finished and unfinished shapes + auto [finished, unfinished] = find_closest_point_pair( + shapes, finished_shapes, s2i, mask_distances); + + // detection of end (best doesn't have value) + if (finished == std::numeric_limits::max()) break; + + assert(unfinished != std::numeric_limits::max()); + const ProjectionDistance &closest_pd = result[finished]; + // check that best_cp is finished and has result + assert(closest_pd.aoi_index != std::numeric_limits::max()); + wanted_distance = closest_pd.distance; + unfinished_index = unfinished; + +#ifdef DEBUG_OUTPUT_DIR + connections.emplace_back(finished, unfinished); +#endif // DEBUG_OUTPUT_DIR + } while (true); //(unfinished_index != std::numeric_limits::max()); +#ifdef DEBUG_OUTPUT_DIR + store(shapes, mask_distances, connections, DEBUG_OUTPUT_DIR + "closest_points.svg"); +#endif // DEBUG_OUTPUT_DIR + return result; +} + +// functions to help 'diff_model' +namespace priv { +const VI default_vi(std::numeric_limits::max()); + +// Keep info about intersection source +struct Source{ HI hi; int sdim=0;}; +using Sources = std::vector; +const std::string vertex_source_map_name = "v:SourceIntersecting"; +using VertexSourceMap = CutMesh::Property_map; + +/// +/// Corefine visitor +/// Store intersection source for vertices of constrained edge of tm1 +/// Must be used with corefine flag no modification of tm2 +/// +struct IntersectionSources +{ + const CutMesh *patch; // patch + const CutMesh *model; // const model + + VertexSourceMap vmap; + + // keep sources from call intersection_point_detected + // until call new_vertex_added + Sources* sources; + + // count intersections + void intersection_point_detected(std::size_t i_id, + int sdim, + HI h_f, + HI h_e, + const CutMesh &tm_f, + const CutMesh &tm_e, + bool is_target_coplanar, + bool is_source_coplanar) + { + Source source; + if (&tm_e == model) { + source = {h_e, sdim}; + // check other CGAL model that is patch + assert(&tm_f == patch); + if (is_target_coplanar) { + assert(sdim == 0); + vmap[tm_f.source(h_f)] = source; + } + if (is_source_coplanar) { + assert(sdim == 0); + vmap[tm_f.target(h_f)] = source; + } + + // clear source to be able check that this intersection source is + // not used any more + if (is_source_coplanar || is_target_coplanar) source = {}; + } else { + source = {h_f, sdim}; + assert(&tm_f == model && &tm_e == patch); + assert(!is_target_coplanar); + assert(!is_source_coplanar); + // if (is_target_coplanar) vmap[tm_e.source(h_e)] = source; + // if (is_source_coplanar) vmap[tm_e.target(h_e)] = source; + // if (sdim == 0) + // vmap[tm_e.target(h_e)] = source; + } + + // By documentation i_id is consecutive. + // check id goes in a row, without skips + assert(sources->size() == i_id); + // add source of intersection + sources->push_back(source); + } + + /// + /// Store VI to intersections by i_id + /// + /// Order number of intersection point + /// New added vertex + /// Affected mesh + void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm) + { + // check that it is first insertation into item of vmap + assert(!vmap[v].hi.is_valid()); + // check valid addresing into sources + assert(i_id < sources->size()); + // check that source has value + assert(sources->at(i_id).hi.is_valid()); + vmap[v] = sources->at(i_id); + } + + // Not used visitor functions + void before_subface_creations(FI /* f_old */, CutMesh & /* mesh */) {} + void after_subface_created(FI /* f_new */, CutMesh & /* mesh */) {} + void after_subface_creations(CutMesh &) {} + void before_subface_created(CutMesh &) {} + void before_edge_split(HI /* h */, CutMesh & /* tm */) {} + void edge_split(HI /* hnew */, CutMesh & /* tm */) {} + void after_edge_split() {} + void add_retriangulation_edge(HI /* h */, CutMesh & /* tm */) {} +}; + +/// +/// Create map1 and map2 +/// +/// Convert tm1.face to type +/// Corefined mesh +/// Source of intersection +/// Identify constrainde edge +/// Convert tm1.face to type +void create_face_types(FaceTypeMap &map, + const CutMesh &tm1, + const CutMesh &tm2, + const EdgeBoolMap &ecm, + const VertexSourceMap &sources); + +/// +/// Implement 'cut' Minus 'clipper', where clipper is reverse input Volume +/// NOTE: clipper will be modified (corefined by cut) !!! +/// +/// differ from +/// differ what +/// True on succes, otherwise FALSE +bool clip_cut(SurfacePatch &cut, CutMesh clipper); + +BoundingBoxf3 bounding_box(const CutAOI &cut, const CutMesh &mesh); +BoundingBoxf3 bounding_box(const CutMesh &mesh); +BoundingBoxf3 bounding_box(const SurfacePatch &ecut); + +/// +/// Create patch +/// +/// Define patch faces +/// Source of fis +/// NOTE: Need temporary add property map for convert vertices +/// Options to reduce vertices from fis. +/// NOTE: Used for skip vertices made by diagonal edge in rectangle of shape side +/// Patch +SurfacePatch create_surface_patch(const std::vector &fis, + /*const*/ CutMesh &mesh, + const ReductionMap *rmap = nullptr); + +} // namespace priv + +void priv::create_face_types(FaceTypeMap &map, + const CutMesh &tm1, + const CutMesh &tm2, + const EdgeBoolMap &ecm, + const VertexSourceMap &sources) +{ + auto get_intersection_source = [&tm2](const Source& s1, const Source& s2)->FI{ + // when one of sources is face than return it + FI fi1 = tm2.face(s1.hi); + if (s1.sdim == 2) return fi1; + FI fi2 = tm2.face(s2.hi); + if (s2.sdim == 2) return fi2; + // both vertices are made by same source triangle + if (fi1 == fi2) return fi1; + + // when one from sources is edge second one decide side of triangle triangle + HI hi1_opposit = tm2.opposite(s1.hi); + FI fi1_opposit; + if (hi1_opposit.is_valid()) + fi1_opposit = tm2.face(hi1_opposit); + if (fi2 == fi1_opposit) return fi2; + + HI hi2_opposit = tm2.opposite(s2.hi); + FI fi2_opposit; + if (hi2_opposit.is_valid()) + fi2_opposit = tm2.face(hi2_opposit); + if (fi1 == fi2_opposit) return fi1; + if (fi1_opposit.is_valid() && fi1_opposit == fi2_opposit) + return fi1_opposit; + + // when intersection is vertex need loop over neighbor + for (FI fi_around_hi1 : tm2.faces_around_target(s1.hi)) { + for (FI fi_around_hi2 : tm2.faces_around_target(s2.hi)) { + if (fi_around_hi1 == fi_around_hi2) + return fi_around_hi1; + } + } + + // should never rich it + // Exist case when do not know source triangle for decide side of intersection + assert(false); + return FI(); + }; + + for (FI fi : tm1.faces()) map[fi] = FaceType::not_constrained; + for (EI ei1 : tm1.edges()) { + if (!get(ecm, ei1)) continue; + + // get faces from tm1 (f1a + f1b) + HI hi1 = tm1.halfedge(ei1); + assert(hi1.is_valid()); + FI f1a = tm1.face(hi1); + assert(f1a.is_valid()); + HI hi_op = tm1.opposite(hi1); + assert(hi_op.is_valid()); + FI f1b = tm1.face(hi_op); + assert(f1b.is_valid()); + + // get faces from tm2 (f2a + f2b) + VI vi1_source = tm1.source(hi1); + assert(vi1_source.is_valid()); + VI vi1_target = tm1.target(hi1); + assert(vi1_target.is_valid()); + + const Source &s_s = sources[vi1_source]; + const Source &s_t = sources[vi1_target]; + FI fi2 = get_intersection_source(s_s, s_t); + + // in release solve situation that face was NOT deduced + if (!fi2.is_valid()) continue; + + HI hi2 = tm2.halfedge(fi2); + std::array t; + size_t ti =0; + for (VI vi2 : tm2.vertices_around_face(hi2)) + t[ti++] = &tm2.point(vi2); + + // triangle tip from face f1a + VI vi1a_tip = tm1.target(tm1.next(hi1)); + assert(vi1a_tip.is_valid()); + const P3 &p = tm1.point(vi1a_tip); + + // check if f1a is behinde f2a + // inside mean it will be used + // outside will be discarded + if (CGAL::orientation(*t[0], *t[1], *t[2], p) == CGAL::POSITIVE) { + map[f1a] = FaceType::inside; + map[f1b] = FaceType::outside; + } else { + map[f1a] = FaceType::outside; + map[f1b] = FaceType::inside; + } + } +} + +#include +#include +bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper) +{ + CutMesh& tm = cut.mesh; + // create backup for case that there is no intersection + CutMesh backup_copy = tm; + + class ExistIntersectionClipVisitor: public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor + { + bool* exist_intersection; + public: + ExistIntersectionClipVisitor(bool *exist_intersection): exist_intersection(exist_intersection){} + void intersection_point_detected(std::size_t, int , HI, HI, const CutMesh&, const CutMesh&, bool, bool) + { *exist_intersection = true;} + }; + bool exist_intersection = false; + ExistIntersectionClipVisitor visitor{&exist_intersection}; + + // namep parameters for model tm and function clip + const auto &np_tm = CGAL::parameters::visitor(visitor) + .throw_on_self_intersection(false); + + // name parameters for model clipper and function clip + const auto &np_c = CGAL::parameters::throw_on_self_intersection(false); + // Can't use 'do_not_modify', when Ture than clipper has to be closed !! + // .do_not_modify(true); + // .throw_on_self_intersection(false); is set automaticaly by param 'do_not_modify' + // .clip_volume(false); is set automaticaly by param 'do_not_modify' + + bool suc = CGAL::Polygon_mesh_processing::clip(tm, clipper, np_tm, np_c); + + // true if the output surface mesh is manifold. + // If false is returned tm and clipper are only corefined. + assert(suc); + // decide what TODO when can't clip source object !?! + if (!exist_intersection || !suc) { + // TODO: test if cut is fully in or fully out!! + cut.mesh = backup_copy; + return false; + } + return true; +} + +BoundingBoxf3 priv::bounding_box(const CutAOI &cut, const CutMesh &mesh) { + const P3& p_from_cut = mesh.point(mesh.target(mesh.halfedge(cut.first.front()))); + Vec3d min = to_vec3d(p_from_cut); + Vec3d max = min; + for (FI fi : cut.first) { + for(VI vi: mesh.vertices_around_face(mesh.halfedge(fi))){ + const P3& p = mesh.point(vi); + for (size_t i = 0; i < 3; ++i) { + if (min[i] > p[i]) min[i] = p[i]; + if (max[i] < p[i]) max[i] = p[i]; + } + } + } + return BoundingBoxf3(min, max); +} + +BoundingBoxf3 priv::bounding_box(const CutMesh &mesh) +{ + Vec3d min = to_vec3d(*mesh.points().begin()); + Vec3d max = min; + for (VI vi : mesh.vertices()) { + const P3 &p = mesh.point(vi); + for (size_t i = 0; i < 3; ++i) { + if (min[i] > p[i]) min[i] = p[i]; + if (max[i] < p[i]) max[i] = p[i]; + } + } + return BoundingBoxf3(min, max); +} + +BoundingBoxf3 priv::bounding_box(const SurfacePatch &ecut) { + return bounding_box(ecut.mesh); +} + +priv::SurfacePatch priv::create_surface_patch(const std::vector &fis, + /* const */ CutMesh &mesh, + const ReductionMap *rmap) +{ + auto is_counted = mesh.add_property_map("v:is_counted").first; + uint32_t count_vertices = 0; + if (rmap == nullptr) { + for (FI fi : fis) + for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) + if (!is_counted[vi]) { + is_counted[vi] = true; + ++count_vertices; + } + } else { + for (FI fi : fis) + for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) { + // Will vertex be reduced? + if ((*rmap)[vi].is_valid()) continue; + if (!is_counted[vi]) { + is_counted[vi] = true; + ++count_vertices; + } + } + } + mesh.remove_property_map(is_counted); + + uint32_t count_faces = fis.size(); + // IMPROVE: Value is greater than neccessary, count edges used twice + uint32_t count_edges = count_faces*3; + + CutMesh cm; + cm.reserve(count_vertices, count_edges, count_faces); + + // vertex conversion function from mesh VI to result VI + CvtVI2VI mesh2result = mesh.add_property_map("v:mesh2result").first; + + if (rmap == nullptr) { + for (FI fi : fis) { + std::array t; + int index = 0; + for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) { + VI &vi_cvt = mesh2result[vi]; + if (!vi_cvt.is_valid()) { + vi_cvt = VI(cm.vertices().size()); + cm.add_vertex(mesh.point(vi)); + } + t[index++] = vi_cvt; + } + cm.add_face(t[0], t[1], t[2]); + } + } else { + for (FI fi :fis) { + std::array t; + int index = 0; + bool exist_reduction = false; + for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) { + VI vi_r = (*rmap)[vi]; + if (vi_r.is_valid()) { + exist_reduction = true; + vi = vi_r; + } + VI &vi_cvt = mesh2result[vi]; + if (!vi_cvt.is_valid()) { + vi_cvt = VI(cm.vertices().size()); + cm.add_vertex(mesh.point(vi)); + } + t[index++] = vi_cvt; + } + + // prevent add reduced triangle + if (exist_reduction && + (t[0] == t[1] || + t[1] == t[2] || + t[2] == t[0])) + continue; + + cm.add_face(t[0], t[1], t[2]); + } + } + + assert(count_vertices == cm.vertices().size()); + assert((rmap == nullptr && count_faces == cm.faces().size()) || + (rmap != nullptr && count_faces >= cm.faces().size())); + assert(count_edges >= cm.edges().size()); + + // convert VI from this patch to source VI, when exist + CvtVI2VI cvt = cm.add_property_map(patch_source_name).first; + // vi_s .. VertexIndex into mesh (source) + // vi_d .. new VertexIndex in cm (destination) + for (VI vi_s : mesh.vertices()) { + VI vi_d = mesh2result[vi_s]; + if (!vi_d.is_valid()) continue; + cvt[vi_d] = vi_s; + } + mesh.remove_property_map(mesh2result); + return {std::move(cm)}; +} + +// diff_models help functions +namespace priv { + +struct SurfacePatchEx +{ + SurfacePatch patch; + + // flag that part will be deleted + bool full_inside = false; + // flag that Patch could contain more than one part + bool just_cliped = false; +}; +using SurfacePatchesEx = std::vector; + + +using QDS = std::vector; +/// +/// Create bounding boxes for AOI +/// +/// Cutted AOI from models +/// Source points of cuts +/// Bounding boxes +QDS create_qds(const VCutAOIs &cuts, const CutMeshes &cut_models); + +using Primitive = CGAL::AABB_face_graph_triangle_primitive; +using Traits = CGAL::AABB_traits; +using Ray = EpicKernel::Ray_3; +using Tree = CGAL::AABB_tree; +using Trees = std::vector; +/// +/// Create AABB trees for check when patch is whole inside of model +/// +/// Source for trees +/// trees +Trees create_trees(const CutMeshes &models); + +/// +/// Check whether bounding box has intersection with model +/// +/// Bounding box to check +/// Model to check with +/// All bounding boxes from VCutAOIs +/// Help index into VCutAOIs +/// True when exist bounding boxes intersection +bool has_bb_intersection(const BoundingBoxf3 &bb, + size_t model_index, + const QDS &qds, + const ModelCut2index &m2i); + +/// +/// Only for model without intersection +/// Use ray (in projection direction) from a point from patch +/// and count intersections: pair .. outside | odd .. inside +/// +/// Patch to check +/// Model converted to AABB tree +/// Define direction of projection +/// True when patch point lay inside of model defined by tree, +/// otherwise FALSE +bool is_patch_inside_of_model(const SurfacePatch &patch, + const Tree &tree, + const Project3d &projection); + +/// +/// Return some shape point index which identify shape +/// NOTE: Used to find expolygon index +/// +/// Used to search source shapes poin +/// +/// shape point index +uint32_t get_shape_point_index(const CutAOI &cut, const CutMesh &model); + +using PatchNumber = CutMesh::Property_map; +/// +/// Separate triangles singned with number n +/// +/// Face indices owned by separate patch +/// Original patch +/// NOTE: Can't be const. For indexing vetices need temporary add property map +/// conversion map +/// Just separated patch +SurfacePatch separate_patch(const std::vector &fis, + /* const*/ SurfacePatch &patch, + const CvtVI2VI &cvt_from); + +/// +/// Separate connected triangles into it's own patches +/// new patches are added to back of input patches +/// +/// index into patches +/// In/Out Patches +void divide_patch(size_t i, SurfacePatchesEx &patches); + +/// +/// Fill outline in patches by open edges +/// +/// Input/Output meshes with open edges +void collect_open_edges(SurfacePatches &patches); + +} // namespace priv + +std::vector priv::create_qds(const VCutAOIs &cuts, + const CutMeshes &cut_models) +{ + size_t count = 0; + for (const CutAOIs &cut : cuts) count += cut.size(); + + std::vector qds; + qds.reserve(count); + for (size_t model_index = 0; model_index < cut_models.size(); ++model_index) { + const CutMesh &cut_model = cut_models[model_index]; + const CutAOIs &cutAOIs = cuts[model_index]; + for (size_t cut_index = 0; cut_index < cutAOIs.size(); ++cut_index) { + const CutAOI &cut = cutAOIs[cut_index]; + qds.push_back(bounding_box(cut, cut_model)); + } + } + return qds; +} + + +priv::Trees priv::create_trees(const CutMeshes &models) { + Trees result; + result.reserve(models.size()); + for (const CutMesh &model : models) { + Tree tree; + tree.insert(faces(model).first, faces(model).second, model); + tree.build(); + result.emplace_back(std::move(tree)); + } + return result; +} + +bool priv::has_bb_intersection(const BoundingBoxf3 &bb, + size_t model_index, + const QDS &qds, + const ModelCut2index &m2i) +{ + const auto&offsets = m2i.get_offsets(); + // for cut index with model_index2 + size_t start = offsets[model_index]; + size_t next = model_index + 1; + size_t end = (next < offsets.size()) ? offsets[next] : m2i.get_count(); + for (size_t bb_index = start; bb_index < end; bb_index++) + if (bb.intersects(qds[bb_index])) return true; + return false; +} + +bool priv::is_patch_inside_of_model(const SurfacePatch &patch, + const Tree &tree, + const Project3d &projection) +{ + // TODO: Solve model with hole in projection direction !!! + const P3 &a = patch.mesh.point(VI(0)); + Vec3d a_ = to_vec3d(a); + Vec3d b_ = projection.project(a_); + P3 b(b_.x(), b_.y(), b_.z()); + + Ray ray_query(a, b); + size_t count = tree.number_of_intersected_primitives(ray_query); + bool is_in = (count % 2) == 1; + + // try opposit direction result should be same, otherwise open model is used + //Vec3f c_ = a_ - (b_ - a_); // opposit direction + //P3 c(c_.x(), c_.y(), c_.z()); + //Ray ray_query2(a, b); + //size_t count2 = tree.number_of_intersected_primitives(ray_query2); + //bool is_in2 = (count2 % 2) == 1; + assert(((tree.number_of_intersected_primitives( + Ray(a, P3(2 * a.x() - b.x(), + 2 * a.y() - b.y(), + 2 * a.z() - b.z()))) % + 2) == 1) == is_in); + return is_in; +} + +uint32_t priv::get_shape_point_index(const CutAOI &cut, const CutMesh &model) +{ + // map is created during intersection by corefine visitor + const VertexShapeMap &vert_shape_map = model.property_map(vert_shape_map_name).first; + // for each half edge of outline + for (HI hi : cut.second) { + VI vi = model.source(hi); + const IntersectingElement *ie = vert_shape_map[vi]; + if (ie == nullptr) continue; + assert(ie->shape_point_index != std::numeric_limits::max()); + return ie->shape_point_index; + } + // can't found any intersecting element in cut + assert(false); + return 0; +} + +priv::SurfacePatch priv::separate_patch(const std::vector& fis, + SurfacePatch &patch, + const CvtVI2VI &cvt_from) +{ + assert(patch.mesh.is_valid()); + SurfacePatch patch_new = create_surface_patch(fis, patch.mesh); + patch_new.bb = bounding_box(patch_new.mesh); + patch_new.aoi_id = patch.aoi_id; + patch_new.model_id = patch.model_id; + patch_new.shape_id = patch.shape_id; + // fix cvt + CvtVI2VI cvt = patch_new.mesh.property_map(patch_source_name).first; + for (VI &vi : cvt) { + if (!vi.is_valid()) continue; + vi = cvt_from[vi]; + } + return patch_new; +} + +void priv::divide_patch(size_t i, SurfacePatchesEx &patches) +{ + SurfacePatchEx &patch_ex = patches[i]; + assert(patch_ex.just_cliped); + patch_ex.just_cliped = false; + + SurfacePatch& patch = patch_ex.patch; + CutMesh& cm = patch.mesh; + assert(!cm.faces().empty()); + std::string patch_number_name = "f:patch_number"; + CutMesh::Property_map is_processed = cm.add_property_map(patch_number_name, false).first; + + const CvtVI2VI& cvt_from = patch.mesh.property_map(patch_source_name).first; + + std::vector fis; + fis.reserve(cm.faces().size()); + + SurfacePatchesEx new_patches; + std::vector queue; + // IMPROVE: create groups around triangles and than connect groups + for (FI fi_cm : cm.faces()) { + if (is_processed[fi_cm]) continue; + assert(queue.empty()); + queue.push_back(fi_cm); + if (!fis.empty()) { + // Be carefull after push to patches, + // all ref on patch contain non valid values + SurfacePatchEx patch_ex_n; + patch_ex_n.patch = separate_patch(fis, patch, cvt_from); + patch_ex_n.patch.is_whole_aoi = false; + new_patches.push_back(std::move(patch_ex_n)); + fis.clear(); + } + // flood fill from triangle fi_cm to surrounding + do { + FI fi_q = queue.back(); + queue.pop_back(); + if (is_processed[fi_q]) continue; + is_processed[fi_q] = true; + fis.push_back(fi_q); + HI hi = cm.halfedge(fi_q); + for (FI fi : cm.faces_around_face(hi)) { + // by documentation The face descriptor may be the null face, and it may be several times the same face descriptor. + if (!fi.is_valid()) continue; + if (!is_processed[fi]) queue.push_back(fi); + } + } while (!queue.empty()); + } + cm.remove_property_map(is_processed); + assert(!fis.empty()); + + // speed up for only one patch - no dividing (the most common) + if (new_patches.empty()) { + patch.bb = bounding_box(cm); + patch.is_whole_aoi = false; + } else { + patch = separate_patch(fis, patch, cvt_from); + patches.insert(patches.end(), new_patches.begin(), new_patches.end()); + } +} + +void priv::collect_open_edges(SurfacePatches &patches) { + std::vector open_half_edges; + for (SurfacePatch &patch : patches) { + open_half_edges.clear(); + const CutMesh &mesh = patch.mesh; + for (FI fi : mesh.faces()) { + HI hi1 = mesh.halfedge(fi); + assert(hi1.is_valid()); + HI hi2 = mesh.next(hi1); + assert(hi2.is_valid()); + HI hi3 = mesh.next(hi2); + assert(hi3.is_valid()); + // Is fi triangle? + assert(mesh.next(hi3) == hi1); + for (HI hi : {hi1, hi2, hi3}) { + HI hi_op = mesh.opposite(hi); + FI fi_op = mesh.face(hi_op); + if (!fi_op.is_valid()) + open_half_edges.push_back(hi); + } + } + patch.loops = create_loops(open_half_edges, mesh); + } +} + +priv::SurfacePatches priv::diff_models(VCutAOIs &cuts, + /*const*/ CutMeshes &cut_models, + /*const*/ CutMeshes &models, + const Project3d &projection) +{ + // IMPROVE: when models contain ONE mesh. It is only about convert cuts to patches + // and reduce unneccessary triangles on contour + + //Convert model_index and cut_index into one index + priv::ModelCut2index m2i(cuts); + + // create bounding boxes for cuts + std::vector qds = create_qds(cuts, cut_models); + Trees trees(models.size()); + + SurfacePatches patches; + + // queue of patches for one AOI (permanent with respect to for loop) + SurfacePatchesEx aoi_patches; + + //SurfacePatches aoi_patches; + patches.reserve(m2i.get_count()); // only approximation of count + size_t index = 0; + for (size_t model_index = 0; model_index < models.size(); ++model_index) { + CutAOIs &model_cuts = cuts[model_index]; + CutMesh &cut_model_ = cut_models[model_index]; + const CutMesh &cut_model = cut_model_; + ReductionMap vertex_reduction_map = cut_model_.add_property_map(vertex_reduction_map_name).first; + create_reduce_map(vertex_reduction_map, cut_model); + + for (size_t cut_index = 0; cut_index < model_cuts.size(); ++cut_index, ++index) { + const CutAOI &cut = model_cuts[cut_index]; + SurfacePatchEx patch_ex; + SurfacePatch &patch = patch_ex.patch; + patch = create_surface_patch(cut.first, cut_model_, &vertex_reduction_map); + patch.bb = qds[index]; + patch.aoi_id = cut_index; + patch.model_id = model_index; + patch.shape_id = get_shape_point_index(cut, cut_model); + patch.is_whole_aoi = true; + + aoi_patches.clear(); + aoi_patches.push_back(patch_ex); + for (size_t model_index2 = 0; model_index2 < models.size(); ++model_index2) { + // do not clip source model itself + if (model_index == model_index2) continue; + for (SurfacePatchEx &patch_ex : aoi_patches) { + SurfacePatch &patch = patch_ex.patch; + if (has_bb_intersection(patch.bb, model_index2, qds, m2i) && + clip_cut(patch, models[model_index2])){ + patch_ex.just_cliped = true; + } else { + // build tree on demand + // NOTE: it is possible not neccessary: e.g. one model + Tree &tree = trees[model_index2]; + if (tree.empty()) { + const CutMesh &model = models[model_index2]; + auto f_range = faces(model); + tree.insert(f_range.first, f_range.second, model); + tree.build(); + } + if (is_patch_inside_of_model(patch, tree, projection)) + patch_ex.full_inside = true; + } + } + // erase full inside + for (size_t i = aoi_patches.size(); i != 0; --i) { + auto it = aoi_patches.begin() + (i - 1); + if (it->full_inside) aoi_patches.erase(it); + } + + // detection of full AOI inside of model + if (aoi_patches.empty()) break; + + // divide cliped into parts + size_t end = aoi_patches.size(); + for (size_t i = 0; i < end; ++i) + if (aoi_patches[i].just_cliped) + divide_patch(i, aoi_patches); + } + + if (!aoi_patches.empty()) { + patches.reserve(patches.size() + aoi_patches.size()); + for (SurfacePatchEx &patch : aoi_patches) + patches.push_back(std::move(patch.patch)); + + } + } + cut_model_.remove_property_map(vertex_reduction_map); + } + + // Also use outline inside of patches(made by non manifold models) + // IMPROVE: trace outline from AOIs + collect_open_edges(patches); + return patches; +} + +bool priv::is_over_whole_expoly(const SurfacePatch &patch, + const ExPolygons &shapes, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes) +{ + if (!patch.is_whole_aoi) return false; + return is_over_whole_expoly(cutAOIs[patch.model_id][patch.aoi_id], + shapes[patch.shape_id], + meshes[patch.model_id]); +} + +bool priv::is_over_whole_expoly(const CutAOI &cutAOI, + const ExPolygon &shape, + const CutMesh &mesh) +{ + // NonInterupted contour is without other point and contain all from shape + const VertexShapeMap &vert_shape_map = mesh.property_map(vert_shape_map_name).first; + for (HI hi : cutAOI.second) { + const IntersectingElement *ie_s = vert_shape_map[mesh.source(hi)]; + const IntersectingElement *ie_t = vert_shape_map[mesh.target(hi)]; + if (ie_s == nullptr || ie_t == nullptr) + return false; + + assert(ie_s->attr != (unsigned char) IntersectingElement::Type::undefined); + assert(ie_t->attr != (unsigned char) IntersectingElement::Type::undefined); + + // check if it is neighbor indices + uint32_t i_s = ie_s->shape_point_index; + uint32_t i_t = ie_t->shape_point_index; + assert(i_s != std::numeric_limits::max()); + assert(i_t != std::numeric_limits::max()); + if (i_s == std::numeric_limits::max() || + i_t == std::numeric_limits::max()) + return false; + + // made by same index + if (i_s == i_t) continue; + + // order from source to target + if (i_s > i_t) { + std::swap(i_s, i_t); + std::swap(ie_s, ie_t); + } + // Must be after fix order !! + bool is_last_polygon_segment = ie_s->is_first() && ie_t->is_last(); + if (is_last_polygon_segment) { + std::swap(i_s, i_t); + std::swap(ie_s, ie_t); + } + + // Is continous indices + if (!is_last_polygon_segment && + (ie_s->is_last() || (i_s + 1) != i_t)) + return false; + + IntersectingElement::Type t_s = ie_s->get_type(); + IntersectingElement::Type t_t = ie_t->get_type(); + if (t_s == IntersectingElement::Type::undefined || + t_t == IntersectingElement::Type::undefined) + return false; + + // next segment must start with edge intersection + if (t_t != IntersectingElement::Type::edge_1) + return false; + + // After face1 must be edge2 or face2 + if (t_s == IntersectingElement::Type::face_1) + return false; + } + + // When all open edges are on contour than there is NO holes is shape + auto is_open = [&mesh](HI hi)->bool { + HI opposite = mesh.opposite(hi); + return !mesh.face(opposite).is_valid(); + }; + + std::vector opens; // copy + opens.reserve(cutAOI.second.size()); + for (HI hi : cutAOI.second) // from lower to bigger + if (is_open(hi)) opens.push_back(hi); + std::sort(opens.begin(), opens.end()); + + for (FI fi: cutAOI.first) { + HI face_hi = mesh.halfedge(fi); + for (HI hi : mesh.halfedges_around_face(face_hi)) { + if (!is_open(hi)) continue; + // open edge + auto lb = std::lower_bound(opens.begin(), opens.end(), hi); + if (lb == opens.end() || *lb != hi) + return false; // not in contour + } + } + return true; +} + +std::vector priv::select_patches(const ProjectionDistances &best_distances, + const SurfacePatches &patches, + const ExPolygons &shapes, + const BoundingBox &shapes_bb, + const ExPolygonsIndices &s2i, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes, + const Project &projection) +{ + // extension to cover numerical mistake made by back projection patch from 3d to 2d + // Calculated as one percent of average size(width and height) + Point s = shapes_bb.size(); + const float extend_delta = (s.x() + s.y())/ float(2 * 100); + + // vector of patches for shape + std::vector> used_shapes_patches(shapes.size()); + std::vector in_distances(patches.size(), {false}); + for (const ProjectionDistance &d : best_distances) { + // exist valid projection for shape point? + if (d.patch_index == std::numeric_limits::max()) continue; + if (in_distances[d.patch_index]) continue; + in_distances[d.patch_index] = true; + + ExPolygonsIndex id = s2i.cvt(&d - &best_distances.front()); + used_shapes_patches[id.expolygons_index].push_back(d.patch_index); + } + + // vector of patches for shape + std::vector> shapes_patches(shapes.size()); + for (const SurfacePatch &patch : patches) + shapes_patches[patch.shape_id].push_back(&patch - &patches.front()); + +#ifdef DEBUG_OUTPUT_DIR + std::string store_dir = DEBUG_OUTPUT_DIR + "select_patches/"; + prepare_dir(store_dir); +#endif // DEBUG_OUTPUT_DIR + + for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) { + const ExPolygon &shape = shapes[shape_index]; + std::vector &used_shape_patches = used_shapes_patches[shape_index]; + if (used_shape_patches.empty()) continue; + // is used all exist patches? + if (used_shapes_patches.size() == shapes_patches[shape_index].size()) continue; + if (used_shape_patches.size() == 1) { + uint32_t patch_index = used_shape_patches.front(); + const SurfacePatch &patch = patches[patch_index]; + if (is_over_whole_expoly(patch, shapes, cutAOIs, meshes)) continue; + } + + // only shapes containing multiple patches + // or not full filled are back projected (hard processed) + + // intersection of converted patches to 2d + ExPolygons fill; + fill.reserve(used_shape_patches.size()); + + // Heuristics to predict which patch to be used need average patch depth + Vec2d used_patches_depth(std::numeric_limits::max(), std::numeric_limits::min()); + for (uint32_t patch_index : used_shape_patches) { + ExPolygon patch_area = to_expoly(patches[patch_index], projection, used_patches_depth); + //*/ + ExPolygons patch_areas = offset_ex(patch_area, extend_delta); + fill.insert(fill.end(), patch_areas.begin(), patch_areas.end()); + /*/ + // without save extension + fill.push_back(patch_area); + //*/ + } + fill = union_ex(fill); + + // not cutted area of expolygon + ExPolygons rest = diff_ex(ExPolygons{shape}, fill, ApplySafetyOffset::Yes); +#ifdef DEBUG_OUTPUT_DIR + BoundingBox shape_bb = get_extents(shape); + SVG svg(store_dir + "shape_" + std::to_string(shape_index) + ".svg", shape_bb); + svg.draw(fill, "darkgreen"); + svg.draw(rest, "green"); +#endif // DEBUG_OUTPUT_DIR + + // already filled by multiple patches + if (rest.empty()) continue; + + // find patches overlaped rest area + struct PatchShape{ + uint32_t patch_index; + ExPolygon shape; + ExPolygons intersection; + double depth_range_center_distance; // always positive + }; + using PatchShapes = std::vector; + PatchShapes patch_shapes; + + double used_patches_depth_center = (used_patches_depth[0] + used_patches_depth[1]) / 2; + + // sort used_patches for faster search + std::sort(used_shape_patches.begin(), used_shape_patches.end()); + for (uint32_t patch_index : shapes_patches[shape_index]) { + // check is patch already used + auto it = std::lower_bound(used_shape_patches.begin(), used_shape_patches.end(), patch_index); + if (it != used_shape_patches.end() && *it == patch_index) continue; + + // Heuristics to predict which patch to be used need average patch depth + Vec2d patche_depth_range(std::numeric_limits::max(), std::numeric_limits::min()); + ExPolygon patch_shape = to_expoly(patches[patch_index], projection, patche_depth_range); + double depth_center = (patche_depth_range[0] + patche_depth_range[1]) / 2; + double depth_range_center_distance = std::fabs(used_patches_depth_center - depth_center); + + ExPolygons patch_intersection = intersection_ex(ExPolygons{patch_shape}, rest); + if (patch_intersection.empty()) continue; + + patch_shapes.push_back({patch_index, patch_shape, patch_intersection, depth_range_center_distance}); + } + + // nothing to add + if (patch_shapes.empty()) continue; + // only one solution to add + if (patch_shapes.size() == 1) { + used_shape_patches.push_back(patch_shapes.front().patch_index); + continue; + } + + // Idea: Get depth range of used patches and add patches in order by distance to used depth center + std::sort(patch_shapes.begin(), patch_shapes.end(), [](const PatchShape &a, const PatchShape &b) + { return a.depth_range_center_distance < b.depth_range_center_distance; }); + +#ifdef DEBUG_OUTPUT_DIR + for (size_t i = patch_shapes.size(); i > 0; --i) { + const PatchShape &p = patch_shapes[i - 1]; + int gray_level = (i * 200) / patch_shapes.size(); + std::stringstream color; + color << "#" << std::hex << std::setfill('0') << std::setw(2) << gray_level << gray_level << gray_level; + svg.draw(p.shape, color.str()); + Point text_pos = get_extents(p.shape).center().cast(); + svg.draw_text(text_pos, std::to_string(i-1).c_str(), "orange", std::ceil(shape_bb.size().x() / 20 * 0.000001)); + //svg.draw(p.intersection, color.str()); + } +#endif // DEBUG_OUTPUT_DIR + + for (const PatchShape &patch : patch_shapes) { + // Check when exist some place to fill + ExPolygons patch_intersection = intersection_ex(patch.intersection, rest); + if (patch_intersection.empty()) continue; + + // Extend for sure + ExPolygons intersection = offset_ex(patch.intersection, extend_delta); + rest = diff_ex(rest, intersection, ApplySafetyOffset::Yes); + + used_shape_patches.push_back(patch.patch_index); + if (rest.empty()) break; + } + + // QUESTION: How to select which patch to use? How to sort them? + // Now is used back projection distance from used patches + // + // Idealy by outline depth: (need ray cast into patches) + // how to calc wanted depth - idealy by depth of outline help to overlap + // how to calc patch depth - depth in place of outline position + // Which outline to use between + + } + + std::vector result(patches.size(), {false}); + for (const std::vector &patches: used_shapes_patches) + for (uint32_t patch_index : patches) { + assert(patch_index < result.size()); + // check only onece insertation of patch + assert(!result[patch_index]); + result[patch_index] = true; + } + return result; +} + +priv::Loops priv::create_loops(const std::vector &outlines, const CutMesh& mesh) +{ + Loops loops; + Loops unclosed; + for (HI hi : outlines) { + VI vi_s = mesh.source(hi); + VI vi_t = mesh.target(hi); + Loop *loop_move = nullptr; + Loop *loop_connect = nullptr; + for (std::vector &cut : unclosed) { + if (cut.back() != vi_s) continue; + if (cut.front() == vi_t) { + // cut closing + loop_move = &cut; + } else { + loop_connect = &cut; + } + break; + } + if (loop_move != nullptr) { + // index of closed cut + size_t index = loop_move - &unclosed.front(); + // move cut to result + loops.emplace_back(std::move(*loop_move)); + // remove it from unclosed cut + unclosed.erase(unclosed.begin() + index); + } else if (loop_connect != nullptr) { + // try find tail to connect cut + Loop *loop_tail = nullptr; + for (Loop &cut : unclosed) { + if (cut.front() != vi_t) continue; + loop_tail = &cut; + break; + } + if (loop_tail != nullptr) { + // index of tail + size_t index = loop_tail - &unclosed.front(); + // move to connect vector + loop_connect->insert(loop_connect->end(), + make_move_iterator(loop_tail->begin()), + make_move_iterator(loop_tail->end())); + // remove tail from unclosed cut + unclosed.erase(unclosed.begin() + index); + } else { + loop_connect->push_back(vi_t); + } + } else { // not found + bool create_cut = true; + // try to insert to front of cut + for (Loop &cut : unclosed) { + if (cut.front() != vi_t) continue; + cut.insert(cut.begin(), vi_s); + create_cut = false; + break; + } + if (create_cut) + unclosed.emplace_back(std::vector{vi_s, vi_t}); + } + } + assert(unclosed.empty()); + return loops; +} + +Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range) +{ + assert(!patch.loops.empty()); + if (patch.loops.empty()) return {}; + + // NOTE: this method is working only when patch did not contain outward faces + Polygons polys; + polys.reserve(patch.loops.size()); + // project conture into 2d space to fillconvert outlines to + + size_t count = 0; + for (const Loop &l : patch.loops) count += l.size(); + std::vector depths; + depths.reserve(count); + + Points pts; + for (const Loop &l : patch.loops) { + pts.clear(); + pts.reserve(l.size()); + for (VI vi : l) { + const P3 &p3 = patch.mesh.point(vi); + Vec3d p = to_vec3d(p3); + double depth; + std::optional p2_opt = projection.unproject(p, &depth); + if (depth_range[0] > depth) depth_range[0] = depth; // min + if (depth_range[1] < depth) depth_range[1] = depth; // max + // Check when appear that skip is enough for poit which can't be unprojected + // - it could break contour + assert(p2_opt.has_value()); + if (!p2_opt.has_value()) continue; + + pts.push_back(p2_opt->cast()); + depths.push_back(static_cast(depth)); + } + // minimal is triangle + assert(pts.size() >= 3); + if (pts.size() < 3) continue; + + polys.emplace_back(pts); + } + + assert(!polys.empty()); + return polys; +} + +ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range) +{ + Polygons polys = unproject_loops(patch, projection, depth_range); + // should not be used when no opposit triangle are counted so should not create overlaps + ClipperLib::PolyFillType fill_type = ClipperLib::PolyFillType::pftEvenOdd; + ExPolygons expolys = Slic3r::union_ex(polys, fill_type); + if (expolys.size() == 1) + return expolys.front(); + + // It should be one expolygon + assert(false); + + if (expolys.empty()) return {}; + // find biggest + const ExPolygon *biggest = &expolys.front(); + for (size_t index = 1; index < expolys.size(); ++index) { + const ExPolygon *current = &expolys[index]; + if (biggest->contour.size() < current->contour.size()) + biggest = current; + } + return *biggest; +} + +SurfaceCut priv::patch2cut(SurfacePatch &patch) +{ + CutMesh &mesh = patch.mesh; + + std::string convert_map_name = "v:convert"; + CutMesh::Property_map convert_map = + mesh.add_property_map(convert_map_name).first; + + size_t indices_size = mesh.faces().size(); + size_t vertices_size = mesh.vertices().size(); + + SurfaceCut sc; + sc.indices.reserve(indices_size); + sc.vertices.reserve(vertices_size); + for (VI vi : mesh.vertices()) { + // vi order is is not sorted + // assert(vi.idx() == sc.vertices.size()); + // vi is not continous + // assert(vi.idx() < vertices_size); + convert_map[vi] = sc.vertices.size(); + const P3 &p = mesh.point(vi); + sc.vertices.emplace_back(p.x(), p.y(), p.z()); + } + + for (FI fi : mesh.faces()) { + HI hi = mesh.halfedge(fi); + assert(mesh.next(hi).is_valid()); + assert(mesh.next(mesh.next(hi)).is_valid()); + // Is fi triangle? + assert(mesh.next(mesh.next(mesh.next(hi))) == hi); + + // triangle indicies + Vec3i32 ti; + size_t i = 0; + for (VI vi : { mesh.source(hi), + mesh.target(hi), + mesh.target(mesh.next(hi))}) + ti[i++] = convert_map[vi]; + sc.indices.push_back(ti); + } + + sc.contours.reserve(patch.loops.size()); + for (const Loop &loop : patch.loops) { + sc.contours.push_back({}); + std::vector &contour = sc.contours.back(); + contour.reserve(loop.size()); + for (VI vi : loop) contour.push_back(convert_map[vi]); + } + + // Not neccessary, clean and free memory + mesh.remove_property_map(convert_map); + return sc; +} + +void priv::append(SurfaceCut &sc, SurfaceCut &&sc_add) +{ + if (sc.empty()) { + sc = std::move(sc_add); + return; + } + + if (!sc_add.contours.empty()) { + SurfaceCut::Index offset = static_cast( + sc.vertices.size()); + size_t require = sc.contours.size() + sc_add.contours.size(); + if (sc.contours.capacity() < require) sc.contours.reserve(require); + for (std::vector &cut : sc_add.contours) + for (SurfaceCut::Index &i : cut) i += offset; + Slic3r::append(sc.contours, std::move(sc_add.contours)); + } + its_merge(sc, std::move(sc_add)); +} + +SurfaceCut priv::merge_patches(SurfacePatches &patches, const std::vector& mask) +{ + SurfaceCut result; + for (SurfacePatch &patch : patches) { + size_t index = &patch - &patches.front(); + if (!mask[index]) continue; + append(result, patch2cut(patch)); + } + return result; +} + +#ifdef DEBUG_OUTPUT_DIR +void priv::prepare_dir(const std::string &dir){ + namespace fs = std::filesystem; + if (fs::exists(dir)) { + for (auto &path : fs::directory_iterator(dir)) fs::remove_all(path); + } else { + fs::create_directories(dir); + } +} + +namespace priv{ +int reduction_order = 0; +int filled_order = 0; +int constrained_order = 0; +int diff_patch_order = 0; + +} // namespace priv + +void priv::initialize_store(const std::string& dir) +{ + // clear previous output + prepare_dir(dir); + reduction_order = 0; + filled_order = 0; + constrained_order = 0; + diff_patch_order = 0; +} + +void priv::store(const Vec3f &vertex, + const Vec3f &normal, + const std::string &file, + float size) +{ + int flatten = 20; + size_t min_i = 0; + for (size_t i = 1; i < 3; i++) + if (normal[min_i] > normal[i]) min_i = i; + Vec3f up_ = Vec3f::Zero(); + up_[min_i] = 1.f; + Vec3f side = normal.cross(up_).normalized() * size; + Vec3f up = side.cross(normal).normalized() * size; + + indexed_triangle_set its; + its.vertices.reserve(flatten + 1); + its.indices.reserve(flatten); + + its.vertices.push_back(vertex); + its.vertices.push_back(vertex + up); + size_t max_i = static_cast(flatten); + for (size_t i = 1; i < max_i; i++) { + float angle = i * 2 * M_PI / flatten; + Vec3f v = vertex + sin(angle) * side + cos(angle) * up; + its.vertices.push_back(v); + its.indices.emplace_back(0, i, i + 1); + } + its.indices.emplace_back(0, flatten, 1); + its_write_obj(its, file.c_str()); +} + +void priv::store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string& dir, bool is_filled) +{ + std::string off_file; + if (is_filled) { + if (filled_order == 0) prepare_dir(dir); + off_file = dir + "model" + std::to_string(filled_order++) + ".off"; + }else{ + if (constrained_order == 0) prepare_dir(dir); + off_file = dir + "model" + std::to_string(constrained_order++) + ".off"; + } + + CutMesh &mesh_ = const_cast(mesh); + auto face_colors = mesh_.add_property_map("f:color").first; + for (FI fi : mesh.faces()) { + auto &color = face_colors[fi]; + switch (face_type_map[fi]) { + case FaceType::inside: color = CGAL::Color{100, 250, 100}; break; // light green + case FaceType::inside_processed: color = CGAL::Color{170, 0, 0}; break; // dark red + case FaceType::outside: color = CGAL::Color{100, 0, 100}; break; // purple + case FaceType::not_constrained: color = CGAL::Color{127, 127, 127}; break; // gray + default: color = CGAL::Color{0, 0, 255}; // blue + } + } + CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::face_color_map(face_colors)); + mesh_.remove_property_map(face_colors); +} + +void priv::store(const ExPolygons &shapes, const std::string &svg_file) { + SVG svg(svg_file); + svg.draw(shapes); +} + +void priv::store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string& dir) +{ + if (reduction_order == 0) prepare_dir(dir); + std::string off_file = dir + "model" + std::to_string(reduction_order++) + ".off"; + + CutMesh &mesh_ = const_cast(mesh); + auto vertex_colors = mesh_.add_property_map("v:color").first; + // initialize to gray color + for (VI vi: mesh.vertices()) + vertex_colors[vi] = CGAL::Color{127, 127, 127}; + + for (VI reduction_from : mesh.vertices()) { + VI reduction_to = reduction_map[reduction_from]; + if (!reduction_to.is_valid()) continue; + vertex_colors[reduction_from] = CGAL::Color{255, 0, 0}; + vertex_colors[reduction_to] = CGAL::Color{0, 0, 255}; + } + + CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::vertex_color_map(vertex_colors)); + mesh_.remove_property_map(vertex_colors); +} + +namespace priv { +indexed_triangle_set create_indexed_triangle_set(const std::vector &faces, + const CutMesh &mesh); +} // namespace priv + +indexed_triangle_set priv::create_indexed_triangle_set( + const std::vector &faces, const CutMesh &mesh) +{ + std::vector vertices; + vertices.reserve(faces.size() * 2); + + indexed_triangle_set its; + its.indices.reserve(faces.size()); + for (FI fi : faces) { + HI hi = mesh.halfedge(fi); + HI hi_end = hi; + + int ti = 0; + Vec3i32 t; + + do { + VI vi = mesh.source(hi); + auto res = std::find(vertices.begin(), vertices.end(), vi); + t[ti++] = res - vertices.begin(); + if (res == vertices.end()) vertices.push_back(vi); + hi = mesh.next(hi); + } while (hi != hi_end); + + its.indices.push_back(t); + } + + its.vertices.reserve(vertices.size()); + for (VI vi : vertices) { + const auto &p = mesh.point(vi); + its.vertices.emplace_back(p.x(), p.y(), p.z()); + } + return its; +} + +void priv::store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir) { + auto create_outline_its = + [&mesh](const std::vector &outlines) -> indexed_triangle_set { + static const float line_width = 0.1f; + indexed_triangle_set its; + its.indices.reserve(2*outlines.size()); + its.vertices.reserve(outlines.size()*4); + for (HI hi : outlines) { + //FI fi = mesh.face(hi); + VI vi_a = mesh.source(hi); + VI vi_b = mesh.target(hi); + VI vi_c = mesh.target(mesh.next(hi)); + P3 p3_a = mesh.point(vi_a); + P3 p3_b = mesh.point(vi_b); + P3 p3_c = mesh.point(vi_c); + + Vec3f a(p3_a.x(), p3_a.y(), p3_a.z()); + Vec3f b(p3_b.x(), p3_b.y(), p3_b.z()); + Vec3f c(p3_c.x(), p3_c.y(), p3_c.z()); + + Vec3f v1 = b - a; // from a to b + v1.normalize(); + Vec3f v2 = c - a; // from a to c + v2.normalize(); + Vec3f norm = v1.cross(v2); + norm.normalize(); + Vec3f perp_to_edge = norm.cross(v1); + perp_to_edge.normalize(); + Vec3f dir = -perp_to_edge * line_width; + + size_t ai = its.vertices.size(); + its.vertices.push_back(a); + size_t bi = its.vertices.size(); + its.vertices.push_back(b); + size_t ai2 = its.vertices.size(); + its.vertices.push_back(a + dir); + size_t bi2 = its.vertices.size(); + its.vertices.push_back(b + dir); + + its.indices.push_back(Vec3i32(ai, ai2, bi)); + its.indices.push_back(Vec3i32(ai2, bi2, bi)); + } + return its; + }; + + prepare_dir(dir); + for (const auto &aoi : aois) { + size_t index = &aoi - &aois.front(); + std::string file = dir + "aoi" + std::to_string(index) + ".obj"; + indexed_triangle_set its = create_indexed_triangle_set(aoi.first, mesh); + its_write_obj(its, file.c_str()); + + // exist some outline? + if (aoi.second.empty()) continue; + std::string file_outline = dir + "outline" + std::to_string(index) + ".obj"; + indexed_triangle_set outline = create_outline_its(aoi.second); + its_write_obj(outline, file_outline.c_str()); + } +} + +void priv::store(const SurfacePatches &patches, const std::string &dir) { + prepare_dir(dir); + for (const priv::SurfacePatch &patch : patches) { + size_t index = &patch - &patches.front(); + if (patch.mesh.faces().empty()) continue; + CGAL::IO::write_OFF(dir + "patch" + std::to_string(index) + ".off", patch.mesh); + } +} +// +//void priv::store(const ProjectionDistances &pds, +// const VCutAOIs &aois, +// const CutMeshes &meshes, +// const std::string &file, +// float width) +//{ +// // create rectangle for each half edge from projection distances +// indexed_triangle_set its; +// its.vertices.reserve(4 * pds.size()); +// its.indices.reserve(2 * pds.size()); +// for (const ProjectionDistance &pd : pds) { +// if (pd.aoi_index == std::numeric_limits::max()) continue; +// HI hi = aois[pd.model_index][pd.aoi_index].second[pd.hi_index]; +// const CutMesh &mesh = meshes[pd.model_index]; +// VI vi1 = mesh.source(hi); +// VI vi2 = mesh.target(hi); +// VI vi3 = mesh.target(mesh.next(hi)); +// const P3 &p1 = mesh.point(vi1); +// const P3 &p2 = mesh.point(vi2); +// const P3 &p3 = mesh.point(vi3); +// Vec3f v1(p1.x(), p1.y(), p1.z()); +// Vec3f v2(p2.x(), p2.y(), p2.z()); +// Vec3f v3(p3.x(), p3.y(), p3.z()); +// +// Vec3f v12 = v2 - v1; +// v12.normalize(); +// Vec3f v13 = v3 - v1; +// v13.normalize(); +// Vec3f n = v12.cross(v13); +// n.normalize(); +// Vec3f side = n.cross(v12); +// side.normalize(); +// side *= -width; +// +// uint32_t i = its.vertices.size(); +// its.vertices.push_back(v1); +// its.vertices.push_back(v1+side); +// its.vertices.push_back(v2); +// its.vertices.push_back(v2+side); +// +// its.indices.emplace_back(i, i + 1, i + 2); +// its.indices.emplace_back(i + 2, i + 1, i + 3); +// } +// its_write_obj(its, file.c_str()); +//} + +void priv::store(const ExPolygons &shapes, const std::vector &mask, const Connections &connections, const std::string &file_svg) +{ + auto bb = get_extents(shapes); + int width = get_extents(shapes.front()).size().x() / 70; + + SVG svg(file_svg, bb); + svg.draw(shapes); + + ExPolygonsIndices s2i(shapes); + auto get_point = [&shapes, &s2i](size_t i)->Point { + auto id = s2i.cvt(i); + const ExPolygon &s = shapes[id.expolygons_index]; + const Polygon &p = (id.polygon_index == 0) ? + s.contour : + s.holes[id.polygon_index - 1]; + return p[id.point_index]; + }; + + bool is_first = true; + for (const Connection &c : connections) { + if (is_first) { + is_first = false; + Point p = get_point(c.first); + svg.draw(p, "purple", 4 * width); + continue; + } + Point p1 = get_point(c.first); + Point p2 = get_point(c.second); + svg.draw(Line(p1, p2), "red", width); + } + + for (size_t i = 0; i < s2i.get_count(); i++) { + Point p = get_point(i); + svg.draw(p, "black", 2*width); + if (!mask[i]) + svg.draw(p, "white", width); + } + svg.Close(); +} + +namespace priv { +/// +/// Create model consist of rectangles for each contour edge +/// +/// +/// +/// +indexed_triangle_set create_contour_its(const indexed_triangle_set& its, const std::vector &contour); + +/// +/// Getter on triangle tip (third vertex of face) +/// +/// First vertex index +/// Second vertex index +/// Source model +/// Tip Vertex index +unsigned int get_triangle_tip(unsigned int vi1, + unsigned int vi2, + const indexed_triangle_set &its); +} + + +unsigned int priv::get_triangle_tip(unsigned int vi1, + unsigned int vi2, + const indexed_triangle_set &its) +{ + assert(vi1 < its.vertices.size()); + assert(vi2 < its.vertices.size()); + for (const auto &t : its.indices) { + unsigned int tvi = std::numeric_limits::max(); + for (const auto &vi : t) { + unsigned int vi_ = static_cast(vi); + if (vi_ == vi1) continue; + if (vi_ == vi2) continue; + if (tvi == std::numeric_limits::max()) { + tvi = vi_; + } else { + tvi = std::numeric_limits::max(); + break; + } + } + if (tvi != std::numeric_limits::max()) + return tvi; + } + // triangle with indices vi1 and vi2 doesnt exist + assert(false); + return std::numeric_limits::max(); +} + +indexed_triangle_set priv::create_contour_its( + const indexed_triangle_set &its, const std::vector &contour) +{ + static const float line_width = 0.1f; + indexed_triangle_set result; + result.vertices.reserve((contour.size() + 1) * 4); + result.indices.reserve((contour.size() + 1) * 2); + unsigned int prev_vi = contour.back(); + for (unsigned int vi : contour) { + const Vec3f &a = its.vertices[vi]; + const Vec3f &b = its.vertices[prev_vi]; + const Vec3f &c = its.vertices[get_triangle_tip(vi, prev_vi, its)]; + + Vec3f v1 = b - a; // from a to b + v1.normalize(); + Vec3f v2 = c - a; // from a to c + v2.normalize(); + // triangle normal + Vec3f norm = v1.cross(v2); + norm.normalize(); + // perpendiculat to edge lay on triangle + Vec3f perp_to_edge = norm.cross(v1); + perp_to_edge.normalize(); + + Vec3f dir = -perp_to_edge * line_width; + + size_t ai = result.vertices.size(); + result.vertices.push_back(a); + size_t bi = result.vertices.size(); + result.vertices.push_back(b); + size_t ai2 = result.vertices.size(); + result.vertices.push_back(a + dir); + size_t bi2 = result.vertices.size(); + result.vertices.push_back(b + dir); + + result.indices.push_back(Vec3i32(ai, bi, ai2)); + result.indices.push_back(Vec3i32(ai2, bi, bi2)); + prev_vi = vi; + } + return result; +} + +//void priv::store(const SurfaceCuts &cut, const std::string &dir) { +// prepare_dir(dir); +// for (const auto &c : cut) { +// size_t index = &c - &cut.front(); +// std::string file = dir + "cut" + std::to_string(index) + ".obj"; +// its_write_obj(c, file.c_str()); +// for (const auto& contour : c.contours) { +// size_t c_index = &contour - &c.contours.front(); +// std::string c_file = dir + "cut" + std::to_string(index) + +// "contour" + std::to_string(c_index) + ".obj"; +// indexed_triangle_set c_its = create_contour_its(c, contour); +// its_write_obj(c_its, c_file.c_str()); +// } +// } +//} + +void priv::store(const SurfaceCut &cut, const std::string &file, const std::string &contour_dir) { + prepare_dir(contour_dir); + its_write_obj(cut, file.c_str()); + for (const auto& contour : cut.contours) { + size_t c_index = &contour - &cut.contours.front(); + std::string c_file = contour_dir + std::to_string(c_index) + ".obj"; + indexed_triangle_set c_its = create_contour_its(cut, contour); + its_write_obj(c_its, c_file.c_str()); + } +} + +void priv::store(const std::vector &models, + const std::string &obj_filename) +{ + indexed_triangle_set merged_model; + for (const indexed_triangle_set &model : models) + its_merge(merged_model, model); + its_write_obj(merged_model, obj_filename.c_str()); +} + +void priv::store(const std::vector &models, + const std::string &dir) +{ + prepare_dir(dir); + if (models.empty()) return; + if (models.size() == 1) { + CGAL::IO::write_OFF(dir + "model.off", models.front()); + return; + } + size_t model_index = 0; + for (const priv::CutMesh& model : models) { + std::string filename = dir + "model" + std::to_string(model_index++) + ".off"; + CGAL::IO::write_OFF(filename, model); + } +} + +// store projection center +void priv::store(const Emboss::IProjection &projection, + const Point &point_to_project, + float projection_ratio, + const std::string &obj_filename) +{ + auto [front, back] = projection.create_front_back(point_to_project); + Vec3d diff = back - front; + Vec3d pos = front + diff * projection_ratio; + priv::store(pos.cast(), diff.normalized().cast(), + DEBUG_OUTPUT_DIR + "projection_center.obj"); // only debug +} + +#endif // DEBUG_OUTPUT_DIR + +bool Slic3r::corefine_test(const std::string &model_path, const std::string &shape_path) { + priv::CutMesh model, shape; + if (!CGAL::IO::read_OFF(model_path, model)) return false; + if (!CGAL::IO::read_OFF(shape_path, shape)) return false; + + CGAL::Polygon_mesh_processing::corefine(model, shape); + return true; +} diff --git a/src/libslic3r/CutSurface.hpp b/src/libslic3r/CutSurface.hpp new file mode 100644 index 0000000..9f4e315 --- /dev/null +++ b/src/libslic3r/CutSurface.hpp @@ -0,0 +1,74 @@ +#ifndef slic3r_CutSurface_hpp_ +#define slic3r_CutSurface_hpp_ + +#include +#include // indexed_triangle_set +#include "ExPolygon.hpp" +#include "Emboss.hpp" // IProjection + +namespace Slic3r{ + +/// +/// Represents cutted surface from object +/// Extend index triangle set by outlines +/// +struct SurfaceCut : public indexed_triangle_set +{ + // vertex indices(index to mesh vertices) + using Index = unsigned int; + using Contour = std::vector; + using Contours = std::vector; + // list of circulated open surface + Contours contours; +}; + +/// +/// Cut surface shape from models. +/// +/// Multiple shape to cut from model +/// Multi mesh to cut, need to be in same coordinate system +/// Define transformation 2d shape into 3d +/// Define ideal ratio between front and back projection to cut +/// 0 .. means use closest to front projection +/// 1 .. means use closest to back projection +/// value from <0, 1> +/// +/// Cutted surface from model +SurfaceCut cut_surface(const ExPolygons &shapes, + const std::vector &models, + const Emboss::IProjection &projection, + float projection_ratio); + +/// +/// Create model from surface cuts by projection +/// +/// Surface from model with outlines +/// Way of emboss +/// Mesh +indexed_triangle_set cut2model(const SurfaceCut &cut, + const Emboss::IProject3d &projection); + +/// +/// Separate (A)rea (o)f (I)nterest .. AoI from model +/// NOTE: Only 2d filtration, do not filtrate by Z coordinate +/// +/// Input model +/// Bounding box to project into space +/// Define tranformation of BB into space +/// Triangles lay at least partialy inside of projected Bounding box +indexed_triangle_set its_cut_AoI(const indexed_triangle_set &its, + const BoundingBox &bb, + const Emboss::IProjection &projection); + +/// +/// Separate triangles by mask +/// +/// Input model +/// Mask - same size as its::indices +/// Copy of indices by mask(with their vertices) +indexed_triangle_set its_mask(const indexed_triangle_set &its, const std::vector &mask); + +bool corefine_test(const std::string &model_path, const std::string &shape_path); + +} // namespace Slic3r +#endif // slic3r_CutSurface_hpp_ diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 65663d9..11e73a6 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -212,11 +212,13 @@ static void process_solid_part_cut( static void reset_instance_transformation(ModelObject * object, size_t src_instance_idx, - const Transform3d &cut_matrix = Transform3d::Identity(), - bool place_on_cut = false, - bool flip = false, - bool is_set_offset = false, - bool offset_pos_dir = true) + const Transform3d &cut_matrix = Transform3d::Identity(), + bool place_on_cut = false, + bool flip = false, + bool is_set_offset = false, + bool offset_pos_dir = true, + bool set_displace = false, + Vec3d local_displace = Vec3d::Zero()) { // Reset instance transformation except offset and Z-rotation @@ -231,15 +233,18 @@ static void reset_instance_transformation(ModelObject * object, obj_instance->set_transformation(inst_trafo); - if (is_set_offset && object->volumes.size() > 0) { - BoundingBoxf3 curBox; - for (size_t i = 0; i < object->volumes.size(); i++) { - curBox.merge(object->volumes[i]->mesh().bounding_box()); + if (object->volumes.size() > 0) { + if (is_set_offset) { + BoundingBoxf3 curBox; + for (size_t i = 0; i < object->volumes.size(); i++) { curBox.merge(object->volumes[i]->mesh().bounding_box()); } + float offset_x = curBox.size().x() * (offset_pos_dir ? 1.1 : 0); + Vec3d displace(offset_x, 0, 0); + displace = rotation_transform(obj_instance->get_rotation()) * displace; + obj_instance->set_offset(obj_instance->get_offset() + displace); + } else if (set_displace) { + Vec3d displace = rotation_transform(obj_instance->get_rotation()) * local_displace; + obj_instance->set_offset(obj_instance->get_offset() + displace); } - auto offset_x = curBox.size().x() * 0.7 * (offset_pos_dir ? 1 : -1); - Vec3d displace(offset_x,0,0); - displace = rotation_transform(obj_instance->get_rotation()) * displace; - obj_instance->set_offset(obj_instance->get_offset() + displace); } Vec3d rotation = Vec3d::Zero(); @@ -274,12 +279,12 @@ Cut::Cut(const ModelObject * object, if (object) m_model.add_object(*object); } -void Cut::post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &cut_object_ptrs, bool keep, bool place_on_cut, bool flip) +void Cut::post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &cut_object_ptrs, bool keep, bool place_on_cut, bool flip, bool discard_half_cut) { if (!object) return; if (keep && !object->volumes.empty()) { - reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip,set_offset_for_two_part, is_upper); + reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip, set_offset_for_two_part, discard_half_cut ? false :is_upper); cut_object_ptrs.push_back(object); } else m_model.objects.push_back(object); // will be deleted in m_model.clear_objects(); @@ -287,11 +292,15 @@ void Cut::post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &cut_ void Cut::post_process(ModelObject *upper, ModelObject *lower, ModelObjectPtrs &cut_object_ptrs) { + bool discard_half_cut = false; + if (!(upper && lower)) { + discard_half_cut = true; + } post_process(upper,true, cut_object_ptrs, m_attributes.has(ModelObjectCutAttribute::KeepUpper), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), - m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + m_attributes.has(ModelObjectCutAttribute::FlipUpper), discard_half_cut); post_process(lower, false, cut_object_ptrs, m_attributes.has(ModelObjectCutAttribute::KeepLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), - m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower), discard_half_cut); } void Cut::finalize(const ModelObjectPtrs &objects) @@ -385,9 +394,16 @@ const ModelObjectPtrs &Cut::perform_with_plane() delete_extra_modifiers(lower); if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { + auto object_box = mo->bounding_box();//in world + auto origin_box_size = object_box.size(); + Vec3d local_dowels_displace = Vec3d(0, -origin_box_size.y() * 0.7, 0); for (auto dowel : dowels) { - reset_instance_transformation(dowel, m_instance); + reset_instance_transformation(dowel, m_instance, Transform3d::Identity(), false, false,false,false,true, local_dowels_displace); + local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.5, 0.0, 0.0)); dowel->name += "-Dowel-" + dowel->volumes[0]->name; + for (auto &volume : dowel->volumes) { + volume->set_offset(Vec3d::Zero()); + } cut_object_ptrs.push_back(dowel); } } @@ -408,10 +424,20 @@ static void distribute_modifiers_from_object(ModelObject *from_obj, const int in for (ModelVolume *vol : from_obj->volumes) if (!vol->is_model_part()) { + // Don't add modifiers which are processed connectors + if (vol->cut_info.is_connector && !vol->cut_info.is_processed) + continue; + + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + const auto modifier_trafo = Transformation(from_obj->instances[instance_idx]->get_transformation().get_matrix_no_offset() * vol->get_matrix()); + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); // Don't add modifiers which are not intersecting with solid parts - if (obj1_bb.intersects(bb)) to_obj1->add_volume(*vol); - if (obj2_bb.intersects(bb)) to_obj2->add_volume(*vol); + if (obj1_bb.intersects(bb)) + to_obj1->add_volume(*vol)->set_transformation(modifier_trafo); + if (obj2_bb.intersects(bb)) + to_obj2->add_volume(*vol)->set_transformation(modifier_trafo); } } @@ -435,9 +461,11 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs &objects) const ModelVolume *mv = mo->volumes[i]; if (mv->is_model_part() && !mv->is_cut_connector()) mo->delete_volume(i); } + // Ensuring that volumes start with solid parts for proper slicing + mo->sort_volumes(true); } } -} +} const ModelObjectPtrs &Cut::perform_by_contour(std::vector parts, int dowels_count) { @@ -635,14 +663,23 @@ const ModelObjectPtrs &Cut::perform_with_groove(const Groove &groove, const Tran // add modifiers for (const ModelVolume *volume : cut_mo->volumes) - if (!volume->is_model_part()) upper->add_volume(*volume); + if (!volume->is_model_part()) { + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + const auto modifier_trafo = Transformation(cut_mo->instances[m_instance]->get_transformation().get_matrix_no_offset() * volume->get_matrix()); + upper->add_volume(*volume)->set_transformation(modifier_trafo); + } cut_object_ptrs.push_back(upper); // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks cut_object_ptrs.push_back(lower); } else { - // add modifiers if object has any + reset_instance_transformation(upper, m_instance, m_cut_matrix); + reset_instance_transformation(lower, m_instance, m_cut_matrix); + + // Add modifiers if object has any + // Note: make it after all transformations are reset for upper/lower object for (const ModelVolume *volume : cut_mo->volumes) if (!volume->is_model_part()) { distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index a27574a..37d8cfa 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -29,7 +29,7 @@ class Cut { const Transform3d m_cut_matrix; ModelObjectCutAttributes m_attributes; - void post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &objects, bool keep, bool place_on_cut, bool flip); + void post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &objects, bool keep, bool place_on_cut, bool flip, bool discard_half_cut); void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects); void finalize(const ModelObjectPtrs& objects); diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 4be2bdd..2002691 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -169,7 +169,126 @@ public: std::vector> intersecting_edges() const; bool has_intersecting_edges() const; - template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, VISITOR &visitor) const + template + void visit_intersect_line_impl(coord_t ix, coord_t iy, Point p1, coord_t ixb, coord_t iyb, Point p2, VISITOR &visitor) const + { + // Account for the end points. + if (!visitor(iy, ix) || (ix == ixb && iy == iyb)) + // Both ends fall into the same cell. + return; + // Raster the centeral part of the line. + coord_t dx = std::abs(p2(0) - p1(0)); + coord_t dy = std::abs(p2(1) - p1(1)); + if (p1(0) < p2(0)) { + int64_t ex = int64_t((ix + 1) * m_resolution - p1(0)) * int64_t(dy); + if (p1(1) < p2(1)) { + // x positive, y positive + int64_t ey = int64_t((iy + 1) * m_resolution - p1(1)) * int64_t(dx); + do { + assert(ix <= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + assert(ix <= ixb); + } else if (ex == ey) { + ex = int64_t(dy) * m_resolution; + ey = int64_t(dx) * m_resolution; + ix += 1; + iy += 1; + assert(ix <= ixb); + assert(iy <= iyb); + } else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + assert(iy <= iyb); + } + if (!visitor(iy, ix)) + return; + } while (ix != ixb || iy != iyb); + } else { + // x positive, y non positive + int64_t ey = int64_t(p1(1) - iy * m_resolution) * int64_t(dx); + do { + assert(ix <= ixb && iy >= iyb); + if (ex <= ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + assert(ix <= ixb); + } else { + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + assert(iy >= iyb); + } + if (!visitor(iy, ix)) + return; + } while (ix != ixb || iy != iyb); + } + } else { + int64_t ex = int64_t(p1(0) - ix * m_resolution) * int64_t(dy); + if (p1(1) < p2(1)) { + // x non positive, y positive + int64_t ey = int64_t((iy + 1) * m_resolution - p1(1)) * int64_t(dx); + do { + assert(ix >= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + assert(ix >= ixb); + } else { + assert(ex >= ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + assert(iy <= iyb); + } + if (!visitor(iy, ix)) + return; + } while (ix != ixb || iy != iyb); + } else { + // x non positive, y non positive + int64_t ey = int64_t(p1(1) - iy * m_resolution) * int64_t(dx); + do { + assert(ix >= ixb && iy >= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + assert(ix >= ixb); + } else if (ex == ey) { + // The lower edge of a grid cell belongs to the cell. + // Handle the case where the ray may cross the lower left corner of a cell in a general case, + // or a left or lower edge in a degenerate case (horizontal or vertical line). + if (dx > 0) { + ex = int64_t(dy) * m_resolution; + ix -= 1; + assert(ix >= ixb); + } + if (dy > 0) { + ey = int64_t(dx) * m_resolution; + iy -= 1; + assert(iy >= iyb); + } + } else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + assert(iy >= iyb); + } + if (!visitor(iy, ix)) + return; + } while (ix != ixb || iy != iyb); + } + } + } + + template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, VISITOR &visitor, bool need_consider_eps = false) const { // End points of the line segment. assert(m_bbox.contains(p1)); @@ -189,127 +308,59 @@ public: assert(iy >= 0 && size_t(iy) < m_rows); assert(ixb >= 0 && size_t(ixb) < m_cols); assert(iyb >= 0 && size_t(iyb) < m_rows); - // Account for the end points. - if (! visitor(iy, ix) || (ix == ixb && iy == iyb)) - // Both ends fall into the same cell. - return; - // Raster the centeral part of the line. - coord_t dx = std::abs(p2(0) - p1(0)); - coord_t dy = std::abs(p2(1) - p1(1)); - if (p1(0) < p2(0)) { - int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy); - if (p1(1) < p2(1)) { - // x positive, y positive - int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); - do { - assert(ix <= ixb && iy <= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix += 1; - assert(ix <= ixb); - } - else if (ex == ey) { - ex = int64_t(dy) * m_resolution; - ey = int64_t(dx) * m_resolution; - ix += 1; - iy += 1; - assert(ix <= ixb); - assert(iy <= iyb); - } - else { - assert(ex > ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy += 1; - assert(iy <= iyb); - } - if (! visitor(iy, ix)) - return; - } while (ix != ixb || iy != iyb); - } - else { - // x positive, y non positive - int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); - do { - assert(ix <= ixb && iy >= iyb); - if (ex <= ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix += 1; - assert(ix <= ixb); - } - else { - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy -= 1; - assert(iy >= iyb); - } - if (! visitor(iy, ix)) - return; - } while (ix != ixb || iy != iyb); - } + + std::vector> start_pos; + std::vector> end_pos; + start_pos.push_back(std::make_tuple(ix, iy, p1)); + end_pos.push_back(std::make_tuple(ixb, iyb, p2)); + + if (need_consider_eps) { + auto calculate_upper = [&](coord_t value, double eps) { + return coord_t((value + eps) / m_resolution); + }; + auto calculate_lower = [&](coord_t value, double eps) { + return coord_t((value - eps) / m_resolution); + }; + const double eps = scale_(10 * EPSILON); + if (coord_t ix_u = calculate_upper(p1(0), eps); + ix_u != ix) { + start_pos.push_back(std::make_tuple(ix_u, iy, Slic3r::Point(coord_t(p1(0) + eps), p1(1)))); + } + if (coord_t ix_l = calculate_lower(p1(0), eps); + ix_l != ix) { + start_pos.push_back(std::make_tuple(ix_l, iy, Slic3r::Point(coord_t(p1(0) - eps), p1(1)))); + } + if (coord_t iy_u = calculate_upper(p1(1), eps); + iy_u != iy) { + start_pos.push_back(std::make_tuple(ix, iy_u, Slic3r::Point(p1(0), coord_t(p1(1) + eps)))); + } + if (coord_t iy_l = calculate_lower(p1(1), eps); + iy_l != iy) { + start_pos.push_back(std::make_tuple(ix, iy_l, Slic3r::Point(p1(0), coord_t(p1(1) - eps)))); + } + + if (coord_t ixb_u = calculate_upper(p2(0), eps); + ixb_u != ixb) { + end_pos.push_back(std::make_tuple(ixb_u, iyb, Slic3r::Point(coord_t(p2(0) + eps), p2(1)))); + } + if (coord_t ixb_l = calculate_lower(p2(0), eps); + ixb_l != ixb) { + end_pos.push_back(std::make_tuple(ixb_l, iyb, Slic3r::Point(coord_t(p2(0) - eps), p2(1)))); + } + if (coord_t iyb_u = calculate_upper(p2(1), eps); + iyb_u != iyb) { + end_pos.push_back(std::make_tuple(ixb, iyb_u, Slic3r::Point(p2(0), coord_t(p2(1) + eps)))); + } + if (coord_t iyb_l = calculate_lower(p2(1), eps); + iyb_l != iyb) { + end_pos.push_back(std::make_tuple(ixb, iyb_l, Slic3r::Point(p2(0), coord_t(p2(1) - eps)))); + } } - else { - int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy); - if (p1(1) < p2(1)) { - // x non positive, y positive - int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); - do { - assert(ix >= ixb && iy <= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix -= 1; - assert(ix >= ixb); - } - else { - assert(ex >= ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy += 1; - assert(iy <= iyb); - } - if (! visitor(iy, ix)) - return; - } while (ix != ixb || iy != iyb); - } - else { - // x non positive, y non positive - int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); - do { - assert(ix >= ixb && iy >= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix -= 1; - assert(ix >= ixb); - } - else if (ex == ey) { - // The lower edge of a grid cell belongs to the cell. - // Handle the case where the ray may cross the lower left corner of a cell in a general case, - // or a left or lower edge in a degenerate case (horizontal or vertical line). - if (dx > 0) { - ex = int64_t(dy) * m_resolution; - ix -= 1; - assert(ix >= ixb); - } - if (dy > 0) { - ey = int64_t(dx) * m_resolution; - iy -= 1; - assert(iy >= iyb); - } - } - else { - assert(ex > ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy -= 1; - assert(iy >= iyb); - } - if (! visitor(iy, ix)) - return; - } while (ix != ixb || iy != iyb); + + for (size_t start_idx = 0; start_idx < start_pos.size(); ++start_idx) { + for (size_t end_idx = 0; end_idx < end_pos.size(); ++end_idx) { + visit_intersect_line_impl(std::get<0>(start_pos[start_idx]), std::get<1>(start_pos[start_idx]), std::get<2>(start_pos[start_idx]), + std::get<0>(end_pos[end_idx]), std::get<1>(end_pos[end_idx]), std::get<2>(end_pos[end_idx]), visitor); } } } diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp new file mode 100644 index 0000000..333db06 --- /dev/null +++ b/src/libslic3r/Emboss.cpp @@ -0,0 +1,2181 @@ +#include +#include "Emboss.hpp" +#include +#include +#include +#include +#include +#include // union_ex + for boldness(polygon extend(offset)) +#include "IntersectionPoints.hpp" + +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "imgui/imstb_truetype.h" // stbtt_fontinfo +#include "Utils.hpp" // ScopeGuard + +#include // CGAL project +#include "libslic3r.h" + +// to heal shape +#include "ExPolygonsIndex.hpp" +#include "libslic3r/AABBTreeLines.hpp" // search structure for found close points +#include "libslic3r/Line.hpp" +#include "libslic3r/BoundingBox.hpp" + +// Experimentaly suggested ration of font ascent by multiple fonts +// to get approx center of normal text line +const double ASCENT_CENTER = 1/3.; // 0.5 is above small letter + +// every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value +// stored in fonts (to be able represents curve by sequence of lines) +static constexpr double SHAPE_SCALE = 0.001; // SCALING_FACTOR promile is fine enough +static unsigned MAX_HEAL_ITERATION_OF_TEXT = 10; + +using namespace Slic3r; +using namespace Emboss; +using fontinfo_opt = std::optional; + +// NOTE: approach to heal shape by Clipper::Closing is not working + +// functionality to remove all spikes from shape +// Potentionaly useable for eliminate spike in layer +//#define REMOVE_SPIKES + +// function to remove useless islands and holes +// #define REMOVE_SMALL_ISLANDS +#ifdef REMOVE_SMALL_ISLANDS +namespace { void remove_small_islands(ExPolygons &shape, double minimal_area);} +#endif //REMOVE_SMALL_ISLANDS + +//#define VISUALIZE_HEAL +#ifdef VISUALIZE_HEAL +namespace { +// for debug purpose only +// NOTE: check scale when store svg !! +#include "libslic3r/SVG.hpp" // for visualize_heal +static std::string visualize_heal_svg_filepath = "C:/data/temp/heal.svg"; +void visualize_heal(const std::string &svg_filepath, const ExPolygons &expolygons) +{ + Points pts = to_points(expolygons); + BoundingBox bb(pts); + // double svg_scale = SHAPE_SCALE / unscale(1.); + // bb.scale(svg_scale); + SVG svg(svg_filepath, bb); + svg.draw(expolygons); + + Points duplicits = collect_duplicates(pts); + int black_size = std::max(bb.size().x(), bb.size().y()) / 20; + svg.draw(duplicits, "black", black_size); + + Slic3r::IntersectionsLines intersections_f = get_intersections(expolygons); + Points intersections = get_unique_intersections(intersections_f); + svg.draw(intersections, "red", black_size * 1.2); +} +} // namespace +#endif // VISUALIZE_HEAL + +// do not expose out of this file stbtt_ data types +namespace{ +using Polygon = Slic3r::Polygon; +bool is_valid(const FontFile &font, unsigned int index); +fontinfo_opt load_font_info(const unsigned char *data, unsigned int index = 0); +std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness); + +// take glyph from cache +const Glyph* get_glyph(int unicode, const FontFile &font, const FontProp &font_prop, + Glyphs &cache, fontinfo_opt &font_info_opt); + +// scale and convert float to int coordinate +Point to_point(const stbtt__point &point); + +// bad is contour smaller than 3 points +void remove_bad(Polygons &polygons); +void remove_bad(ExPolygons &expolygons); + +// Try to remove self intersection by subtracting rect 2x2 px +ExPolygon create_bounding_rect(const ExPolygons &shape); + +// Heal duplicates points and self intersections +bool heal_dupl_inter(ExPolygons &shape, unsigned max_iteration); + +const Points pts_2x2({Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)}); +const Points pts_3x3({Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)}); + +struct SpikeDesc +{ + // cosinus of max spike angle + double cos_angle; // speed up to skip acos + + // Half of Wanted bevel size + double half_bevel; + + /// + /// Calculate spike description + /// + /// Size of spike width after cut of the tip, has to be grater than 2.5 + /// When spike has same or more pixels with width less than 1 pixel + SpikeDesc(double bevel_size, double pixel_spike_length = 6): + // create min angle given by spike_length + // Use it as minimal height of 1 pixel base spike + cos_angle(std::fabs(std::cos( + /*angle*/ 2. * std::atan2(pixel_spike_length, .5) + ))), + + // When remove spike this angle is set. + // Value must be grater than min_angle + half_bevel(bevel_size / 2) + {} +}; + +// return TRUE when remove point. It could create polygon with 2 points. +bool remove_when_spike(Polygon &polygon, size_t index, const SpikeDesc &spike_desc); +void remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &duplicates); + +#ifdef REMOVE_SPIKES +// Remove long sharp corners aka spikes +// by adding points to bevel tip of spikes - Not printable parts +// Try to not modify long sides of spike and add points on it's side +void remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc); +void remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc); +void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc); +#endif + +// spike ... very sharp corner - when not removed cause iteration of heal process +// index ... index of duplicit point in polygon +bool remove_when_spike(Slic3r::Polygon &polygon, size_t index, const SpikeDesc &spike_desc) { + + std::optional add; + bool do_erase = false; + Points &pts = polygon.points; + { + size_t pts_size = pts.size(); + if (pts_size < 3) + return false; + + const Point &a = (index == 0) ? pts.back() : pts[index - 1]; + const Point &b = pts[index]; + const Point &c = (index == (pts_size - 1)) ? pts.front() : pts[index + 1]; + + // calc sides + Vec2d ba = (a - b).cast(); + Vec2d bc = (c - b).cast(); + + double dot_product = ba.dot(bc); + + // sqrt together after multiplication save one sqrt + double ba_size_sq = ba.squaredNorm(); + double bc_size_sq = bc.squaredNorm(); + double norm = sqrt(ba_size_sq * bc_size_sq); + double cos_angle = dot_product / norm; + + // small angle are around 1 --> cos(0) = 1 + if (cos_angle < spike_desc.cos_angle) + return false; // not a spike + + // has to be in range <-1, 1> + // Due to preccission of floating point number could be sligtly out of range + if (cos_angle > 1.) + cos_angle = 1.; + // if (cos_angle < -1.) + // cos_angle = -1.; + + // Current Spike angle + double angle = acos(cos_angle); + double wanted_size = spike_desc.half_bevel / cos(angle / 2.); + double wanted_size_sq = wanted_size * wanted_size; + + bool is_ba_short = ba_size_sq < wanted_size_sq; + bool is_bc_short = bc_size_sq < wanted_size_sq; + + auto a_side = [&b, &ba, &ba_size_sq, &wanted_size]() -> Point { + Vec2d ba_norm = ba / sqrt(ba_size_sq); + return b + (wanted_size * ba_norm).cast(); + }; + auto c_side = [&b, &bc, &bc_size_sq, &wanted_size]() -> Point { + Vec2d bc_norm = bc / sqrt(bc_size_sq); + return b + (wanted_size * bc_norm).cast(); + }; + + if (is_ba_short && is_bc_short) { + // remove short spike + do_erase = true; + } else if (is_ba_short) { + // move point B on C-side + pts[index] = c_side(); + } else if (is_bc_short) { + // move point B on A-side + pts[index] = a_side(); + } else { + // move point B on C-side and add point on A-side(left - before) + pts[index] = c_side(); + add = a_side(); + if (*add == pts[index]) { + // should be very rare, when SpikeDesc has small base + // will be fixed by remove B point + add.reset(); + do_erase = true; + } + } + } + if (do_erase) { + pts.erase(pts.begin() + index); + return true; + } + if (add.has_value()) + pts.insert(pts.begin() + index, *add); + return false; +} + +void remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &duplicates) { + if (duplicates.empty()) + return; + auto check = [](Slic3r::Polygon &polygon, const Point &d) -> bool { + double spike_bevel = 1 / SHAPE_SCALE; + double spike_length = 5.; + const static SpikeDesc sd(spike_bevel, spike_length); + Points& pts = polygon.points; + bool exist_remove = false; + for (size_t i = 0; i < pts.size(); i++) { + if (pts[i] != d) + continue; + exist_remove |= remove_when_spike(polygon, i, sd); + } + return exist_remove && pts.size() < 3; + }; + + bool exist_remove = false; + for (ExPolygon &expolygon : expolygons) { + BoundingBox bb(to_points(expolygon.contour)); + for (const Point &d : duplicates) { + if (!bb.contains(d)) + continue; + exist_remove |= check(expolygon.contour, d); + for (Polygon &hole : expolygon.holes) + exist_remove |= check(hole, d); + } + } + + if (exist_remove) + remove_bad(expolygons); +} + +bool is_valid(const FontFile &font, unsigned int index) { + if (font.data == nullptr) return false; + if (font.data->empty()) return false; + if (index >= font.infos.size()) return false; + return true; +} + +fontinfo_opt load_font_info( + const unsigned char *data, unsigned int index) +{ + int font_offset = stbtt_GetFontOffsetForIndex(data, index); + if (font_offset < 0) { + assert(false); + // "Font index(" << index << ") doesn't exist."; + return {}; + } + stbtt_fontinfo font_info; + if (stbtt_InitFont(&font_info, data, font_offset) == 0) { + // Can't initialize font. + assert(false); + return {}; + } + return font_info; +} + +void remove_bad(Polygons &polygons) { + polygons.erase( + std::remove_if(polygons.begin(), polygons.end(), + [](const Polygon &p) { return p.size() < 3; }), + polygons.end()); +} + +void remove_bad(ExPolygons &expolygons) { + expolygons.erase( + std::remove_if(expolygons.begin(), expolygons.end(), + [](const ExPolygon &p) { return p.contour.size() < 3; }), + expolygons.end()); + + for (ExPolygon &expolygon : expolygons) + remove_bad(expolygon.holes); +} +} // end namespace + +bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double distance) +{ + if (expolygons.empty()) return false; + if (distance < 0.) return false; + + // ExPolygons can't contain same neigbours + remove_same_neighbor(expolygons); + + // IMPROVE: use int(insted of double) lines and tree + const ExPolygonsIndices ids(expolygons); + const std::vector lines = Slic3r::to_linesf(expolygons, ids.get_count()); + AABBTreeIndirect::Tree<2, double> tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + using Div = std::pair; + std::vector
divs; + size_t point_index = 0; + auto check_points = [&divs, &point_index, &lines, &tree, &distance, &ids, &expolygons](const Points &pts) { + for (const Point &p : pts) { + Vec2d p_d = p.cast(); + std::vector close_lines = AABBTreeLines::all_lines_in_radius(lines, tree, p_d, distance); + for (size_t index : close_lines) { + // skip point neighbour lines indices + if (index == point_index) continue; + if (&p != &pts.front()) { + if (index == point_index - 1) continue; + } else if (index == (pts.size()-1)) continue; + + // do not doubled side point of segment + const ExPolygonsIndex id = ids.cvt(index); + const ExPolygon &expoly = expolygons[id.expolygons_index]; + const Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + const Points &poly_pts = poly.points; + const Point &line_a = poly_pts[id.point_index]; + const Point &line_b = (!ids.is_last_point(id)) ? poly_pts[id.point_index + 1] : poly_pts.front(); + assert(line_a == lines[index].a.cast()); + assert(line_b == lines[index].b.cast()); + if (p == line_a || p == line_b) continue; + + divs.emplace_back(p, index); + } + ++point_index; + } + }; + for (const ExPolygon &expoly : expolygons) { + check_points(expoly.contour.points); + for (const Polygon &hole : expoly.holes) + check_points(hole.points); + } + + // check if exist division + if (divs.empty()) return false; + + // sort from biggest index to zero + // to be able add points and not interupt indices + std::sort(divs.begin(), divs.end(), + [](const Div &d1, const Div &d2) { return d1.second > d2.second; }); + + auto it = divs.begin(); + // divide close line + while (it != divs.end()) { + // colect division of a line segmen + size_t index = it->second; + auto it2 = it+1; + while (it2 != divs.end() && it2->second == index) ++it2; + + ExPolygonsIndex id = ids.cvt(index); + ExPolygon &expoly = expolygons[id.expolygons_index]; + Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + Points &pts = poly.points; + size_t count = it2 - it; + + // add points into polygon to divide in place of near point + if (count == 1) { + pts.insert(pts.begin() + id.point_index + 1, it->first); + ++it; + } else { + // collect points to add into polygon + Points points; + points.reserve(count); + for (; it < it2; ++it) + points.push_back(it->first); + + // need sort by line direction + const Linef &line = lines[index]; + Vec2d dir = line.b - line.a; + // select mayorit direction + int axis = (abs(dir.x()) > abs(dir.y())) ? 0 : 1; + using Fnc = std::function; + Fnc fnc = (dir[axis] < 0) ? Fnc([axis](const Point &p1, const Point &p2) { return p1[axis] > p2[axis]; }) : + Fnc([axis](const Point &p1, const Point &p2) { return p1[axis] < p2[axis]; }) ; + std::sort(points.begin(), points.end(), fnc); + + // use only unique points + points.erase(std::unique(points.begin(), points.end()), points.end()); + + // divide line by adding points into polygon + pts.insert(pts.begin() + id.point_index + 1, + points.begin(), points.end()); + } + assert(it == it2); + } + return true; +} + +HealedExPolygons Emboss::heal_polygons(const Polygons &shape, bool is_non_zero, unsigned int max_iteration) +{ + const double clean_distance = 1.415; // little grater than sqrt(2) + ClipperLib::PolyFillType fill_type = is_non_zero ? + ClipperLib::pftNonZero : ClipperLib::pftEvenOdd; + + // When edit this code check that font 'ALIENATE.TTF' and glyph 'i' still work + // fix of self intersections + // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygon.htm + ClipperLib::Paths paths = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(shape), fill_type); + ClipperLib::CleanPolygons(paths, clean_distance); + Polygons polygons = to_polygons(paths); + polygons.erase(std::remove_if(polygons.begin(), polygons.end(), + [](const Polygon &p) { return p.size() < 3; }), polygons.end()); + + if (polygons.empty()) + return {{}, false}; + + // Do not remove all duplicates but do it better way + // Overlap all duplicit points by rectangle 3x3 + Points duplicits = collect_duplicates(to_points(polygons)); + if (!duplicits.empty()) { + polygons.reserve(polygons.size() + duplicits.size()); + for (const Point &p : duplicits) { + Polygon rect_3x3(pts_3x3); + rect_3x3.translate(p); + polygons.push_back(rect_3x3); + } + } + ExPolygons res = Slic3r::union_ex(polygons, fill_type); + bool is_healed = heal_expolygons(res, max_iteration); + return {res, is_healed}; +} + + +bool Emboss::heal_expolygons(ExPolygons &shape, unsigned max_iteration) +{ + return ::heal_dupl_inter(shape, max_iteration); +} + +namespace { + +Points get_unique_intersections(const Slic3r::IntersectionsLines &intersections) +{ + Points result; + if (intersections.empty()) + return result; + + // convert intersections into Points + result.reserve(intersections.size()); + std::transform(intersections.begin(), intersections.end(), std::back_inserter(result), + [](const Slic3r::IntersectionLines &i) { return Point( + std::floor(i.intersection.x()), + std::floor(i.intersection.y())); + }); + // intersections should be unique poits + std::sort(result.begin(), result.end()); + auto it = std::unique(result.begin(), result.end()); + result.erase(it, result.end()); + return result; +} + +Polygons get_holes_with_points(const Polygons &holes, const Points &points) +{ + Polygons result; + for (const Slic3r::Polygon &hole : holes) + for (const Point &p : points) + for (const Point &h : hole) + if (p == h) { + result.push_back(hole); + break; + } + return result; +} + +/// +/// Fill holes which create duplicits or intersections +/// When healing hole creates trouble in shape again try to heal by an union instead of diff_ex +/// +/// Holes which was substracted from shape previous +/// Current duplicates in shape +/// Current intersections in shape +/// Partialy healed shape[could be modified] +/// True when modify shape otherwise False +bool fill_trouble_holes(const Polygons &holes, const Points &duplicates, const Points &intersections, ExPolygons &shape) +{ + if (holes.empty()) + return false; + if (duplicates.empty() && intersections.empty()) + return false; + + Polygons fill = get_holes_with_points(holes, duplicates); + append(fill, get_holes_with_points(holes, intersections)); + if (fill.empty()) + return false; + + shape = union_ex(shape, fill); + return true; +} + +// extend functionality from Points.cpp --> collect_duplicates +// with address of duplicated points +struct Duplicate { + Point point; + std::vector indices; +}; +using Duplicates = std::vector; +Duplicates collect_duplicit_indices(const ExPolygons &expoly) +{ + Points pts = to_points(expoly); + + // initialize original index locations + std::vector idx(pts.size()); + std::iota(idx.begin(), idx.end(), 0); + std::sort(idx.begin(), idx.end(), + [&pts](uint32_t i1, uint32_t i2) { return pts[i1] < pts[i2]; }); + + Duplicates result; + const Point *prev = &pts[idx.front()]; + for (size_t i = 1; i < idx.size(); ++i) { + uint32_t index = idx[i]; + const Point *act = &pts[index]; + if (*prev == *act) { + // duplicit point + if (!result.empty() && result.back().point == *act) { + // more than 2 points with same coordinate + result.back().indices.push_back(index); + } else { + uint32_t prev_index = idx[i-1]; + result.push_back({*act, {prev_index, index}}); + } + continue; + } + prev = act; + } + return result; +} + +Points get_points(const Duplicates& duplicate_indices) +{ + Points result; + if (duplicate_indices.empty()) + return result; + + // convert intersections into Points + result.reserve(duplicate_indices.size()); + std::transform(duplicate_indices.begin(), duplicate_indices.end(), std::back_inserter(result), + [](const Duplicate &d) { return d.point; }); + return result; +} + +bool heal_dupl_inter(ExPolygons &shape, unsigned max_iteration) +{ + if (shape.empty()) return true; + remove_same_neighbor(shape); + + // create loop permanent memory + Polygons holes; + while (--max_iteration) { + Duplicates duplicate_indices = collect_duplicit_indices(shape); + //Points duplicates = collect_duplicates(to_points(shape)); + IntersectionsLines intersections = get_intersections(shape); + + // Check whether shape is already healed + if (intersections.empty() && duplicate_indices.empty()) + return true; + + Points duplicate_points = get_points(duplicate_indices); + Points intersection_points = get_unique_intersections(intersections); + + if (fill_trouble_holes(holes, duplicate_points, intersection_points, shape)) { + holes.clear(); + continue; + } + + holes.clear(); + holes.reserve(intersections.size() + duplicate_points.size()); + + remove_spikes_in_duplicates(shape, duplicate_points); + + // Fix self intersection in result by subtracting hole 2x2 + for (const Point &p : intersection_points) { + Polygon hole(pts_2x2); + hole.translate(p); + holes.push_back(hole); + } + + // Fix duplicit points by hole 3x3 around duplicit point + for (const Point &p : duplicate_points) { + Polygon hole(pts_3x3); + hole.translate(p); + holes.push_back(hole); + } + + shape = Slic3r::diff_ex(shape, holes, ApplySafetyOffset::No); + // ApplySafetyOffset::Yes is incompatible with function fill_trouble_holes + } + + // Create partialy healed output + Duplicates duplicates = collect_duplicit_indices(shape); + IntersectionsLines intersections = get_intersections(shape); + if (duplicates.empty() && intersections.empty()){ + // healed in the last loop + return true; + } + + #ifdef VISUALIZE_HEAL + visualize_heal(visualize_heal_svg_filepath, shape); + #endif // VISUALIZE_HEAL + + assert(false); // Can not heal this shape + // investigate how to heal better way + + ExPolygonsIndices ei(shape); + std::vector is_healed(shape.size(), {true}); + for (const Duplicate &duplicate : duplicates){ + for (uint32_t i : duplicate.indices) + is_healed[ei.cvt(i).expolygons_index] = false; + } + for (const IntersectionLines &intersection : intersections) { + is_healed[ei.cvt(intersection.line_index1).expolygons_index] = false; + is_healed[ei.cvt(intersection.line_index2).expolygons_index] = false; + } + + for (size_t shape_index = 0; shape_index < shape.size(); shape_index++) { + if (!is_healed[shape_index]) { + // exchange non healed expoly with bb rect + ExPolygon &expoly = shape[shape_index]; + expoly = create_bounding_rect({expoly}); + } + } + return false; +} + +ExPolygon create_bounding_rect(const ExPolygons &shape) { + BoundingBox bb = get_extents(shape); + Point size = bb.size(); + if (size.x() < 10) + bb.max.x() += 10; + if (size.y() < 10) + bb.max.y() += 10; + + Polygon rect({// CCW + bb.min, + {bb.max.x(), bb.min.y()}, + bb.max, + {bb.min.x(), bb.max.y()}}); + + Point offset = bb.size() * 0.1; + Polygon hole({// CW + bb.min + offset, + {bb.min.x() + offset.x(), bb.max.y() - offset.y()}, + bb.max - offset, + {bb.max.x() - offset.x(), bb.min.y() + offset.y()}}); + + return ExPolygon(rect, hole); +} + +#ifdef REMOVE_SMALL_ISLANDS +void remove_small_islands(ExPolygons &expolygons, double minimal_area) { + if (expolygons.empty()) + return; + + // remove small expolygons contours + auto expoly_it = std::remove_if(expolygons.begin(), expolygons.end(), + [&minimal_area](const ExPolygon &p) { return p.contour.area() < minimal_area; }); + expolygons.erase(expoly_it, expolygons.end()); + + // remove small holes in expolygons + for (ExPolygon &expoly : expolygons) { + Polygons& holes = expoly.holes; + auto it = std::remove_if(holes.begin(), holes.end(), + [&minimal_area](const Polygon &p) { return -p.area() < minimal_area; }); + holes.erase(it, holes.end()); + } +} +#endif // REMOVE_SMALL_ISLANDS + +std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness) +{ + int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); + if (glyph_index == 0) { + //wchar_t wchar = static_cast(unicode_letter); + //<< "Character unicode letter (" + //<< "decimal value = " << std::dec << unicode_letter << ", " + //<< "hexadecimal value = U+" << std::hex << unicode_letter << std::dec << ", " + //<< "wchar value = " << wchar + //<< ") is NOT defined inside of the font. \n"; + return {}; + } + + Glyph glyph; + stbtt_GetGlyphHMetrics(&font_info, glyph_index, &glyph.advance_width, &glyph.left_side_bearing); + + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(&font_info, glyph_index, &vertices); + if (num_verts <= 0) return glyph; // no shape + ScopeGuard sg1([&vertices]() { free(vertices); }); + + int *contour_lengths = NULL; + int num_countour_int = 0; + stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts, + flatness, &contour_lengths, &num_countour_int, font_info.userdata); + if (!points) return glyph; // no valid flattening + ScopeGuard sg2([&contour_lengths, &points]() { + free(contour_lengths); + free(points); + }); + + size_t num_contour = static_cast(num_countour_int); + Polygons glyph_polygons; + glyph_polygons.reserve(num_contour); + size_t pi = 0; // point index + for (size_t ci = 0; ci < num_contour; ++ci) { + int length = contour_lengths[ci]; + // check minimal length for triangle + if (length < 4) { + // weird font + pi+=length; + continue; + } + // last point is first point + --length; + Points pts; + pts.reserve(length); + for (int i = 0; i < length; ++i) + pts.emplace_back(to_point(points[pi++])); + + // last point is first point --> closed contour + assert(pts.front() == to_point(points[pi])); + ++pi; + + // change outer cw to ccw and inner ccw to cw order + std::reverse(pts.begin(), pts.end()); + glyph_polygons.emplace_back(pts); + } + if (!glyph_polygons.empty()) { + unsigned max_iteration = 10; + // TrueTypeFonts use non zero winding number + // https://docs.microsoft.com/en-us/typography/opentype/spec/ttch01 + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html + bool is_non_zero = true; + glyph.shape = Emboss::heal_polygons(glyph_polygons, is_non_zero, max_iteration); + } + return glyph; +} + +const Glyph* get_glyph( + int unicode, + const FontFile & font, + const FontProp & font_prop, + Glyphs & cache, + fontinfo_opt &font_info_opt) +{ + // TODO: Use resolution by printer configuration, or add it into FontProp + const float RESOLUTION = 0.0125f; // [in mm] + auto glyph_item = cache.find(unicode); + if (glyph_item != cache.end()) return &glyph_item->second; + + unsigned int font_index = font_prop.collection_number.value_or(0); + if (!is_valid(font, font_index)) return nullptr; + + if (!font_info_opt.has_value()) { + + font_info_opt = load_font_info(font.data->data(), font_index); + // can load font info? + if (!font_info_opt.has_value()) return nullptr; + } + + float flatness = font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm; + + // Fix for very small flatness because it create huge amount of points from curve + if (flatness < RESOLUTION) flatness = RESOLUTION; + + std::optional glyph_opt = get_glyph(*font_info_opt, unicode, flatness); + + // IMPROVE: multiple loadig glyph without data + // has definition inside of font? + if (!glyph_opt.has_value()) return nullptr; + + Glyph &glyph = *glyph_opt; + if (font_prop.char_gap.has_value()) + glyph.advance_width += *font_prop.char_gap; + + // scale glyph size + glyph.advance_width = static_cast(glyph.advance_width / SHAPE_SCALE); + glyph.left_side_bearing = static_cast(glyph.left_side_bearing / SHAPE_SCALE); + + if (!glyph.shape.empty()) { + if (font_prop.boldness.has_value()) { + float delta = static_cast(*font_prop.boldness / SHAPE_SCALE / font_prop.size_in_mm); + glyph.shape = Slic3r::union_ex(offset_ex(glyph.shape, delta)); + } + if (font_prop.skew.has_value()) { + double ratio = *font_prop.skew; + auto skew = [&ratio](Polygon &polygon) { + for (Slic3r::Point &p : polygon.points) + p.x() += static_cast(std::round(p.y() * ratio)); + }; + for (ExPolygon &expolygon : glyph.shape) { + skew(expolygon.contour); + for (Polygon &hole : expolygon.holes) skew(hole); + } + } + } + auto [it, success] = cache.try_emplace(unicode, std::move(glyph)); + assert(success); + return &it->second; +} + +Point to_point(const stbtt__point &point) { + return Point(static_cast(std::round(point.x / SHAPE_SCALE)), + static_cast(std::round(point.y / SHAPE_SCALE))); +} + +} // namespace + +#ifdef _WIN32 +#include +#include +#include +#include + +namespace { +EmbossStyle create_style(const std::wstring& name, const std::wstring& path) { + return { boost::nowide::narrow(name.c_str()), + boost::nowide::narrow(path.c_str()), + EmbossStyle::Type::file_path, FontProp() }; +} +} // namespace + +// Get system font file path +std::optional Emboss::get_font_path(const std::wstring &font_face_name) +{ +// static const LPWSTR fontRegistryPath = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + static const LPCWSTR fontRegistryPath = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + HKEY hKey; + LONG result; + + // Open Windows font registry key + result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, fontRegistryPath, 0, KEY_READ, &hKey); + if (result != ERROR_SUCCESS) return {}; + + DWORD maxValueNameSize, maxValueDataSize; + result = RegQueryInfoKey(hKey, 0, 0, 0, 0, 0, 0, 0, &maxValueNameSize, &maxValueDataSize, 0, 0); + if (result != ERROR_SUCCESS) return {}; + + DWORD valueIndex = 0; + LPWSTR valueName = new WCHAR[maxValueNameSize]; + LPBYTE valueData = new BYTE[maxValueDataSize]; + DWORD valueNameSize, valueDataSize, valueType; + std::wstring wsFontFile; + + // Look for a matching font name + do { + wsFontFile.clear(); + valueDataSize = maxValueDataSize; + valueNameSize = maxValueNameSize; + + result = RegEnumValue(hKey, valueIndex, valueName, &valueNameSize, 0, &valueType, valueData, &valueDataSize); + + valueIndex++; + if (result != ERROR_SUCCESS || valueType != REG_SZ) { + continue; + } + + std::wstring wsValueName(valueName, valueNameSize); + + // Found a match + if (_wcsnicmp(font_face_name.c_str(), wsValueName.c_str(), font_face_name.length()) == 0) { + + wsFontFile.assign((LPWSTR)valueData, valueDataSize); + break; + } + }while (result != ERROR_NO_MORE_ITEMS); + + delete[] valueName; + delete[] valueData; + + RegCloseKey(hKey); + + if (wsFontFile.empty()) return {}; + + // Build full font file path + WCHAR winDir[MAX_PATH]; + GetWindowsDirectory(winDir, MAX_PATH); + + std::wstringstream ss; + ss << winDir << "\\Fonts\\" << wsFontFile; + wsFontFile = ss.str(); + + return wsFontFile; +} + +EmbossStyles Emboss::get_font_list() +{ + //EmbossStyles list1 = get_font_list_by_enumeration(); + //EmbossStyles list2 = get_font_list_by_register(); + //EmbossStyles list3 = get_font_list_by_folder(); + return get_font_list_by_register(); +} + +EmbossStyles Emboss::get_font_list_by_register() { +// static const LPWSTR fontRegistryPath = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + static const LPCWSTR fontRegistryPath = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + HKEY hKey; + LONG result; + + // Open Windows font registry key + result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, fontRegistryPath, 0, KEY_READ, &hKey); + if (result != ERROR_SUCCESS) { + assert(false); + //std::wcerr << L"Can not Open register key (" << fontRegistryPath << ")" + // << L", function 'RegOpenKeyEx' return code: " << result << std::endl; + return {}; + } + + DWORD maxValueNameSize, maxValueDataSize; + result = RegQueryInfoKey(hKey, 0, 0, 0, 0, 0, 0, 0, &maxValueNameSize, + &maxValueDataSize, 0, 0); + if (result != ERROR_SUCCESS) { + assert(false); + // Can not earn query key, function 'RegQueryInfoKey' return code: result + return {}; + } + + // Build full font file path + WCHAR winDir[MAX_PATH]; + GetWindowsDirectory(winDir, MAX_PATH); + std::wstring font_path = std::wstring(winDir) + L"\\Fonts\\"; + + EmbossStyles font_list; + DWORD valueIndex = 0; + // Look for a matching font name + LPWSTR font_name = new WCHAR[maxValueNameSize]; + LPBYTE fileTTF_name = new BYTE[maxValueDataSize]; + DWORD font_name_size, fileTTF_name_size, valueType; + do { + fileTTF_name_size = maxValueDataSize; + font_name_size = maxValueNameSize; + + result = RegEnumValue(hKey, valueIndex, font_name, &font_name_size, 0, + &valueType, fileTTF_name, &fileTTF_name_size); + valueIndex++; + if (result != ERROR_SUCCESS || valueType != REG_SZ) continue; + std::wstring font_name_w(font_name, font_name_size); + std::wstring file_name_w((LPWSTR) fileTTF_name, fileTTF_name_size); + std::wstring path_w = font_path + file_name_w; + + // filtrate .fon from lists + size_t pos = font_name_w.rfind(L" (TrueType)"); + if (pos >= font_name_w.size()) continue; + // remove TrueType text from name + font_name_w = std::wstring(font_name_w, 0, pos); + font_list.emplace_back(create_style(font_name_w, path_w)); + } while (result != ERROR_NO_MORE_ITEMS); + delete[] font_name; + delete[] fileTTF_name; + + RegCloseKey(hKey); + return font_list; +} + +// TODO: Fix global function +bool CALLBACK EnumFamCallBack(LPLOGFONT lplf, + LPNEWTEXTMETRIC lpntm, + DWORD FontType, + LPVOID aFontList) +{ + std::vector *fontList = + (std::vector *) (aFontList); + if (FontType & TRUETYPE_FONTTYPE) { + std::wstring name = lplf->lfFaceName; + fontList->push_back(name); + } + return true; + // UNREFERENCED_PARAMETER(lplf); + UNREFERENCED_PARAMETER(lpntm); +} + +EmbossStyles Emboss::get_font_list_by_enumeration() { + + HDC hDC = GetDC(NULL); + std::vector font_names; + EnumFontFamilies(hDC, (LPCTSTR) NULL, (FONTENUMPROC) EnumFamCallBack, + (LPARAM) &font_names); + + EmbossStyles font_list; + for (const std::wstring &font_name : font_names) { + font_list.emplace_back(create_style(font_name, L"")); + } + return font_list; +} + +EmbossStyles Emboss::get_font_list_by_folder() { + EmbossStyles result; + WCHAR winDir[MAX_PATH]; + UINT winDir_size = GetWindowsDirectory(winDir, MAX_PATH); + std::wstring search_dir = std::wstring(winDir, winDir_size) + L"\\Fonts\\"; + WIN32_FIND_DATA fd; + HANDLE hFind; + // By https://en.wikipedia.org/wiki/TrueType has also suffix .tte + std::vector suffixes = {L"*.ttf", L"*.ttc", L"*.tte"}; + for (const std::wstring &suffix : suffixes) { + hFind = ::FindFirstFile((search_dir + suffix).c_str(), &fd); + if (hFind == INVALID_HANDLE_VALUE) continue; + do { + // skip folder . and .. + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; + std::wstring file_name(fd.cFileName); + // TODO: find font name instead of filename + result.emplace_back(create_style(file_name, search_dir + file_name)); + } while (::FindNextFile(hFind, &fd)); + ::FindClose(hFind); + } + return result; +} + +#else +EmbossStyles Emboss::get_font_list() { + // not implemented + return {}; +} + +std::optional Emboss::get_font_path(const std::wstring &font_face_name){ + // not implemented + return {}; +} +#endif + +std::unique_ptr Emboss::create_font_file( + std::unique_ptr> data) +{ + int collection_size = stbtt_GetNumberOfFonts(data->data()); + // at least one font must be inside collection + if (collection_size < 1) { + assert(false); + // There is no font collection inside font data + return nullptr; + } + + unsigned int c_size = static_cast(collection_size); + std::vector infos; + infos.reserve(c_size); + for (unsigned int i = 0; i < c_size; ++i) { + auto font_info = load_font_info(data->data(), i); + if (!font_info.has_value()) return nullptr; + + const stbtt_fontinfo *info = &(*font_info); + // load information about line gap + int ascent, descent, linegap; + stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap); + + float pixels = 1000.; // value is irelevant + float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels); + int units_per_em = static_cast(std::round(pixels / em_pixels)); + + infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em}); + } + return std::make_unique(std::move(data), std::move(infos)); +} + +std::unique_ptr Emboss::create_font_file(const char *file_path) +{ + FILE *file = std::fopen(file_path, "rb"); + if (file == nullptr) { + assert(false); + BOOST_LOG_TRIVIAL(error) << "Couldn't open " << file_path << " for reading."; + return nullptr; + } + ScopeGuard sg([&file]() { std::fclose(file); }); + + // find size of file + if (fseek(file, 0L, SEEK_END) != 0) { + assert(false); + BOOST_LOG_TRIVIAL(error) << "Couldn't fseek file " << file_path << " for size measure."; + return nullptr; + } + size_t size = ftell(file); + if (size == 0) { + assert(false); + BOOST_LOG_TRIVIAL(error) << "Size of font file is zero. Can't read."; + return nullptr; + } + rewind(file); + auto buffer = std::make_unique>(size); + size_t count_loaded_bytes = fread((void *) &buffer->front(), 1, size, file); + if (count_loaded_bytes != size) { + assert(false); + BOOST_LOG_TRIVIAL(error) << "Different loaded(from file) data size."; + return nullptr; + } + return create_font_file(std::move(buffer)); +} + + +#ifdef _WIN32 +static bool load_hfont(void* hfont, DWORD &dwTable, DWORD &dwOffset, size_t& size, HDC hdc = nullptr){ + bool del_hdc = false; + if (hdc == nullptr) { + del_hdc = true; + hdc = ::CreateCompatibleDC(NULL); + if (hdc == NULL) return false; + } + + // To retrieve the data from the beginning of the file for TrueType + // Collection files specify 'ttcf' (0x66637474). + dwTable = 0x66637474; + dwOffset = 0; + + ::SelectObject(hdc, hfont); + size = ::GetFontData(hdc, dwTable, dwOffset, NULL, 0); + if (size == GDI_ERROR) { + // HFONT is NOT TTC(collection) + dwTable = 0; + size = ::GetFontData(hdc, dwTable, dwOffset, NULL, 0); + } + + if (size == 0 || size == GDI_ERROR) { + if (del_hdc) ::DeleteDC(hdc); + return false; + } + return true; +} + +void *Emboss::can_load(void *hfont) +{ + DWORD dwTable=0, dwOffset=0; + size_t size = 0; + if (!load_hfont(hfont, dwTable, dwOffset, size)) return nullptr; + return hfont; +} + +std::unique_ptr Emboss::create_font_file(void *hfont) +{ + HDC hdc = ::CreateCompatibleDC(NULL); + if (hdc == NULL) { + assert(false); + BOOST_LOG_TRIVIAL(error) << "Can't create HDC by CreateCompatibleDC(NULL)."; + return nullptr; + } + + DWORD dwTable=0,dwOffset = 0; + size_t size; + if (!load_hfont(hfont, dwTable, dwOffset, size, hdc)) { + ::DeleteDC(hdc); + return nullptr; + } + auto buffer = std::make_unique>(size); + size_t loaded_size = ::GetFontData(hdc, dwTable, dwOffset, buffer->data(), size); + ::DeleteDC(hdc); + if (size != loaded_size) { + assert(false); + BOOST_LOG_TRIVIAL(error) << "Different loaded(from HFONT) data size."; + return nullptr; + } + return create_font_file(std::move(buffer)); +} +#endif // _WIN32 + +std::optional Emboss::letter2glyph(const FontFile &font, + unsigned int font_index, + int letter, + float flatness) +{ + if (!is_valid(font, font_index)) return {}; + auto font_info_opt = load_font_info(font.data->data(), font_index); + if (!font_info_opt.has_value()) return {}; + return get_glyph(*font_info_opt, letter, flatness); +} + +const FontFile::Info &Emboss::get_font_info(const FontFile &font, const FontProp &prop) +{ + unsigned int font_index = prop.collection_number.value_or(0); + assert(is_valid(font, font_index)); + return font.infos[font_index]; +} + +int Emboss::get_line_height(const FontFile &font, const FontProp &prop) { + const FontFile::Info &info = get_font_info(font, prop); + int line_height = info.ascent - info.descent + info.linegap; + line_height += prop.line_gap.value_or(0); + return static_cast(line_height / SHAPE_SCALE); +} + +namespace { +ExPolygons letter2shapes( + wchar_t letter, Point &cursor, FontFileWithCache &font_with_cache, const FontProp &font_prop, fontinfo_opt& font_info_cache) +{ + assert(font_with_cache.has_value()); + if (!font_with_cache.has_value()) + return {}; + + Glyphs &cache = *font_with_cache.cache; + const FontFile &font = *font_with_cache.font_file; + + if (letter == '\n') { + cursor.x() = 0; + // 2d shape has opposit direction of y + cursor.y() -= get_line_height(font, font_prop); + return {}; + } + if (letter == '\t') { + // '\t' = 4*space => same as imgui + const int count_spaces = 4; + const Glyph *space = get_glyph(int(' '), font, font_prop, cache, font_info_cache); + if (space == nullptr) + return {}; + cursor.x() += count_spaces * space->advance_width; + return {}; + } + if (letter == '\r') + return {}; + + int unicode = static_cast(letter); + auto it = cache.find(unicode); + + // Create glyph from font file and cache it + const Glyph *glyph_ptr = (it != cache.end()) ? &it->second : get_glyph(unicode, font, font_prop, cache, font_info_cache); + if (glyph_ptr == nullptr) + return {}; + + // move glyph to cursor position + ExPolygons expolygons = glyph_ptr->shape; // copy + for (ExPolygon &expolygon : expolygons) + expolygon.translate(cursor); + + cursor.x() += glyph_ptr->advance_width; + return expolygons; +} + +// Check cancel every X letters in text +// Lower number - too much checks(slows down) +// Higher number - slows down response on cancelation +const int CANCEL_CHECK = 10; +} // namespace + +namespace { +HealedExPolygons union_with_delta(const ExPolygonsWithIds &shapes, float delta, unsigned max_heal_iteration) +{ + // unify to one expolygons + ExPolygons expolygons; + for (const ExPolygonsWithId &shape : shapes) { + if (shape.expoly.empty()) + continue; + expolygons_append(expolygons, offset_ex(shape.expoly, delta)); + } + ExPolygons result = union_ex(expolygons); + result = offset_ex(result, -delta); + bool is_healed = heal_expolygons(result, max_heal_iteration); + return {result, is_healed}; +} +} // namespace + +ExPolygons Slic3r::union_with_delta(EmbossShape &shape, float delta, unsigned max_heal_iteration) +{ + if (!shape.final_shape.expolygons.empty()) + return shape.final_shape; + + shape.final_shape = ::union_with_delta(shape.shapes_with_ids, delta, max_heal_iteration); + for (const ExPolygonsWithId &e : shape.shapes_with_ids) + if (!e.is_healed) + shape.final_shape.is_healed = false; + return shape.final_shape.expolygons; +} + +void Slic3r::translate(ExPolygonsWithIds &expolygons_with_ids, const Point &p) +{ + for (ExPolygonsWithId &expolygons_with_id : expolygons_with_ids) + translate(expolygons_with_id.expoly, p); +} + +BoundingBox Slic3r::get_extents(const ExPolygonsWithIds &expolygons_with_ids) +{ + BoundingBox bb; + for (const ExPolygonsWithId &expolygons_with_id : expolygons_with_ids) + bb.merge(get_extents(expolygons_with_id.expoly)); + return bb; +} + +void Slic3r::center(ExPolygonsWithIds &e) +{ + BoundingBox bb = get_extents(e); + translate(e, -bb.center()); +} + +HealedExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const char *text, const FontProp &font_prop, const std::function& was_canceled) +{ + std::wstring text_w = boost::nowide::widen(text); + ExPolygonsWithIds vshapes = text2vshapes(font_with_cache, text_w, font_prop, was_canceled); + + float delta = static_cast(1. / SHAPE_SCALE); + return ::union_with_delta(vshapes, delta, MAX_HEAL_ITERATION_OF_TEXT); +} + +namespace { +/// +/// Align shape against pivot +/// +/// Shapes to align +/// Prerequisities: shapes are aligned left top +/// To detect end of lines - to be able horizontal center the line +/// Containe Horizontal and vertical alignment +/// Needed for scale and font size +void align_shape(ExPolygonsWithIds &shapes, const std::wstring &text, const FontProp &prop, const FontFile &font); +} + +ExPolygonsWithIds Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled){ + assert(font_with_cache.has_value()); + const FontFile &font = *font_with_cache.font_file; + unsigned int font_index = font_prop.collection_number.value_or(0); + if (!is_valid(font, font_index)) + return {}; + + unsigned counter = 0; + Point cursor(0, 0); + + fontinfo_opt font_info_cache; + ExPolygonsWithIds result; + result.reserve(text.size()); + for (wchar_t letter : text) { + if (++counter == CANCEL_CHECK) { + counter = 0; + if (was_canceled()) + return {}; + } + unsigned id = static_cast(letter); + result.push_back({id, letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)}); + } + + align_shape(result, text, font_prop, font); + return result; +} + +#include +unsigned Emboss::get_count_lines(const std::wstring& ws) +{ + if (ws.empty()) + return 0; + + unsigned count = 1; + for (wchar_t wc : ws) + if (wc == '\n') + ++count; + return count; + + // unsigned prev_count = 0; + // for (wchar_t wc : ws) + // if (wc == '\n') + // ++prev_count; + // else + // break; + // + // unsigned post_count = 0; + // for (wchar_t wc : boost::adaptors::reverse(ws)) + // if (wc == '\n') + // ++post_count; + // else + // break; + //return count - prev_count - post_count; +} + +unsigned Emboss::get_count_lines(const std::string &text) +{ + std::wstring ws = boost::nowide::widen(text.c_str()); + return get_count_lines(ws); +} + +unsigned Emboss::get_count_lines(const ExPolygonsWithIds &shapes) { + if (shapes.empty()) + return 0; // no glyphs + unsigned result = 1; // one line is minimum + for (const ExPolygonsWithId &shape_id : shapes) + if (shape_id.id == ENTER_UNICODE) + ++result; + return result; +} + +void Emboss::apply_transformation(const std::optional& angle, const std::optional& distance, Transform3d &transformation) { + if (angle.has_value()) { + double angle_z = *angle; + transformation *= Eigen::AngleAxisd(angle_z, Vec3d::UnitZ()); + } + if (distance.has_value()) { + Vec3d translate = Vec3d::UnitZ() * (*distance); + transformation.translate(translate); + } +} + +bool Emboss::is_italic(const FontFile &font, unsigned int font_index) +{ + if (font_index >= font.infos.size()) return false; + fontinfo_opt font_info_opt = load_font_info(font.data->data(), font_index); + + if (!font_info_opt.has_value()) return false; + stbtt_fontinfo *info = &(*font_info_opt); + + // https://docs.microsoft.com/cs-cz/typography/opentype/spec/name + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html + // 2 ==> Style / Subfamily name + int name_id = 2; + int length; + const char* value = stbtt_GetFontNameString(info, &length, + STBTT_PLATFORM_ID_MICROSOFT, + STBTT_MS_EID_UNICODE_BMP, + STBTT_MS_LANG_ENGLISH, + name_id); + + // value is big endian utf-16 i need extract only normal chars + std::string value_str; + value_str.reserve(length / 2); + for (int i = 1; i < length; i += 2) + value_str.push_back(value[i]); + + // lower case + std::transform(value_str.begin(), value_str.end(), value_str.begin(), + [](unsigned char c) { return std::tolower(c); }); + + const std::vector italics({"italic", "oblique"}); + for (const std::string &it : italics) { + if (value_str.find(it) != std::string::npos) { + return true; + } + } + return false; +} + +std::string Emboss::create_range_text(const std::string &text, + const FontFile &font, + unsigned int font_index, + bool *exist_unknown) +{ + if (!is_valid(font, font_index)) return {}; + + std::wstring ws = boost::nowide::widen(text); + + // need remove symbols not contained in font + std::sort(ws.begin(), ws.end()); + + auto font_info_opt = load_font_info(font.data->data(), 0); + if (!font_info_opt.has_value()) return {}; + const stbtt_fontinfo *font_info = &(*font_info_opt); + + if (exist_unknown != nullptr) *exist_unknown = false; + int prev_unicode = -1; + ws.erase(std::remove_if(ws.begin(), ws.end(), + [&prev_unicode, font_info, exist_unknown](wchar_t wc) -> bool { + int unicode = static_cast(wc); + + // skip white spaces + if (unicode == '\n' || + unicode == '\r' || + unicode == '\t') return true; + + // is duplicit? + if (prev_unicode == unicode) return true; + prev_unicode = unicode; + + // can find in font? + bool is_unknown = !stbtt_FindGlyphIndex(font_info, unicode); + if (is_unknown && exist_unknown != nullptr) + *exist_unknown = true; + return is_unknown; + }), ws.end()); + + return boost::nowide::narrow(ws); +} + +double Emboss::get_text_shape_scale(const FontProp &fp, const FontFile &ff) +{ + const FontFile::Info &info = get_font_info(ff, fp); + double scale = fp.size_in_mm / (double) info.unit_per_em; + // Shape is scaled for store point coordinate as integer + return scale * SHAPE_SCALE; +} + +namespace { + +void add_quad(uint32_t i1, + uint32_t i2, + indexed_triangle_set &result, + uint32_t count_point) +{ + // bottom indices + uint32_t i1_ = i1 + count_point; + uint32_t i2_ = i2 + count_point; + result.indices.emplace_back(i2, i2_, i1); + result.indices.emplace_back(i1_, i1, i2_); +}; + +indexed_triangle_set polygons2model_unique( + const ExPolygons &shape2d, + const IProjection &projection, + const Points &points) +{ + // CW order of triangle indices + std::vector shape_triangles=Triangulation::triangulate(shape2d, points); + uint32_t count_point = points.size(); + + indexed_triangle_set result; + result.vertices.reserve(2 * count_point); + std::vector &front_points = result.vertices; // alias + std::vector back_points; + back_points.reserve(count_point); + + for (const Point &p : points) { + auto p2 = projection.create_front_back(p); + front_points.push_back(p2.first.cast()); + back_points.push_back(p2.second.cast()); + } + + // insert back points, front are already in + result.vertices.insert(result.vertices.end(), + std::make_move_iterator(back_points.begin()), + std::make_move_iterator(back_points.end())); + result.indices.reserve(shape_triangles.size() * 2 + points.size() * 2); + // top triangles - change to CCW + for (const Vec3i32 &t : shape_triangles) + result.indices.emplace_back(t.x(), t.z(), t.y()); + // bottom triangles - use CW + for (const Vec3i32 &t : shape_triangles) + result.indices.emplace_back(t.x() + count_point, + t.y() + count_point, + t.z() + count_point); + + // quads around - zig zag by triangles + size_t polygon_offset = 0; + auto add_quads = [&polygon_offset,&result, &count_point] + (const Polygon& polygon) { + uint32_t polygon_points = polygon.points.size(); + // previous index + uint32_t prev = polygon_offset + polygon_points - 1; + for (uint32_t p = 0; p < polygon_points; ++p) { + uint32_t index = polygon_offset + p; + add_quad(prev, index, result, count_point); + prev = index; + } + polygon_offset += polygon_points; + }; + + for (const ExPolygon &expolygon : shape2d) { + add_quads(expolygon.contour); + for (const Polygon &hole : expolygon.holes) add_quads(hole); + } + + return result; +} + +indexed_triangle_set polygons2model_duplicit( + const ExPolygons &shape2d, + const IProjection &projection, + const Points &points, + const Points &duplicits) +{ + // CW order of triangle indices + std::vector changes = Triangulation::create_changes(points, duplicits); + std::vector shape_triangles = Triangulation::triangulate(shape2d, points, changes); + uint32_t count_point = *std::max_element(changes.begin(), changes.end()) + 1; + + indexed_triangle_set result; + result.vertices.reserve(2 * count_point); + std::vector &front_points = result.vertices; + std::vector back_points; + back_points.reserve(count_point); + + uint32_t max_index = std::numeric_limits::max(); + for (uint32_t i = 0; i < changes.size(); ++i) { + uint32_t index = changes[i]; + if (max_index != std::numeric_limits::max() && + index <= max_index) continue; // duplicit point + assert(index == max_index + 1); + assert(front_points.size() == index); + assert(back_points.size() == index); + max_index = index; + const Point &p = points[i]; + auto p2 = projection.create_front_back(p); + front_points.push_back(p2.first.cast()); + back_points.push_back(p2.second.cast()); + } + assert(max_index+1 == count_point); + + // insert back points, front are already in + result.vertices.insert(result.vertices.end(), + std::make_move_iterator(back_points.begin()), + std::make_move_iterator(back_points.end())); + + result.indices.reserve(shape_triangles.size() * 2 + points.size() * 2); + // top triangles - change to CCW + for (const Vec3i32 &t : shape_triangles) + result.indices.emplace_back(t.x(), t.z(), t.y()); + // bottom triangles - use CW + for (const Vec3i32 &t : shape_triangles) + result.indices.emplace_back(t.x() + count_point, t.y() + count_point, + t.z() + count_point); + + // quads around - zig zag by triangles + size_t polygon_offset = 0; + auto add_quads = [&polygon_offset, &result, count_point, &changes] + (const Polygon &polygon) { + uint32_t polygon_points = polygon.points.size(); + // previous index + uint32_t prev = changes[polygon_offset + polygon_points - 1]; + for (uint32_t p = 0; p < polygon_points; ++p) { + uint32_t index = changes[polygon_offset + p]; + if (prev == index) continue; + add_quad(prev, index, result, count_point); + prev = index; + } + polygon_offset += polygon_points; + }; + + for (const ExPolygon &expolygon : shape2d) { + add_quads(expolygon.contour); + for (const Polygon &hole : expolygon.holes) add_quads(hole); + } + return result; +} +} // namespace + +indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, + const IProjection &projection) +{ + Points points = to_points(shape2d); + Points duplicits = collect_duplicates(points); + return (duplicits.empty()) ? + polygons2model_unique(shape2d, projection, points) : + polygons2model_duplicit(shape2d, projection, points, duplicits); +} + +std::pair Emboss::ProjectZ::create_front_back(const Point &p) const +{ + Vec3d front(p.x(), p.y(), 0.); + return std::make_pair(front, project(front)); +} + +Vec3d Emboss::ProjectZ::project(const Vec3d &point) const +{ + Vec3d res = point; // copy + res.z() = m_depth; + return res; +} + +std::optional Emboss::ProjectZ::unproject(const Vec3d &p, double *depth) const { + return Vec2d(p.x(), p.y()); +} + + +Vec3d Emboss::suggest_up(const Vec3d normal, double up_limit) +{ + // Normal must be 1 + assert(is_approx(normal.squaredNorm(), 1.)); + + // wanted up direction of result + Vec3d wanted_up_side = + (std::fabs(normal.z()) > up_limit)? + Vec3d::UnitY() : Vec3d::UnitZ(); + + // create perpendicular unit vector to surface triangle normal vector + // lay on surface of triangle and define up vector for text + Vec3d wanted_up_dir = normal.cross(wanted_up_side).cross(normal); + // normal3d is NOT perpendicular to normal_up_dir + wanted_up_dir.normalize(); + + return wanted_up_dir; +} + +std::optional Emboss::calc_up(const Transform3d &tr, double up_limit) +{ + auto tr_linear = tr.linear(); + // z base of transformation ( tr * UnitZ ) + Vec3d normal = tr_linear.col(2); + // scaled matrix has base with different size + normal.normalize(); + Vec3d suggested = suggest_up(normal, up_limit); + assert(is_approx(suggested.squaredNorm(), 1.)); + + Vec3d up = tr_linear.col(1); // tr * UnitY() + up.normalize(); + Matrix3d m; + m.row(0) = up; + m.row(1) = suggested; + m.row(2) = normal; + double det = m.determinant(); + double dot = suggested.dot(up); + double res = -atan2(det, dot); + if (is_approx(res, 0.)) + return {}; + return res; +} + +Transform3d Emboss::create_transformation_onto_surface(const Vec3d &position, + const Vec3d &normal, + double up_limit) +{ + // is normalized ? + assert(is_approx(normal.squaredNorm(), 1.)); + + // up and emboss direction for generated model + Vec3d up_dir = Vec3d::UnitY(); + Vec3d emboss_dir = Vec3d::UnitZ(); + + // after cast from float it needs to be normalized again + Vec3d wanted_up_dir = suggest_up(normal, up_limit); + + // perpendicular to emboss vector of text and normal + Vec3d axis_view; + double angle_view; + if (normal == -Vec3d::UnitZ()) { + // text_emboss_dir has opposit direction to wanted_emboss_dir + axis_view = Vec3d::UnitY(); + angle_view = M_PI; + } else { + axis_view = emboss_dir.cross(normal); + angle_view = std::acos(emboss_dir.dot(normal)); // in rad + axis_view.normalize(); + } + + Eigen::AngleAxis view_rot(angle_view, axis_view); + Vec3d wanterd_up_rotated = view_rot.matrix().inverse() * wanted_up_dir; + wanterd_up_rotated.normalize(); + double angle_up = std::acos(up_dir.dot(wanterd_up_rotated)); + + Vec3d text_view = up_dir.cross(wanterd_up_rotated); + Vec3d diff_view = emboss_dir - text_view; + if (std::fabs(diff_view.x()) > 1. || + std::fabs(diff_view.y()) > 1. || + std::fabs(diff_view.z()) > 1.) // oposit direction + angle_up *= -1.; + + Eigen::AngleAxis up_rot(angle_up, emboss_dir); + + Transform3d transform = Transform3d::Identity(); + transform.translate(position); + transform.rotate(view_rot); + transform.rotate(up_rot); + return transform; +} + + +// OrthoProject + +std::pair Emboss::OrthoProject::create_front_back(const Point &p) const { + Vec3d front(p.x(), p.y(), 0.); + Vec3d front_tr = m_matrix * front; + return std::make_pair(front_tr, project(front_tr)); +} + +Vec3d Emboss::OrthoProject::project(const Vec3d &point) const +{ + return point + m_direction; +} + +std::optional Emboss::OrthoProject::unproject(const Vec3d &p, double *depth) const +{ + Vec3d pp = m_matrix_inv * p; + if (depth != nullptr) *depth = pp.z(); + return Vec2d(pp.x(), pp.y()); +} + +// sample slice +namespace { + +// using coor2 = int64_t; +using Coord2 = double; +using P2 = Eigen::Matrix; + +bool point_in_distance(const Coord2 &distance_sq, PolygonPoint &polygon_point, const size_t &i, const Slic3r::Polygon &polygon, bool is_first, bool is_reverse = false) +{ + size_t s = polygon.size(); + size_t ii = (i + polygon_point.index) % s; + + // second point of line + const Point &p = polygon[ii]; + Point p_d = p - polygon_point.point; + + P2 p_d2 = p_d.cast(); + Coord2 p_distance_sq = p_d2.squaredNorm(); + if (p_distance_sq < distance_sq) + return false; + + // found line + if (is_first) { + // on same line + // center also lay on line + // new point is distance moved from point by direction + polygon_point.point += p_d * sqrt(distance_sq / p_distance_sq); + return true; + } + + // line cross circle + + // start point of line + size_t ii2 = (is_reverse) ? (ii + 1) % s : (ii + s - 1) % s; + polygon_point.index = (is_reverse) ? ii : ii2; + const Point &p2 = polygon[ii2]; + + Point line_dir = p2 - p; + P2 line_dir2 = line_dir.cast(); + + Coord2 a = line_dir2.dot(line_dir2); + Coord2 b = 2 * p_d2.dot(line_dir2); + Coord2 c = p_d2.dot(p_d2) - distance_sq; + + double discriminant = b * b - 4 * a * c; + if (discriminant < 0) { + assert(false); + // no intersection + polygon_point.point = p; + return true; + } + + // ray didn't totally miss sphere, + // so there is a solution to + // the equation. + discriminant = sqrt(discriminant); + + // either solution may be on or off the ray so need to test both + // t1 is always the smaller value, because BOTH discriminant and + // a are nonnegative. + double t1 = (-b - discriminant) / (2 * a); + double t2 = (-b + discriminant) / (2 * a); + + double t = std::min(t1, t2); + if (t < 0. || t > 1.) { + // Bad intersection + assert(false); + polygon_point.point = p; + return true; + } + + polygon_point.point = p + (t * line_dir2).cast(); + return true; +} + +void point_in_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon) +{ + Coord2 distance_sq = static_cast(distance) * distance; + bool is_first = true; + for (size_t i = 1; i < polygon.size(); ++i) { + if (point_in_distance(distance_sq, p, i, polygon, is_first)) + return; + is_first = false; + } + // There is not point on polygon with this distance +} + +void point_in_reverse_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon) +{ + Coord2 distance_sq = static_cast(distance) * distance; + bool is_first = true; + bool is_reverse = true; + for (size_t i = polygon.size(); i > 0; --i) { + if (point_in_distance(distance_sq, p, i, polygon, is_first, is_reverse)) + return; + is_first = false; + } + // There is not point on polygon with this distance +} +} // namespace + +// calculate rotation, need copy of polygon point +double Emboss::calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon) +{ + PolygonPoint polygon_point2 = polygon_point; // copy + point_in_distance(distance, polygon_point, polygon); + point_in_reverse_distance(distance, polygon_point2, polygon); + + Point surface_dir = polygon_point2.point - polygon_point.point; + Point norm(-surface_dir.y(), surface_dir.x()); + Vec2d norm_d = norm.cast(); + //norm_d.normalize(); + return std::atan2(norm_d.y(), norm_d.x()); +} + +std::vector Emboss::calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon) +{ + std::vector result; + result.reserve(polygon_points.size()); + for(const PolygonPoint& pp: polygon_points) + result.emplace_back(calculate_angle(distance, pp, polygon)); + return result; +} + +PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &qds, double scale) +{ + // find BB in center of line + size_t first_right_index = 0; + for (const BoundingBox &bb : qds) + if (!bb.defined) // white char do not have bb + continue; + else if (bb.min.x() < 0) + ++first_right_index; + else + break; + + PolygonPoints samples(qds.size()); + int32_t shapes_x_cursor = 0; + + PolygonPoint cursor = slice.start; //copy + + auto create_sample = [&] //polygon_cursor, &polygon_line_index, &line_qds, &shapes_x_cursor, &shape_scale, &em_2_polygon, &line, &offsets] + (const BoundingBox &bb, bool is_reverse) { + if (!bb.defined) + return cursor; + Point letter_center = bb.center(); + int32_t shape_distance = shapes_x_cursor - letter_center.x(); + shapes_x_cursor = letter_center.x(); + double distance_mm = shape_distance * scale; + int32_t distance_polygon = static_cast(std::round(scale_(distance_mm))); + if (is_reverse) + point_in_distance(distance_polygon, cursor, slice.polygon); + else + point_in_reverse_distance(distance_polygon, cursor, slice.polygon); + return cursor; + }; + + // calc transformation for letters on the Right side from center + bool is_reverse = true; + for (size_t index = first_right_index; index < qds.size(); ++index) + samples[index] = create_sample(qds[index], is_reverse); + + // calc transformation for letters on the Left side from center + if (first_right_index < qds.size()) { + shapes_x_cursor = qds[first_right_index].center().x(); + cursor = samples[first_right_index]; + }else{ + // only left side exists + shapes_x_cursor = 0; + cursor = slice.start; // copy + } + is_reverse = false; + for (size_t index_plus_one = first_right_index; index_plus_one > 0; --index_plus_one) { + size_t index = index_plus_one - 1; + samples[index] = create_sample(qds[index], is_reverse); + } + return samples; +} + +namespace { +float get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp) +{ + assert(count_lines != 0); + int line_height = get_line_height(ff, fp); + int ascent = get_font_info(ff, fp).ascent / SHAPE_SCALE; + float line_center = static_cast(std::round(ascent * ASCENT_CENTER)); + + // direction of Y in 2d is from top to bottom + // zero is on base line of first line + switch (align) { + case FontProp::VerticalAlign::bottom: return line_height * (count_lines - 1); + case FontProp::VerticalAlign::top: return -ascent; + case FontProp::VerticalAlign::center: + default: + return -line_center + line_height * (count_lines - 1) / 2.; + } +} + +int32_t get_align_x_offset(FontProp::HorizontalAlign align, const BoundingBox &shape_bb, const BoundingBox &line_bb) +{ + switch (align) { + case FontProp::HorizontalAlign::right: return -shape_bb.max.x() + (shape_bb.size().x() - line_bb.size().x()); + case FontProp::HorizontalAlign::center: return -shape_bb.center().x() + (shape_bb.size().x() - line_bb.size().x()) / 2; + case FontProp::HorizontalAlign::left: // no change + default: break; + } + return 0; +} + +void align_shape(ExPolygonsWithIds &shapes, const std::wstring &text, const FontProp &prop, const FontFile &font) +{ + // Shapes have to match letters in text + assert(shapes.size() == text.length()); + + unsigned count_lines = get_count_lines(text); + int y_offset = get_align_y_offset(prop.align.second, count_lines, font, prop); + + // Speed up for left aligned text + if (prop.align.first == FontProp::HorizontalAlign::left){ + // already horizontaly aligned + for (ExPolygonsWithId& shape : shapes) + for (ExPolygon &s : shape.expoly) + s.translate(Point(0, y_offset)); + return; + } + + BoundingBox shape_bb; + for (const ExPolygonsWithId& shape: shapes) + shape_bb.merge(get_extents(shape.expoly)); + + auto get_line_bb = [&](size_t j) { + BoundingBox line_bb; + for (; j < text.length() && text[j] != '\n'; ++j) + line_bb.merge(get_extents(shapes[j].expoly)); + return line_bb; + }; + + // Align x line by line + Point offset( + get_align_x_offset(prop.align.first, shape_bb, get_line_bb(0)), + y_offset); + for (size_t i = 0; i < shapes.size(); ++i) { + wchar_t letter = text[i]; + if (letter == '\n'){ + offset.x() = get_align_x_offset(prop.align.first, shape_bb, get_line_bb(i + 1)); + continue; + } + ExPolygons &shape = shapes[i].expoly; + for (ExPolygon &s : shape) + s.translate(offset); + } +} +} // namespace + +double Emboss::get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp){ + float offset_in_font_point = get_align_y_offset(align, count_lines, ff, fp); + double scale = get_text_shape_scale(fp, ff); + return scale * offset_in_font_point; +} + +#ifdef REMOVE_SPIKES +#include +void remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc) +{ + enum class Type { + add, // Move with point B on A-side and add new point on C-side + move, // Only move with point B + erase // left only points A and C without move + }; + struct SpikeHeal + { + Type type; + size_t index; + Point b; + Point add; + }; + using SpikeHeals = std::vector; + SpikeHeals heals; + + size_t count = polygon.size(); + if (count < 3) + return; + + const Point *ptr_a = &polygon[count - 2]; + const Point *ptr_b = &polygon[count - 1]; + for (const Point &c : polygon) { + const Point &a = *ptr_a; + const Point &b = *ptr_b; + ScopeGuard sg([&ptr_a, &ptr_b, &c]() { + // prepare for next loop + ptr_a = ptr_b; + ptr_b = &c; + }); + + // calc sides + Point ba = a - b; + Point bc = c - b; + + Vec2d ba_f = ba.cast(); + Vec2d bc_f = bc.cast(); + double dot_product = ba_f.dot(bc_f); + + // sqrt together after multiplication save one sqrt + double ba_size_sq = ba_f.squaredNorm(); + double bc_size_sq = bc_f.squaredNorm(); + double norm = sqrt(ba_size_sq * bc_size_sq); + double cos_angle = dot_product / norm; + + // small angle are around 1 --> cos(0) = 1 + if (cos_angle < spike_desc.cos_angle) + continue; + + SpikeHeal heal; + heal.index = &b - &polygon.points.front(); + + // has to be in range <-1, 1> + // Due to preccission of floating point number could be sligtly out of range + if (cos_angle > 1.) + cos_angle = 1.; + if (cos_angle < -1.) + cos_angle = -1.; + + // Current Spike angle + double angle = acos(cos_angle); + double wanted_size = spike_desc.half_bevel / cos(angle / 2.); + double wanted_size_sq = wanted_size * wanted_size; + + bool is_ba_short = ba_size_sq < wanted_size_sq; + bool is_bc_short = bc_size_sq < wanted_size_sq; + auto a_side = [&b, &ba_f, &ba_size_sq, &wanted_size]() { + Vec2d ba_norm = ba_f / sqrt(ba_size_sq); + return b + (wanted_size * ba_norm).cast(); + }; + auto c_side = [&b, &bc_f, &bc_size_sq, &wanted_size]() { + Vec2d bc_norm = bc_f / sqrt(bc_size_sq); + return b + (wanted_size * bc_norm).cast(); + }; + if (is_ba_short && is_bc_short) { + // remove short spike + heal.type = Type::erase; + } else if (is_ba_short){ + // move point B on C-side + heal.type = Type::move; + heal.b = c_side(); + } else if (is_bc_short) { + // move point B on A-side + heal.type = Type::move; + heal.b = a_side(); + } else { + // move point B on A-side and add point on C-side + heal.type = Type::add; + heal.b = a_side(); + heal.add = c_side(); + } + heals.push_back(heal); + } + + if (heals.empty()) + return; + + // sort index from high to low + if (heals.front().index == (count - 1)) + std::rotate(heals.begin(), heals.begin()+1, heals.end()); + std::reverse(heals.begin(), heals.end()); + + int extend = 0; + int curr_extend = 0; + for (const SpikeHeal &h : heals) + switch (h.type) { + case Type::add: + ++curr_extend; + if (extend < curr_extend) + extend = curr_extend; + break; + case Type::erase: + --curr_extend; + } + + Points &pts = polygon.points; + if (extend > 0) + pts.reserve(pts.size() + extend); + + for (const SpikeHeal &h : heals) { + switch (h.type) { + case Type::add: + pts[h.index] = h.b; + pts.insert(pts.begin() + h.index+1, h.add); + break; + case Type::erase: + pts.erase(pts.begin() + h.index); + break; + case Type::move: + pts[h.index] = h.b; + break; + default: break; + } + } +} + +void remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc) +{ + for (Polygon &polygon : polygons) + remove_spikes(polygon, spike_desc); + remove_bad(polygons); +} + +void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc) +{ + for (ExPolygon &expolygon : expolygons) { + remove_spikes(expolygon.contour, spike_desc); + remove_spikes(expolygon.holes, spike_desc); + } + remove_bad(expolygons); +} + +#endif // REMOVE_SPIKES diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp new file mode 100644 index 0000000..d0ea17d --- /dev/null +++ b/src/libslic3r/Emboss.hpp @@ -0,0 +1,473 @@ +#ifndef slic3r_Emboss_hpp_ +#define slic3r_Emboss_hpp_ + +#include +#include +#include +#include +#include // indexed_triangle_set +#include "Polygon.hpp" +#include "ExPolygon.hpp" +#include "EmbossShape.hpp" // ExPolygonsWithIds +#include "BoundingBox.hpp" +#include "TextConfiguration.hpp" + +namespace Slic3r { + +/// +/// class with only static function add ability to engraved OR raised +/// text OR polygons onto model surface +/// +namespace Emboss +{ + static const float UNION_DELTA = 50.0f; // [approx in nano meters depends on volume scale] + static const unsigned UNION_MAX_ITERATIN = 10; // [count] + + /// + /// Collect fonts registred inside OS + /// + /// OS registred TTF font files(full path) with names + EmbossStyles get_font_list(); +#ifdef _WIN32 + EmbossStyles get_font_list_by_register(); + EmbossStyles get_font_list_by_enumeration(); + EmbossStyles get_font_list_by_folder(); +#endif + + /// + /// OS dependent function to get location of font by its name descriptor + /// + /// Unique identificator for font + /// File path to font when found + std::optional get_font_path(const std::wstring &font_face_name); + + // description of one letter + struct Glyph + { + // NOTE: shape is scaled by SHAPE_SCALE + // to be able store points without floating points + ExPolygons shape; + + // values are in font points + int advance_width=0, left_side_bearing=0; + }; + // cache for glyph by unicode + using Glyphs = std::map; + + /// + /// keep information from file about font + /// (store file data itself) + /// + cache data readed from buffer + /// + struct FontFile + { + // loaded data from font file + // must store data size for imgui rasterization + // To not store data on heap and To prevent unneccesary copy + // data are stored inside unique_ptr + std::unique_ptr> data; + + struct Info + { + // vertical position is "scale*(ascent - descent + lineGap)" + int ascent, descent, linegap; + + // for convert font units to pixel + int unit_per_em; + }; + // info for each font in data + std::vector infos; + + FontFile(std::unique_ptr> data, + std::vector &&infos) + : data(std::move(data)), infos(std::move(infos)) + { + assert(this->data != nullptr); + assert(!this->data->empty()); + } + + bool operator==(const FontFile &other) const { + if (data->size() != other.data->size()) + return false; + //if(*data != *other.data) return false; + for (size_t i = 0; i < infos.size(); i++) + if (infos[i].ascent != other.infos[i].ascent || + infos[i].descent == other.infos[i].descent || + infos[i].linegap == other.infos[i].linegap) + return false; + return true; + } + }; + + /// + /// Add caching for shape of glyphs + /// + struct FontFileWithCache + { + // Pointer on data of the font file + std::shared_ptr font_file; + + // Cache for glyph shape + // IMPORTANT: accessible only in plater job thread !!! + // main thread only clear cache by set to another shared_ptr + std::shared_ptr cache; + + FontFileWithCache() : font_file(nullptr), cache(nullptr) {} + explicit FontFileWithCache(std::unique_ptr font_file) + : font_file(std::move(font_file)) + , cache(std::make_shared()) + {} + bool has_value() const { return font_file != nullptr && cache != nullptr; } + }; + + /// + /// Load font file into buffer + /// + /// Location of .ttf or .ttc font file + /// Font object when loaded. + std::unique_ptr create_font_file(const char *file_path); + // data = raw file data + std::unique_ptr create_font_file(std::unique_ptr> data); +#ifdef _WIN32 + // fix for unknown pointer HFONT is replaced with "void *" + void * can_load(void* hfont); + std::unique_ptr create_font_file(void * hfont); +#endif // _WIN32 + + /// + /// convert letter into polygons + /// + /// Define fonts + /// Index of font in collection + /// One character defined by unicode codepoint + /// Precision of lettter outline curve in conversion to lines + /// inner polygon cw(outer ccw) + std::optional letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness); + + /// + /// Convert text into polygons + /// + /// Define fonts + cache, which could extend + /// Characters to convert + /// User defined property of the font + /// Way to interupt processing + /// Inner polygon cw(outer ccw) + HealedExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function &was_canceled = []() {return false;}); + ExPolygonsWithIds text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled = []() {return false;}); + + const unsigned ENTER_UNICODE = static_cast('\n'); + /// Sum of character '\n' + unsigned get_count_lines(const std::wstring &ws); + unsigned get_count_lines(const std::string &text); + unsigned get_count_lines(const ExPolygonsWithIds &shape); + + /// + /// Fix duplicit points and self intersections in polygons. + /// Also try to reduce amount of points and remove useless polygon parts + /// + /// Fill type ClipperLib::pftNonZero for overlapping otherwise + /// Look at heal_expolygon()::max_iteration + /// Healed shapes with flag is fully healed + HealedExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10); + + /// + /// NOTE: call Slic3r::union_ex before this call + /// + /// Heal (read: Fix) issues in expolygons: + /// - self intersections + /// - duplicit points + /// - points close to line segments + /// + /// In/Out shape to heal + /// Heal could create another issue, + /// After healing it is checked again until shape is good or maximal count of iteration + /// True when shapes is good otherwise False + bool heal_expolygons(ExPolygons &shape, unsigned max_iteration = 10); + + /// + /// Divide line segments in place near to point + /// (which could lead to self intersection due to preccision) + /// Remove same neighbors + /// Note: Possible part of heal shape + /// + /// Expolygon to edit + /// (epsilon)Euclidean distance from point to line which divide line + /// True when some division was made otherwise false + bool divide_segments_for_close_point(ExPolygons &expolygons, double distance); + + /// + /// Use data from font property to modify transformation + /// + /// Z-rotation as angle to Y axis + /// Z-move as surface distance + /// In / Out transformation to modify by property + void apply_transformation(const std::optional &angle, const std::optional &distance, Transform3d &transformation); + + /// + /// Read information from naming table of font file + /// search for italic (or oblique), bold italic (or bold oblique) + /// + /// Selector of font + /// Index of font in collection + /// True when the font description contains italic/obligue otherwise False + bool is_italic(const FontFile &font, unsigned int font_index); + + /// + /// Create unique character set from string with filtered from text with only character from font + /// + /// Source vector of glyphs + /// Font descriptor + /// Define font in collection + /// True when text contain glyph unknown in font + /// Unique set of character from text contained in font + std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr); + + /// + /// Calculate scale for glyph shape convert from shape points to mm + /// + /// Property of font + /// Font data + /// Conversion to mm + double get_text_shape_scale(const FontProp &fp, const FontFile &ff); + + /// + /// getter of font info by collection defined in prop + /// + /// Contain infos about all fonts(collections) in file + /// Index of collection + /// Ascent, descent, line gap + const FontFile::Info &get_font_info(const FontFile &font, const FontProp &prop); + + /// + /// Read from font file and properties height of line with spacing + /// + /// Infos for collections + /// Collection index + Additional line gap + /// Line height with spacing in scaled font points (same as ExPolygons) + int get_line_height(const FontFile &font, const FontProp &prop); + + /// + /// Calculate Vertical align + /// + /// Top | Center | Bottom + /// + /// Return align Y offset in mm + double get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp); + + /// + /// Project spatial point + /// + class IProject3d + { + public: + virtual ~IProject3d() = default; + /// + /// Move point with respect to projection direction + /// e.g. Orthogonal projection will move with point by direction + /// e.g. Spherical projection need to use center of projection + /// + /// Spatial point coordinate + /// Projected spatial point + virtual Vec3d project(const Vec3d &point) const = 0; + }; + + /// + /// Project 2d point into space + /// Could be plane, sphere, cylindric, ... + /// + class IProjection : public IProject3d + { + public: + virtual ~IProjection() = default; + + /// + /// convert 2d point to 3d points + /// + /// 2d coordinate + /// + /// first - front spatial point + /// second - back spatial point + /// + virtual std::pair create_front_back(const Point &p) const = 0; + + /// + /// Back projection + /// + /// Point to project + /// [optional] Depth of 2d projected point. Be careful number is in 2d scale + /// Uprojected point when it is possible + virtual std::optional unproject(const Vec3d &p, double * depth = nullptr) const = 0; + }; + + /// + /// Create triangle model for text + /// + /// text or image + /// Define transformation from 2d to 3d(orientation, position, scale, ...) + /// Projected shape into space + indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection); + + /// + /// Suggest wanted up vector of embossed text by emboss direction + /// + /// Normalized vector of emboss direction in world + /// Is compared with normal.z to suggest up direction + /// Wanted up vector + Vec3d suggest_up(const Vec3d normal, double up_limit = 0.9); + + /// + /// By transformation calculate angle between suggested and actual up vector + /// + /// Transformation of embossed volume in world + /// Is compared with normal.z to suggest up direction + /// Rotation of suggested up-vector[in rad] in the range [-Pi, Pi], When rotation is not zero + std::optional calc_up(const Transform3d &tr, double up_limit = 0.9); + + /// + /// Create transformation for emboss text object to lay on surface point + /// + /// Position of surface point + /// Normal of surface point + /// Is compared with normal.z to suggest up direction + /// Transformation onto surface point + Transform3d create_transformation_onto_surface( + const Vec3d &position, const Vec3d &normal, double up_limit = 0.9); + + class ProjectZ : public IProjection + { + public: + explicit ProjectZ(double depth) : m_depth(depth) {} + // Inherited via IProject + std::pair create_front_back(const Point &p) const override; + Vec3d project(const Vec3d &point) const override; + std::optional unproject(const Vec3d &p, double * depth = nullptr) const override; + double m_depth; + }; + + class ProjectScale : public IProjection + { + std::unique_ptr core; + double m_scale; + public: + ProjectScale(std::unique_ptr core, double scale) + : core(std::move(core)), m_scale(scale) + {} + + // Inherited via IProject + std::pair create_front_back(const Point &p) const override + { + auto res = core->create_front_back(p); + return std::make_pair(res.first * m_scale, res.second * m_scale); + } + Vec3d project(const Vec3d &point) const override{ + return core->project(point); + } + std::optional unproject(const Vec3d &p, double *depth = nullptr) const override { + auto res = core->unproject(p / m_scale, depth); + if (depth != nullptr) *depth *= m_scale; + return res; + } + }; + + class ProjectTransform : public IProjection + { + std::unique_ptr m_core; + Transform3d m_tr; + Transform3d m_tr_inv; + double z_scale; + public: + ProjectTransform(std::unique_ptr core, const Transform3d &tr) : m_core(std::move(core)), m_tr(tr) + { + m_tr_inv = m_tr.inverse(); + z_scale = (m_tr.linear() * Vec3d::UnitZ()).norm(); + } + + // Inherited via IProject + std::pair create_front_back(const Point &p) const override + { + auto [front, back] = m_core->create_front_back(p); + return std::make_pair(m_tr * front, m_tr * back); + } + Vec3d project(const Vec3d &point) const override{ + return m_core->project(point); + } + std::optional unproject(const Vec3d &p, double *depth = nullptr) const override { + auto res = m_core->unproject(m_tr_inv * p, depth); + if (depth != nullptr) + *depth *= z_scale; + return res; + } + }; + + class OrthoProject3d : public Emboss::IProject3d + { + // size and direction of emboss for ortho projection + Vec3d m_direction; + public: + OrthoProject3d(Vec3d direction) : m_direction(direction) {} + Vec3d project(const Vec3d &point) const override{ return point + m_direction;} + }; + + class OrthoProject: public Emboss::IProjection { + Transform3d m_matrix; + // size and direction of emboss for ortho projection + Vec3d m_direction; + Transform3d m_matrix_inv; + public: + OrthoProject(Transform3d matrix, Vec3d direction) + : m_matrix(matrix), m_direction(direction), m_matrix_inv(matrix.inverse()) + {} + // Inherited via IProject + std::pair create_front_back(const Point &p) const override; + Vec3d project(const Vec3d &point) const override; + std::optional unproject(const Vec3d &p, double * depth = nullptr) const override; + }; + + /// + /// Define polygon for draw letters + /// + struct TextLine + { + // slice of object + Polygon polygon; + + // point laying on polygon closest to zero + PolygonPoint start; + + // offset of text line in volume mm + float y; + }; + using TextLines = std::vector; + + /// + /// Sample slice polygon by bounding boxes centers + /// slice start point has shape_center_x coor + /// + /// Polygon and start point[Slic3r scaled milimeters] + /// Bounding boxes of letter on one line[in font scales] + /// Scale for qds (after multiply bb is in milimeters) + /// Sampled polygon by bounding boxes + PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &qds, double scale); + + /// + /// Calculate angle for polygon point + /// + /// Distance for found normal in point + /// Select point on polygon + /// Polygon know neighbor of point + /// angle(atan2) of normal in polygon point + double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon); + std::vector calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon); + +} // namespace Emboss + +/////////////////////// +// Move to ExPolygonsWithIds Utils +void translate(ExPolygonsWithIds &e, const Point &p); +BoundingBox get_extents(const ExPolygonsWithIds &e); +void center(ExPolygonsWithIds &e); +// delta .. safe offset before union (use as boolean close) +// NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve) +ExPolygons union_with_delta(EmbossShape &shape, float delta, unsigned max_heal_iteration); +} // namespace Slic3r +#endif // slic3r_Emboss_hpp_ diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp new file mode 100644 index 0000000..d705178 --- /dev/null +++ b/src/libslic3r/EmbossShape.hpp @@ -0,0 +1,143 @@ +#ifndef slic3r_EmbossShape_hpp_ +#define slic3r_EmbossShape_hpp_ + +#include +#include +#include // unique_ptr +#include +#include +#include +#include +#include +#include "Point.hpp" // Transform3d +#include "ExPolygon.hpp" +#include "ExPolygonSerialize.hpp" +#include "nanosvg/nanosvg.h" // NSVGimage + +namespace Slic3r { + +struct EmbossProjection{ + // Emboss depth, Size in local Z direction + double depth = 1.; // [in loacal mm] + // NOTE: User should see and modify mainly world size not local + + // Flag that result volume use surface cutted from source objects + bool use_surface = false; + + bool operator==(const EmbossProjection &other) const { + return depth == other.depth && use_surface == other.use_surface; + } + + // undo / redo stack recovery + template void serialize(Archive &ar) { ar(depth, use_surface); } +}; + +// Extend expolygons with information whether it was successfull healed +struct HealedExPolygons{ + ExPolygons expolygons; + bool is_healed; + operator ExPolygons&() { return expolygons; } +}; + +// Help structure to identify expolygons grups +// e.g. emboss -> per glyph -> identify character +struct ExPolygonsWithId +{ + // Identificator for shape + // In text it separate letters and the name is unicode value of letter + // Is svg it is id of path + unsigned id; + + // shape defined by integer point contain only lines + // Curves are converted to sequence of lines + ExPolygons expoly; + + // flag whether expolygons are fully healed(without duplication) + bool is_healed = true; +}; +using ExPolygonsWithIds = std::vector; + +/// +/// Contain plane shape information to be able emboss it and edit it +/// +struct EmbossShape +{ + // shapes to to emboss separately over surface + ExPolygonsWithIds shapes_with_ids; + + // Only cache for final shape + // It is calculated from ExPolygonsWithIds + // Flag is_healed --> whether union of shapes is healed + // Healed mean without selfintersection and point duplication + HealedExPolygons final_shape; + + // scale of shape, multiplier to get 3d point in mm from integer shape + double scale = SCALING_FACTOR; + + // Define how to emboss shape + EmbossProjection projection; + + // !!! Volume stored in .3mf has transformed vertices. + // (baked transformation into vertices position) + // Only place for fill this is when load from .3mf + // This is correction for volume transformation + // Stored_Transform3d * fix_3mf_tr = Transform3d_before_store_to_3mf + std::optional fix_3mf_tr; + + struct SvgFile { + // File(.svg) path on local computer + // When empty can't reload from disk + std::string path; + + // File path into .3mf(.zip) + // When empty svg is not stored into .3mf file yet. + // and will create dialog to delete private data on save. + std::string path_in_3mf; + + // Loaded svg file data. + // !!! It is not serialized on undo/redo stack + std::shared_ptr image = nullptr; + + // Loaded string data from file + std::shared_ptr file_data = nullptr; + + template void save(Archive &ar) const { + // Note: image is only cache it is not neccessary to store + + // Store file data as plain string + // For Embossed text file_data are nullptr + ar(path, path_in_3mf, (file_data != nullptr) ? *file_data : std::string("")); + } + template void load(Archive &ar) { + // for restore shared pointer on file data + std::string file_data_str; + ar(path, path_in_3mf, file_data_str); + if (!file_data_str.empty()) + file_data = std::make_unique(file_data_str); + } + }; + // When embossing shape is made by svg file this is source data + std::optional svg_file; + + // undo / redo stack recovery + template void save(Archive &ar) const + { + // final_shape is not neccessary to store - it is only cache + ar(shapes_with_ids, final_shape, scale, projection, svg_file); + cereal::save(ar, fix_3mf_tr); + } + template void load(Archive &ar) + { + ar(shapes_with_ids, final_shape, scale, projection, svg_file); + cereal::load(ar, fix_3mf_tr); + } +}; +} // namespace Slic3r + +// Serialization through the Cereal library +namespace cereal { +template void serialize(Archive &ar, Slic3r::ExPolygonsWithId &o) { ar(o.id, o.expoly, o.is_healed); } +template void serialize(Archive &ar, Slic3r::HealedExPolygons &o) { ar(o.expolygons, o.is_healed); } +}; // namespace cereal + +#endif // slic3r_EmbossShape_hpp_ diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 77222dc..5a6556b 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -185,6 +185,15 @@ bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2) return false; } +bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly) +{ + for (const ExPolygon& el : expolys) { + if (el.overlaps(expoly)) + return true; + } + return false; +} + Point projection_onto(const ExPolygons& polygons, const Point& from) { Point projected_pt; @@ -475,6 +484,21 @@ bool has_duplicate_points(const ExPolygons &expolys) #endif } +bool remove_same_neighbor(ExPolygons &expolygons) +{ + if (expolygons.empty()) return false; + bool remove_from_holes = false; + bool remove_from_contour = false; + for (ExPolygon &expoly : expolygons) { + remove_from_contour |= remove_same_neighbor(expoly.contour); + remove_from_holes |= remove_same_neighbor(expoly.holes); + } + // Removing of expolygons without contour + if (remove_from_contour) + expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const ExPolygon &p) { return p.contour.points.size() <= 2; }), expolygons.end()); + return remove_from_holes || remove_from_contour; +} + bool remove_sticks(ExPolygon &poly) { return remove_sticks(poly.contour) || remove_sticks(poly.holes); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index d213d99..12d5db1 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -372,15 +372,20 @@ inline Points to_points(const ExPolygon &expoly) return out; } -inline void polygons_append(Polygons &dst, const ExPolygon &src) -{ +inline void translate(ExPolygons &expolys, const Point &p) +{ + for (ExPolygon &expoly : expolys) expoly.translate(p); +} + +inline void polygons_append(Polygons &dst, const ExPolygon &src) +{ dst.reserve(dst.size() + src.holes.size() + 1); dst.push_back(src.contour); dst.insert(dst.end(), src.holes.begin(), src.holes.end()); } -inline void polygons_append(Polygons &dst, const ExPolygons &src) -{ +inline void polygons_append(Polygons &dst, const ExPolygons &src) +{ dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) { dst.push_back(it->contour); @@ -452,6 +457,7 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc bool expolygons_match(const ExPolygon &l, const ExPolygon &r); bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2); +bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly); Point projection_onto(const ExPolygons& expolys, const Point& pt); @@ -464,6 +470,8 @@ std::vector get_extents_vector(const ExPolygons &polygons); // Test for duplicate points. The points are copied, sorted and checked for duplicates globally. bool has_duplicate_points(const ExPolygon &expoly); bool has_duplicate_points(const ExPolygons &expolys); +// Return True when erase some otherwise False. +bool remove_same_neighbor(ExPolygons &expolys); bool remove_sticks(ExPolygon &poly); void keep_largest_contour_only(ExPolygons &polygons); diff --git a/src/libslic3r/ExPolygonSerialize.hpp b/src/libslic3r/ExPolygonSerialize.hpp new file mode 100644 index 0000000..3cd760b --- /dev/null +++ b/src/libslic3r/ExPolygonSerialize.hpp @@ -0,0 +1,28 @@ +#ifndef slic3r_ExPolygonSerialize_hpp_ +#define slic3r_ExPolygonSerialize_hpp_ + +#include "ExPolygon.hpp" +#include "Point.hpp" // Cereal serialization of Point +#include +#include + +/// +/// External Cereal serialization of ExPolygons +/// + +// Serialization through the Cereal library +#include +namespace cereal { + +template +void serialize(Archive &archive, Slic3r::Polygon &polygon) { + archive(polygon.points); +} + +template +void serialize(Archive &archive, Slic3r::ExPolygon &expoly) { + archive(expoly.contour, expoly.holes); +} + +} // namespace Slic3r +#endif // slic3r_ExPolygonSerialize_hpp_ diff --git a/src/libslic3r/ExPolygonsIndex.cpp b/src/libslic3r/ExPolygonsIndex.cpp new file mode 100644 index 0000000..f083eff --- /dev/null +++ b/src/libslic3r/ExPolygonsIndex.cpp @@ -0,0 +1,82 @@ +#include "ExPolygonsIndex.hpp" +using namespace Slic3r; + +// IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound +ExPolygonsIndices::ExPolygonsIndices(const ExPolygons &shapes) +{ + // prepare offsets + m_offsets.reserve(shapes.size()); + uint32_t offset = 0; + for (const ExPolygon &shape : shapes) { + assert(!shape.contour.points.empty()); + std::vector shape_offsets; + shape_offsets.reserve(shape.holes.size() + 1); + shape_offsets.push_back(offset); + offset += shape.contour.points.size(); + for (const Polygon &hole: shape.holes) { + shape_offsets.push_back(offset); + offset += hole.points.size(); + } + m_offsets.push_back(std::move(shape_offsets)); + } + m_count = offset; +} + +uint32_t ExPolygonsIndices::cvt(const ExPolygonsIndex &id) const +{ + assert(id.expolygons_index < m_offsets.size()); + const std::vector &shape_offset = m_offsets[id.expolygons_index]; + assert(id.polygon_index < shape_offset.size()); + uint32_t res = shape_offset[id.polygon_index] + id.point_index; + assert(res < m_count); + return res; +} + +ExPolygonsIndex ExPolygonsIndices::cvt(uint32_t index) const +{ + assert(index < m_count); + ExPolygonsIndex result{0, 0, 0}; + // find expolygon index + auto fn = [](const std::vector &offsets, uint32_t index) { return offsets[0] < index; }; + auto it = std::lower_bound(m_offsets.begin() + 1, m_offsets.end(), index, fn); + result.expolygons_index = it - m_offsets.begin(); + if (it == m_offsets.end() || it->at(0) != index) --result.expolygons_index; + + // find polygon index + const std::vector &shape_offset = m_offsets[result.expolygons_index]; + auto it2 = std::lower_bound(shape_offset.begin() + 1, shape_offset.end(), index); + result.polygon_index = it2 - shape_offset.begin(); + if (it2 == shape_offset.end() || *it2 != index) --result.polygon_index; + + // calculate point index + uint32_t polygon_offset = shape_offset[result.polygon_index]; + assert(index >= polygon_offset); + result.point_index = index - polygon_offset; + return result; +} + +bool ExPolygonsIndices::is_last_point(const ExPolygonsIndex &id) const { + assert(id.expolygons_index < m_offsets.size()); + const std::vector &shape_offset = m_offsets[id.expolygons_index]; + assert(id.polygon_index < shape_offset.size()); + uint32_t index = shape_offset[id.polygon_index] + id.point_index; + assert(index < m_count); + // next index + uint32_t next_point_index = index + 1; + uint32_t next_poly_index = id.polygon_index + 1; + uint32_t next_expoly_index = id.expolygons_index + 1; + // is last expoly? + if (next_expoly_index == m_offsets.size()) { + // is last expoly last poly? + if (next_poly_index == shape_offset.size()) + return next_point_index == m_count; + } else { + // (not last expoly) is expoly last poly? + if (next_poly_index == shape_offset.size()) + return next_point_index == m_offsets[next_expoly_index][0]; + } + // Not last polygon in expolygon + return next_point_index == shape_offset[next_poly_index]; +} + +uint32_t ExPolygonsIndices::get_count() const { return m_count; } diff --git a/src/libslic3r/ExPolygonsIndex.hpp b/src/libslic3r/ExPolygonsIndex.hpp new file mode 100644 index 0000000..66e87b1 --- /dev/null +++ b/src/libslic3r/ExPolygonsIndex.hpp @@ -0,0 +1,74 @@ +#ifndef slic3r_ExPolygonsIndex_hpp_ +#define slic3r_ExPolygonsIndex_hpp_ + +#include "ExPolygon.hpp" +namespace Slic3r { + +/// +/// Index into ExPolygons +/// Identify expolygon, its contour (or hole) and point +/// +struct ExPolygonsIndex +{ + // index of ExPolygons + uint32_t expolygons_index; + + // index of Polygon + // 0 .. contour + // N .. hole[N-1] + uint32_t polygon_index; + + // index of point in polygon + uint32_t point_index; + + bool is_contour() const { return polygon_index == 0; } + bool is_hole() const { return polygon_index != 0; } + uint32_t hole_index() const { return polygon_index - 1; } +}; + +/// +/// Keep conversion from ExPolygonsIndex to Index and vice versa +/// ExPolygonsIndex .. contour(or hole) point from ExPolygons +/// Index .. continous number +/// +/// index is used to address lines and points as result from function +/// Slic3r::to_lines, Slic3r::to_points +/// +class ExPolygonsIndices +{ + std::vector> m_offsets; + // for check range of index + uint32_t m_count; // count of points +public: + ExPolygonsIndices(const ExPolygons &shapes); + + /// + /// Convert to one index number + /// + /// Compose of adress into expolygons + /// Index + uint32_t cvt(const ExPolygonsIndex &id) const; + + /// + /// Separate to multi index + /// + /// adress into expolygons + /// + ExPolygonsIndex cvt(uint32_t index) const; + + /// + /// Check whether id is last point in polygon + /// + /// Identify point in expolygon + /// True when id is last point in polygon otherwise false + bool is_last_point(const ExPolygonsIndex &id) const; + + /// + /// Count of points in expolygons + /// + /// Count of points in expolygons + uint32_t get_count() const; +}; + +} // namespace Slic3r +#endif // slic3r_ExPolygonsIndex_hpp_ diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index a659aaa..c679e7b 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -69,8 +69,7 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale //1.9.7.52 bool ExtrusionPath::can_merge(const ExtrusionPath& other) { - return overhang_degree == other.overhang_degree && - curve_degree==other.curve_degree && + return curve_degree==other.curve_degree && mm3_per_mm == other.mm3_per_mm && width == other.width && height == other.height && @@ -470,13 +469,32 @@ bool ExtrusionLoop::check_seam_point_angle(double angle_threshold, double min_ar idx_next = Slic3r::next_idx_modulo(idx_next, points.size()); } - // Calculate angle between idx_prev, idx_curr, idx_next. - const Point &p0 = points[idx_prev]; - const Point &p1 = points[idx_curr]; - const Point &p2 = points[idx_next]; - const auto a = angle(p0 - p1, p2 - p1); - if (a > 0 ? a < angle_threshold : a > -angle_threshold) { - return false; + //thanks orca + for (size_t _i = 0; _i < points.size(); ++_i) { + // pull idx_prev to current as much as possible, while respecting the min_arm_length + while (distance_to_prev - lengths[idx_prev] > min_arm_length) { + distance_to_prev -= lengths[idx_prev]; + idx_prev = Slic3r::next_idx_modulo(idx_prev, points.size()); + } + + // push idx_next forward as far as needed + while (distance_to_next < min_arm_length) { + distance_to_next += lengths[idx_next]; + idx_next = Slic3r::next_idx_modulo(idx_next, points.size()); + } + + // Calculate angle between idx_prev, idx_curr, idx_next. + const Point &p0 = points[idx_prev]; + const Point &p1 = points[idx_curr]; + const Point &p2 = points[idx_next]; + const auto a = angle(p0 - p1, p2 - p1); + if (a > 0 ? a < angle_threshold : a > -angle_threshold) { return false; } + + // increase idx_curr by one + float curr_distance = lengths[idx_curr]; + idx_curr++; + distance_to_prev += curr_distance; + distance_to_next -= curr_distance; } return true; @@ -553,7 +571,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped( ExtrusionPaths &original_paths, paths.emplace_back(std::move(flat_path), *path); remaining_length = 0; } else { - remaining_length -= path_len; + // QDS: protection for accuracy issues + remaining_length = remaining_length - path_len < EPSILON ? 0 : remaining_length - path_len; const double end_ratio = lerp(1.0, start_slope_ratio, remaining_length / slope_min_length); add_slop(*path, path->polyline, start_ratio, end_ratio); start_ratio = end_ratio; diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 6a543a8..1838839 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -43,12 +43,16 @@ enum ExtrusionRole : uint8_t { // Special flags describing loop enum ExtrusionLoopRole { - elrDefault, - elrContourInternalPerimeter, - elrSkirt, - elrPerimeterHole, + elrDefault = 1 << 0, + elrContourInternalPerimeter = 1 << 1, + elrSkirt = 1 << 2, + elrPerimeterHole = 1 << 3, + elrSecondPerimeter = 1 << 4 }; +inline ExtrusionLoopRole operator |(ExtrusionLoopRole a, ExtrusionLoopRole b) { + return static_cast(static_cast(a) | static_cast(b)); +} inline bool is_perimeter(ExtrusionRole role) { diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 609f61e..f9015f9 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -636,6 +636,7 @@ void Layer::make_ironing() double height; double speed; double angle; + double inset; bool operator<(const IroningParams &rhs) const { if (this->extruder < rhs.extruder) diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 1419841..6065b40 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -50,7 +50,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive::Filler(); case ipSupportCubic: return new FillAdaptive::Filler(); - case ipSupportBase: return new FillSupportBase(); + case ipSupportBase: return new FillSupportBase(); // simply line fill case ipLightning: return new FillLightning::Filler(); // QDS: for internal solid infill only case ipConcentricInternal: return new FillConcentricInternal(); diff --git a/src/libslic3r/Fill/FillCrossHatch.cpp b/src/libslic3r/Fill/FillCrossHatch.cpp index 3f3906a..850c50e 100644 --- a/src/libslic3r/Fill/FillCrossHatch.cpp +++ b/src/libslic3r/Fill/FillCrossHatch.cpp @@ -12,7 +12,7 @@ namespace Slic3r { // It introduces transform layers between direction shifts for better line cohesion, which fixes the weakness of line infill. // The transform technique is inspired by David Eccles, improved 3D honeycomb but we made a more flexible implementation. // This method notably increases printing speed, meeting the demands of modern high-speed 3D printers, and reduces noise for most layers. -// By QIDI Lab +// By QIDI Tech // graph credits: David Eccles (gringer). // But we made a different definition for points. diff --git a/src/libslic3r/FlushVolCalc.cpp b/src/libslic3r/FlushVolCalc.cpp index 12258bf..4705cb5 100644 --- a/src/libslic3r/FlushVolCalc.cpp +++ b/src/libslic3r/FlushVolCalc.cpp @@ -1,7 +1,7 @@ #include #include #include "slic3r/Utils/ColorSpaceConvert.hpp" - +#include "Utils.hpp" #include "FlushVolCalc.hpp" @@ -44,6 +44,58 @@ FlushVolCalculator::FlushVolCalculator(int min, int max, float multiplier) { } +int FlushVolCalculator::calc_flush_vol_rgb(unsigned char src_r, unsigned char src_g, unsigned char src_b, + unsigned char dst_r, unsigned char dst_g, unsigned char dst_b) +{ + auto& pd = FlushVolPredictor::get_instance(); + float ret_flush_volume = 0; + FlushPredict::RGBColor src(src_r, src_g, src_b); + FlushPredict::RGBColor dst(dst_r, dst_g, dst_b); + bool success = pd.predict(src, dst, ret_flush_volume); + // if we could find the color pair from dataset, we need to recalculate + if (!success) { + float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f; + float from_hsv_h, from_hsv_s, from_hsv_v; + float to_hsv_h, to_hsv_s, to_hsv_v; + + src_r_f = (float)src_r / 255.f; + src_g_f = (float)src_g / 255.f; + src_b_f = (float)src_b / 255.f; + dst_r_f = (float)dst_r / 255.f; + dst_g_f = (float)dst_g / 255.f; + dst_b_f = (float)dst_b / 255.f; + + // Calculate color distance in HSV color space + RGB2HSV(src_r_f, src_g_f,src_b_f, &from_hsv_h, &from_hsv_s, &from_hsv_v); + RGB2HSV(dst_r_f, dst_g_f, dst_b_f, &to_hsv_h, &to_hsv_s, &to_hsv_v); + float hs_dist = DeltaHS_QDS(from_hsv_h, from_hsv_s, from_hsv_v, to_hsv_h, to_hsv_s, to_hsv_v); + + // 1. Color difference is more obvious if the dest color has high luminance + // 2. Color difference is more obvious if the source color has low luminance + float from_lumi = get_luminance(src_r_f, src_g_f, src_b_f); + float to_lumi = get_luminance(dst_r_f, dst_g_f, dst_b_f); + float lumi_flush = 0.f; + if (to_lumi >= from_lumi) { + lumi_flush = std::pow(to_lumi - from_lumi, 0.7f) * 560.f; + } + else { + lumi_flush = (from_lumi - to_lumi) * 80.f; + + float inter_hsv_v = 0.67 * to_hsv_v + 0.33 * from_hsv_v; + hs_dist = std::min(inter_hsv_v, hs_dist); + } + float hs_flush = 230.f * hs_dist; + + float flush_volume = calc_triangle_3rd_edge(hs_flush, lumi_flush, 120.f); + flush_volume = std::max(flush_volume, 60.f); + + ret_flush_volume = flush_volume; + } + + return ret_flush_volume; +} + + int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, unsigned char src_g, unsigned char src_b, unsigned char dst_a, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b) { @@ -55,42 +107,8 @@ int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, dst_r = dst_g = dst_b = 255; } - float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f; - float from_hsv_h, from_hsv_s, from_hsv_v; - float to_hsv_h, to_hsv_s, to_hsv_v; + float flush_volume = calc_flush_vol_rgb(src_r, src_g, src_b, dst_r, dst_g, dst_b); - src_r_f = (float)src_r / 255.f; - src_g_f = (float)src_g / 255.f; - src_b_f = (float)src_b / 255.f; - dst_r_f = (float)dst_r / 255.f; - dst_g_f = (float)dst_g / 255.f; - dst_b_f = (float)dst_b / 255.f; - - // Calculate color distance in HSV color space - RGB2HSV(src_r_f, src_g_f,src_b_f, &from_hsv_h, &from_hsv_s, &from_hsv_v); - RGB2HSV(dst_r_f, dst_g_f, dst_b_f, &to_hsv_h, &to_hsv_s, &to_hsv_v); - float hs_dist = DeltaHS_QDS(from_hsv_h, from_hsv_s, from_hsv_v, to_hsv_h, to_hsv_s, to_hsv_v); - - // 1. Color difference is more obvious if the dest color has high luminance - // 2. Color difference is more obvious if the source color has low luminance - float from_lumi = get_luminance(src_r_f, src_g_f, src_b_f); - float to_lumi = get_luminance(dst_r_f, dst_g_f, dst_b_f); - float lumi_flush = 0.f; - if (to_lumi >= from_lumi) { - lumi_flush = std::pow(to_lumi - from_lumi, 0.7f) * 560.f; - } - else { - lumi_flush = (from_lumi - to_lumi) * 80.f; - - float inter_hsv_v = 0.67 * to_hsv_v + 0.33 * from_hsv_v; - hs_dist = std::min(inter_hsv_v, hs_dist); - } - float hs_flush = 230.f * hs_dist; - - float flush_volume = calc_triangle_3rd_edge(hs_flush, lumi_flush, 120.f); - flush_volume = std::max(flush_volume, 60.f); - - //float flush_multiplier = std::atof(m_flush_multiplier_ebox->GetValue().c_str()); flush_volume += m_min_flush_vol; return std::min((int)flush_volume, m_max_flush_vol); } diff --git a/src/libslic3r/FlushVolCalc.hpp b/src/libslic3r/FlushVolCalc.hpp index 630b11c..ed9da60 100644 --- a/src/libslic3r/FlushVolCalc.hpp +++ b/src/libslic3r/FlushVolCalc.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "Config.hpp" +#include "FlushVolPredictor.hpp" namespace Slic3r { @@ -22,6 +23,10 @@ public: int calc_flush_vol(unsigned char src_a, unsigned char src_r, unsigned char src_g, unsigned char src_b, unsigned char dst_a, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b); + + int calc_flush_vol_rgb(unsigned char src_r,unsigned char src_g,unsigned char src_b, + unsigned char dst_r, unsigned char dst_g, unsigned char dst_b); + private: int m_min_flush_vol; int m_max_flush_vol; diff --git a/src/libslic3r/FlushVolPredictor.cpp b/src/libslic3r/FlushVolPredictor.cpp new file mode 100644 index 0000000..d980554 --- /dev/null +++ b/src/libslic3r/FlushVolPredictor.cpp @@ -0,0 +1,292 @@ +#include "FlushVolPredictor.hpp" +#include "Utils.hpp" +#include +#include +#include +#include + +namespace FlushPredict +{ + static double rad_to_deg(double rad) { + return 180.0 / M_PI * rad; + } + + static double deg_to_rad(double deg) { + return deg * M_PI / 180.0; + } + + LABColor RGB2LAB(const RGBColor& color) { + using XYZColor = std::tuple; + auto gamma = [](double x) { + if (x > 0.04045) + return pow((x + 0.055) / 1.055, 2.4); + else + return x / 12.92; + }; + auto RGB2XYZ = [gamma](const RGBColor& color)->XYZColor { + double R = gamma(static_cast(color.r) / 255.0) * 100; + double G = gamma(static_cast(color.g) / 255.0) * 100; + double B = gamma(static_cast(color.b) / 255.0) * 100; + + double x = 0.412453 * R + 0.357580 * G + 0.180423 * B; + double y = 0.212671 * R + 0.715160 * G + 0.072169 * B; + double z = 0.019334 * R + 0.119193 * G + 0.950227 * B; + return { x,y,z }; + }; + + static const double XN = 95.0489; + static const double YN = 100; + static const double ZN = 108.8840; + + auto f = [](double t) { + static const double threshold = 0.008856f; + if (t > threshold) + return pow(t, 1.0 / 3.0); + else + return 7.787 * t + 0.137931; + }; + + auto xyz_color = RGB2XYZ(color); + double x = std::get<0>(xyz_color); + double y = std::get<1>(xyz_color); + double z = std::get<2>(xyz_color); + double xn = f(x / XN); + double yn = f(y / YN); + double zn = f(z / ZN); + + double L = 116.0 * yn - 16.0; + double A = 500.0 * (xn - yn); + double B = 200.0 * (yn - zn); + return LABColor(L, A, B); + } + + float calc_color_distance(const LABColor& lab1, const LABColor& lab2) + { + static const double pow_25_to_7 = pow(25, 7); + + const double C1 = sqrt(lab1.a * lab1.a + lab1.b * lab1.b); + const double C2 = sqrt(lab2.a * lab2.a + lab2.b * lab2.b); + const double CMean = (C1 + C2) / 2.0; + const double pow_CMean_to_7 = pow(CMean, 7); + const double G = 0.5 * (1 - sqrt(pow_CMean_to_7 / (pow_CMean_to_7 + pow_25_to_7))); + + const double p_l1 = lab1.l; + const double p_l2 = lab2.l; + const double p_a1 = (1. + G) * lab1.a; + const double p_a2 = (1. + G) * lab2.a; + const double p_b1 = lab1.b; + const double p_b2 = lab2.b; + const double p_c1 = sqrt(p_a1 * p_a1 + p_b1 * p_b1); + const double p_c2 = sqrt(p_a2 * p_a2 + p_b2 * p_b2); + double p_h1; + if (p_a1 == 0 && p_b1 == 0) + p_h1 = 0; + else { + p_h1 = atan2(p_b1, p_a1); + if (p_h1 < 0) + p_h1 += M_PI * 2; + } + double p_h2; + if (p_a2 == 0 && p_b2 == 0) + p_h2 = 0; + else { + p_h2 = atan2(p_b2, p_a2); + if (p_h2 < 0) + p_h2 += M_PI * 2; + } + + const double delta_L = p_l2 - p_l1; + const double delta_C = p_c2 - p_c1; + + double delta_H; + const double p_c_multi = p_c1 * p_c2; + if (p_c_multi == 0) + delta_H = 0; + else { + delta_H = p_h2 - p_h1; + if (delta_H < -M_PI) + delta_H += 2 * M_PI; + else if (delta_H > M_PI) + delta_H -= 2 * M_PI; + delta_H = 2 * sqrt(p_c_multi) * sin(delta_H / 2.); + } + + + double p_L_mean = (p_l1 + p_l2) / 2.0; + double p_C_mean = (p_c1 + p_c2) / 2.0; + + double p_H_mean, p_H_sum = p_h1 + p_h2; + if (p_c1 * p_c2 == 0) { + p_H_mean = p_H_sum; + } + else { + if (fabs(p_h1 - p_h2) <= M_PI) + p_H_mean = p_H_sum / 2; + else { + if (p_H_sum < 2 * M_PI) + p_H_mean = (p_H_sum + 2 * M_PI) / 2.0; + else + p_H_mean = (p_H_sum - 2 * M_PI) / 2.0; + } + } + + const double T = 1 - 0.17 * cos(p_H_mean - deg_to_rad(30)) + 0.24 * cos(2 * p_H_mean) + 0.32 * cos(3 * p_H_mean + deg_to_rad(6)) - 0.2 * cos(4 * p_H_mean - deg_to_rad(63)); + const double dtheta = deg_to_rad(30) * exp(-pow((p_H_mean - deg_to_rad(275)) / deg_to_rad(25), 2)); + + const double pow_p_cmean_to_7 = pow(p_C_mean, 7); + const double R_C = 2 * sqrt(pow_p_cmean_to_7 / (pow_p_cmean_to_7 + pow_25_to_7)); + + const double pow_p_lmean_to_2 = pow(p_L_mean - 50, 2); + const double S_L = 1 + (0.015 * pow_p_lmean_to_2) / sqrt(20 + pow_p_lmean_to_2); + const double S_C = 1 + 0.045 * p_C_mean; + const double S_H = 1 + 0.015 * p_C_mean * T; + const double R_T = -sin(2 * dtheta) * R_C; + + const double K_L = 1.0, K_C = 1.0, K_H = 1.0; + + double de = sqrt( + pow(delta_L / (K_L * S_L), 2) + pow(delta_C / (K_C * S_C), 2) + pow(delta_H / (K_H * S_H), 2) + (R_T * (delta_C / (K_C * S_C)) * (delta_H / (K_H * S_H))) + ); + return de; + } + + float calc_color_distance(const RGBColor& color1, const RGBColor& color2) { + LABColor lab1 = RGB2LAB(color1); + LABColor lab2 = RGB2LAB(color2); + return calc_color_distance(lab1, lab2); + } + + bool is_similar_color(const RGBColor& from, const RGBColor& to, float distance_threshold) + { + float color_distance = calc_color_distance(from, to); + if (color_distance > distance_threshold) + return false; + return true; + } + +} + + +uint64_t FlushVolPredictor::generate_hash_key(const RGB& from, const RGB& to) +{ + uint64_t key = 0; + key |= (static_cast(from.r) << 40); + key |= (static_cast(from.g) << 32); + key |= (static_cast(from.b) << 24); + key |= (static_cast(to.r) << 16); + key |= (static_cast(to.g) << 8); + key |= static_cast(to.b); + return key; +} + +FlushVolPredictor::FlushVolPredictor(const std::string& data_file) +{ + auto rgb_hex_to_dec = [](const std::string& hexstr, FlushPredict::RGBColor& color)->bool + { + if (hexstr.empty() || hexstr.length() != 7 || hexstr[0] != '#') + { + assert(false); + color.r = 0, color.g = 0, color.b = 0; + return false; + } + + auto hexToByte = [](const std::string& hex)->int + { + unsigned int byte; + std::istringstream(hex) >> std::hex >> byte; + return byte; + }; + color.r = hexToByte(hexstr.substr(1, 2)); + color.g = hexToByte(hexstr.substr(3, 2)); + color.b = hexToByte(hexstr.substr(5, 2)); + return true; + }; + + std::ifstream in(data_file); + if (!in.is_open()) { + m_valid = false; + return; + } + std::string line; + std::getline(in, line); //skip color description line + std::getline(in, line); + // read and save color lists + { + std::istringstream in(line); + std::string color; + while (in >> color) { + RGB c; + if (!rgb_hex_to_dec(color, c)) { + m_valid = false; + return; + } + m_colors.emplace_back(c); + } + } + std::getline(in, line); // skip colume name line + while (std::getline(in, line)) { + std::istringstream iss(line); + std::string rgb_from, rgb_to; + float value; + if (iss >> rgb_from >> rgb_to >> value) { + RGB from,to; + // transfer hex str to rgb format + if (!rgb_hex_to_dec(rgb_from, from)) { + m_valid = false; + return; + } + if (!rgb_hex_to_dec(rgb_to, to)) { + m_valid = false; + return; + } + // generate hash key for two rgb color + uint64_t key = generate_hash_key(from,to); + m_flush_map.emplace(key, value); + } + else { + m_valid = false; + return; + } + } + m_valid = true; +} + +bool FlushVolPredictor::predict(const RGB& from, const RGB& to, float& flush) +{ + if (!m_valid) + return false; + + // find similar colors in color list + std::optional similar_from, similar_to; + for (auto& color : m_colors) { + if (FlushPredict::is_similar_color(color, from)) { + similar_from = color; + break; + } + } + for (auto& color : m_colors) { + if (FlushPredict::is_similar_color(color, to)) { + similar_to = color; + break; + } + } + + // `from` and `to` should have similar colors in list + if (!similar_from || !similar_to) + return false; + + uint64_t key = generate_hash_key(*similar_from,*similar_to); + auto iter = m_flush_map.find(key); + if (iter == m_flush_map.end()) + return false; + + flush = iter->second; + return true; +} + +FlushVolPredictor& FlushVolPredictor::get_instance() +{ + static std::string prefix = Slic3r::resources_dir(); + static FlushVolPredictor instance(prefix + "/flush/flush_data.txt"); + return instance; +} diff --git a/src/libslic3r/FlushVolPredictor.hpp b/src/libslic3r/FlushVolPredictor.hpp new file mode 100644 index 0000000..378a608 --- /dev/null +++ b/src/libslic3r/FlushVolPredictor.hpp @@ -0,0 +1,61 @@ +#ifndef FLUSH_VOL_PREDICTOR_HPP +#define FLUSH_VOL_PREDICTOR_HPP + +#include +#include +#include +#include + +namespace FlushPredict +{ + struct RGBColor + { + unsigned char r{ 0 }; + unsigned char g{ 0 }; + unsigned char b{ 0 }; + RGBColor(unsigned char r_,unsigned char g_,unsigned char b_) :r(r_),g(g_),b(b_){} + RGBColor() = default; + }; + + struct LABColor + { + double l{ 0 }; + double a{ 0 }; + double b{ 0 }; + LABColor() = default; + LABColor(double l_,double a_,double b_):l(l_),a(a_),b(b_){} + }; + // transfer colour in RGB space to LAB space + LABColor RGB2LAB(const RGBColor& color); + // calculate DeltaE2000 + float calc_color_distance(const LABColor& lab1, const LABColor& lab2); + float calc_color_distance(const RGBColor& rgb1, const RGBColor& rgb2); + // check if DeltaE is within the threshold. We consider colors within the threshold to be the same + bool is_similar_color(const RGBColor& from, const RGBColor& to, float distance_threshold = 5.0); + +} + + +// Singleton pattern +class FlushVolPredictor +{ + using RGB = FlushPredict::RGBColor; +public: + bool predict(const RGB& from,const RGB& to , float& flush); + static FlushVolPredictor& get_instance(); +private: + FlushVolPredictor(const std::string& data_file); + FlushVolPredictor(const FlushVolPredictor&) = delete; + FlushVolPredictor& operator=(const FlushVolPredictor&) = delete; + ~FlushVolPredictor() = default; + + uint64_t generate_hash_key(const RGB& from, const RGB& to); +private: + std::unordered_map m_flush_map; + std::vector m_colors; + bool m_valid; +}; + + + +#endif \ No newline at end of file diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index abaae36..634d29d 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -8,6 +8,7 @@ #include #include +#include #ifdef _WIN32 #define DIR_SEPARATOR '\\' @@ -33,6 +34,11 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s message = _L("load_obj: failed to parse"); return false; } + + obj_info.ml_region = data.ml_region; + obj_info.ml_name = data.ml_name; + obj_info.ml_id = data.ml_id; + bool exist_mtl = false; if (data.mtllibs.size() > 0) { // read mtl for (auto mtl_name : data.mtllibs) { @@ -40,16 +46,20 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s continue; } exist_mtl = true; - bool mtl_name_is_path = false; - boost::filesystem::path mtl_abs_path(mtl_name); + bool mtl_name_is_path = false; + std::wstring wide_mtl_name = boost::locale::conv::to_utf(mtl_name, "UTF-8"); + if (boost::istarts_with(wide_mtl_name,"./")){ + boost::replace_first(wide_mtl_name, "./", ""); + } + boost::filesystem::path mtl_abs_path(wide_mtl_name); if (boost::filesystem::exists(mtl_abs_path)) { mtl_name_is_path = true; } boost::filesystem::path mtl_path; if (!mtl_name_is_path) { boost::filesystem::path full_path(path); - std::string dir = full_path.parent_path().string(); - auto mtl_file = dir + "/" + mtl_name; + auto dir = full_path.parent_path().wstring(); + auto mtl_file = dir + L"/" + wide_mtl_name; boost::filesystem::path temp_mtl_path(mtl_file); mtl_path = temp_mtl_path; } diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp index 9b618bd..7fccb6f 100644 --- a/src/libslic3r/Format/OBJ.hpp +++ b/src/libslic3r/Format/OBJ.hpp @@ -1,13 +1,15 @@ #ifndef slic3r_Format_OBJ_hpp_ #define slic3r_Format_OBJ_hpp_ #include "libslic3r/Color.hpp" +#include "objparser.hpp" #include namespace Slic3r { class TriangleMesh; class Model; class ModelObject; -typedef std::function &input_colors, bool is_single_color, std::vector &filament_ids, unsigned char &first_extruder_id)> ObjImportColorFn; +typedef std::function &input_colors, bool is_single_color, std::vector &filament_ids, unsigned char &first_extruder_id, + std::string& ml_region, std::string& ml_name, std::string& ml_id)> ObjImportColorFn; // Load an OBJ file into a provided model. struct ObjInfo { std::vector vertex_colors; @@ -19,6 +21,9 @@ struct ObjInfo { std::unordered_map uv_map_pngs; bool has_uv_png{false}; + std::string ml_region; + std::string ml_name; + std::string ml_id; }; extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_colors, std::string &message); extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr); diff --git a/src/libslic3r/Format/STEP.cpp b/src/libslic3r/Format/STEP.cpp index f3cdd18..810fd99 100644 --- a/src/libslic3r/Format/STEP.cpp +++ b/src/libslic3r/Format/STEP.cpp @@ -33,9 +33,8 @@ #include "TopExp_Explorer.hxx" #include "TopExp_Explorer.hxx" #include "BRep_Tool.hxx" - -const double STEP_TRANS_CHORD_ERROR = 0.003; -const double STEP_TRANS_ANGLE_RES = 0.5; +#include "BRepTools.hxx" +#include namespace Slic3r { @@ -166,13 +165,6 @@ int StepPreProcessor::preNum(const unsigned char byte) { return num; } -struct NamedSolid { - NamedSolid(const TopoDS_Shape& s, - const std::string& n) : solid{s}, name{n} {} - const TopoDS_Shape solid; - const std::string name; -}; - static void getNamedSolids(const TopLoc_Location& location, const std::string& prefix, unsigned int& id, const Handle(XCAFDoc_ShapeTool) shapeTool, const TDF_Label label, std::vector& namedSolids) { @@ -185,7 +177,7 @@ static void getNamedSolids(const TopLoc_Location& location, const std::string& p if (referredLabel.FindAttribute(TDataStd_Name::GetID(), shapeName)) name = TCollection_AsciiString(shapeName->Get()).ToCString(); - if (name == "") + if (name == "" || !StepPreProcessor::isUtf8(name)) name = std::to_string(id++); std::string fullName{name}; @@ -197,15 +189,19 @@ static void getNamedSolids(const TopLoc_Location& location, const std::string& p } } else { TopoDS_Shape shape; + TopExp_Explorer explorer; shapeTool->GetShape(referredLabel, shape); TopAbs_ShapeEnum shape_type = shape.ShapeType(); BRepBuilderAPI_Transform transform(shape, localLocation, Standard_True); + int i = 0; switch (shape_type) { case TopAbs_COMPOUND: - namedSolids.emplace_back(TopoDS::Compound(transform.Shape()), fullName); - break; case TopAbs_COMPSOLID: - namedSolids.emplace_back(TopoDS::CompSolid(transform.Shape()), fullName); + for (explorer.Init(transform.Shape(), TopAbs_SOLID); explorer.More(); explorer.Next()) { + i++; + const TopoDS_Shape& currentShape = explorer.Current(); + namedSolids.emplace_back(TopoDS::Solid(currentShape), fullName + "-SOLID-" + std::to_string(i)); + } break; case TopAbs_SOLID: namedSolids.emplace_back(TopoDS::Solid(transform.Shape()), fullName); @@ -216,7 +212,10 @@ static void getNamedSolids(const TopLoc_Location& location, const std::string& p } } -bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn) +bool load_step(const char *path, Model *model, bool& is_cancel, + double linear_defletion/*=0.003*/, + double angle_defletion/*= 0.5*/, + ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn, long& mesh_face_num) { bool cb_cancel = false; if (stepFn) { @@ -271,7 +270,7 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre stl.resize(namedSolids.size()); tbb::parallel_for(tbb::blocked_range(0, namedSolids.size()), [&](const tbb::blocked_range &range) { for (size_t i = range.begin(); i < range.end(); i++) { - BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true); + BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true); // QDS: calculate total number of the nodes and triangles int aNbNodes = 0; int aNbTriangles = 0; @@ -317,7 +316,7 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre } // QDS: copy triangles const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation(); - Standard_Integer anId[3]; + Standard_Integer anId[3] = {}; for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) { Poly_Triangle aTri = aTriangulation->Triangle(aTriIter); @@ -344,6 +343,14 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre } }); + if (mesh_face_num != -1) { + for (size_t i = 0; i < stl.size(); i++) { + // Test for overflow + mesh_face_num += stl[i].stats.number_of_facets; + } + return true; + } + ModelObject *new_object = model->add_object(); const char * last_slash = strrchr(path, DIR_SEPARATOR); new_object->name.assign((last_slash == nullptr) ? path : last_slash + 1); @@ -388,4 +395,104 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre return true; } +Step::Step(fs::path path, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn): + m_stepFn(stepFn), + m_utf8Fn(isUtf8Fn) +{ + m_path = path.string(); + m_app->NewDocument(TCollection_ExtendedString("BinXCAF"), m_doc); +} + +Step::Step(std::string path, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn) : + m_path(path), + m_stepFn(stepFn), + m_utf8Fn(isUtf8Fn) +{ + m_app->NewDocument(TCollection_ExtendedString("BinXCAF"), m_doc); +} + +bool Step::load() +{ + if (!StepPreProcessor::isUtf8File(m_path.c_str()) && m_utf8Fn) { + m_utf8Fn(false); + return false; + } + + STEPCAFControl_Reader reader; + reader.SetNameMode(true); + IFSelect_ReturnStatus stat = reader.ReadFile(m_path.c_str()); + if (stat != IFSelect_RetDone || !reader.Transfer(m_doc)) { + m_app->Close(m_doc); + return false; + } + m_shape_tool = XCAFDoc_DocumentTool::ShapeTool(m_doc->Main()); + TDF_LabelSequence topLevelShapes; + m_shape_tool->GetFreeShapes(topLevelShapes); + unsigned int id{ 1 }; + Standard_Integer topShapeLength = topLevelShapes.Length() + 1; + for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) { + getNamedSolids(TopLoc_Location{}, "", id, m_shape_tool, topLevelShapes.Value(iLabel), m_name_solids); + } + + return true; +} + +void Step::clean_mesh_data() +{ + for (const auto& name_solid : m_name_solids) { + BRepTools::Clean(name_solid.solid); + } +} + +unsigned int Step::get_triangle_num(double linear_defletion, double angle_defletion) +{ + unsigned int tri_num = 0; + Handle(StepProgressIncdicator) progress = new StepProgressIncdicator(m_stop_mesh); + clean_mesh_data(); + IMeshTools_Parameters param; + param.Deflection = linear_defletion; + param.Angle = angle_defletion; + param.InParallel = true; + for (int i = 0; i < m_name_solids.size(); ++i) { + BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, param, progress->Start()); + for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); + if (!aTriangulation.IsNull()) { + tri_num += aTriangulation->NbTriangles(); + } + } + if (m_stop_mesh.load()) { + return 0; + } + } + return tri_num; +} + +unsigned int Step::get_triangle_num_tbb(double linear_defletion, double angle_defletion) +{ + unsigned int tri_num = 0; + clean_mesh_data(); + tbb::parallel_for(tbb::blocked_range(0, m_name_solids.size()), + [&](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); i++) { + unsigned int solids_tri_num = 0; + BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, linear_defletion, false, angle_defletion, true); + for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); + if (!aTriangulation.IsNull()) { + solids_tri_num += aTriangulation->NbTriangles(); + } + } + m_name_solids[i].tri_face_cout = solids_tri_num; + } + + }); + for (int i = 0; i < m_name_solids.size(); ++i) { + tri_num += m_name_solids[i].tri_face_cout; + } + return tri_num; +} + }; // namespace Slic3r diff --git a/src/libslic3r/Format/STEP.hpp b/src/libslic3r/Format/STEP.hpp index 6f80c5c..0b86ad4 100644 --- a/src/libslic3r/Format/STEP.hpp +++ b/src/libslic3r/Format/STEP.hpp @@ -1,8 +1,19 @@ #ifndef slic3r_Format_STEP_hpp_ #define slic3r_Format_STEP_hpp_ +#include "XCAFDoc_DocumentTool.hxx" +#include "XCAFApp_Application.hxx" +#include "XCAFDoc_ShapeTool.hxx" +#include +#include +#include +#include + +namespace fs = boost::filesystem; + namespace Slic3r { +class Model; class TriangleMesh; class ModelObject; @@ -16,8 +27,24 @@ const int LOAD_STEP_STAGE_UNIT_NUM = 5; typedef std::function ImportStepProgressFn; typedef std::function StepIsUtf8Fn; +struct NamedSolid +{ + NamedSolid(const TopoDS_Shape& s, + const std::string& n) : solid{ s }, name{ n } { + } + const TopoDS_Shape solid; + const std::string name; + int tri_face_cout = 0; +}; + //QDS: Load an step file into a provided model. -extern bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn proFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr); +extern bool load_step(const char *path, Model *model, + bool& is_cancel, + double linear_defletion = 0.003, + double angle_defletion = 0.5, + ImportStepProgressFn proFn = nullptr, + StepIsUtf8Fn isUtf8Fn = nullptr, + long& mesh_face_num = *(new long(-1))); //QDS: Used to detect what kind of encoded type is used in name field of step // If is encoded in UTF8, the file don't need to be handled, then return the original path directly. @@ -36,14 +63,50 @@ class StepPreProcessor { public: bool preprocess(const char* path, std::string &output_path); static bool isUtf8File(const char* path); -private: static bool isUtf8(const std::string str); +private: static bool isGBK(const std::string str); static int preNum(const unsigned char byte); //QDS: default is UTF8 for most step file. EncodedType m_encode_type = EncodedType::UTF8; }; +class StepProgressIncdicator : public Message_ProgressIndicator +{ +public: + StepProgressIncdicator(std::atomic& stop_flag) : should_stop(stop_flag){} + + Standard_Boolean UserBreak() override { return should_stop.load(); } + + void Show(const Message_ProgressScope&, const Standard_Boolean) override { + std::cout << "Progress: " << GetPosition() << "%" << std::endl; + } +private: + std::atomic& should_stop; +}; + +class Step +{ +public: + Step(fs::path path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr); + Step(std::string path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr); + bool load(); + unsigned int get_triangle_num(double linear_defletion, double angle_defletion); + unsigned int get_triangle_num_tbb(double linear_defletion, double angle_defletion); + void clean_mesh_data(); + + std::atomic m_stop_mesh; +private: + std::string m_path; + ImportStepProgressFn m_stepFn; + StepIsUtf8Fn m_utf8Fn; + Handle(XCAFApp_Application) m_app = XCAFApp_Application::GetApplication(); + Handle(TDocStd_Document) m_doc; + Handle(XCAFDoc_ShapeTool) m_shape_tool; + std::vector m_name_solids; +}; + + }; // namespace Slic3r #endif /* slic3r_Format_STEP_hpp_ */ diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 82bf2b4..86ed6df 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -577,6 +577,8 @@ bool objparse(const char *path, ObjData &data) char buf[65536 * 2]; size_t len = 0; size_t lenPrev = 0; + size_t lineCount = 0; + while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) { len += lenPrev; size_t lastLine = 0; @@ -589,6 +591,13 @@ bool objparse(const char *path, ObjData &data) //FIXME check the return value and exit on error? // Will it break parsing of some obj files? obj_parseline(c, data); + + /*for ml*/ + if (lineCount == 0) { data.ml_region = parsemlinfo(c, "region:");} + if (lineCount == 1) { data.ml_name = parsemlinfo(c, "ml_name:"); } + if (lineCount == 2) { data.ml_id = parsemlinfo(c, "ml_file_id:");} + + ++lineCount; lastLine = i + 1; } lenPrev = len - lastLine; @@ -607,6 +616,27 @@ bool objparse(const char *path, ObjData &data) return true; } +std::string parsemlinfo(const char* input, const char* condition) { + const char* regionPtr = std::strstr(input, condition); + + std::string regionContent = ""; + + if (regionPtr != nullptr) { + regionPtr += std::strlen(condition); + + while (*regionPtr == ' ' || *regionPtr == '\t') { + ++regionPtr; + } + + const char* endPtr = std::strchr(regionPtr, '\n'); + size_t length = (endPtr != nullptr) ? (endPtr - regionPtr) : std::strlen(regionPtr); + + regionContent = std::string(regionPtr, length); + } + + return regionContent; +} + bool mtlparse(const char *path, MtlData &data) { Slic3r::CNumericLocalesSetter locales_setter; @@ -664,6 +694,14 @@ bool objparse(std::istream &stream, ObjData &data) while (*c == ' ' || *c == '\t') ++ c; obj_parseline(c, data); + + /*for ml*/ + if (lastLine < 3) { + data.ml_region = parsemlinfo(c, "region"); + data.ml_name = parsemlinfo(c, "ml_name"); + data.ml_id = parsemlinfo(c, "ml_file_id"); + } + lastLine = i + 1; } lenPrev = len - lastLine; diff --git a/src/libslic3r/Format/objparser.hpp b/src/libslic3r/Format/objparser.hpp index 48493de..8116c08 100644 --- a/src/libslic3r/Format/objparser.hpp +++ b/src/libslic3r/Format/objparser.hpp @@ -115,6 +115,10 @@ struct ObjData { // List of faces, delimited by an ObjVertex with all members set to -1. std::vector vertices; + + std::string ml_region; + std::string ml_name; + std::string ml_id; }; struct MtlData @@ -127,6 +131,8 @@ extern bool objparse(const char *path, ObjData &data); extern bool mtlparse(const char *path, MtlData &data); extern bool objparse(std::istream &stream, ObjData &data); +extern std::string parsemlinfo(const char* input, const char* condition); + extern bool objbinsave(const char *path, const ObjData &data); extern bool objbinload(const char *path, ObjData &data); diff --git a/src/libslic3r/Format/qds_3mf.cpp b/src/libslic3r/Format/qds_3mf.cpp index 09ff042..09c3272 100644 --- a/src/libslic3r/Format/qds_3mf.cpp +++ b/src/libslic3r/Format/qds_3mf.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include #include #include #include @@ -46,6 +48,11 @@ namespace pt = boost::property_tree; #include #include "miniz_extension.hpp" #include "nlohmann/json.hpp" + +#include "EmbossShape.hpp" +#include "ExPolygonSerialize.hpp" +#include "NSVGUtils.hpp" + #include // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, @@ -130,6 +137,9 @@ const std::string QDT_APPLICATION_TAG = "Application"; const std::string QDT_MAKERLAB_TAG = "MakerLab"; const std::string QDT_MAKERLAB_VERSION_TAG = "MakerLabVersion"; +const std::string QDT_MAKERLAB_NAME = "MakerLab"; +const std::string QDT_MAKERLAB_REGION = "MakerLabRegion"; +const std::string QDT_MAKERLAB_ID = "MakerLabFileId"; const std::string QDT_PROFILE_TITLE_TAG = "ProfileTitle"; const std::string QDT_PROFILE_COVER_TAG = "ProfileCover"; @@ -163,6 +173,7 @@ const std::string QDS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.co const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config"; const std::string QDS_LAYER_HEIGHTS_PROFILE_FILE = "Metadata/layer_heights_profile.txt"; const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/layer_config_ranges.xml"; +const std::string BRIM_EAR_POINTS_FILE = "Metadata/brim_ear_points.txt"; /*const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";*/ const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/custom_gcode_per_layer.xml"; @@ -332,6 +343,40 @@ static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed"; static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; +// Store / load of TextConfiguration +static constexpr const char *TEXT_DATA_ATTR = "text"; +// TextConfiguration::EmbossStyle +static constexpr const char *STYLE_NAME_ATTR = "style_name"; +static constexpr const char *FONT_DESCRIPTOR_ATTR = "font_descriptor"; +static constexpr const char *FONT_DESCRIPTOR_TYPE_ATTR = "font_descriptor_type"; + +// TextConfiguration::FontProperty +static constexpr const char *CHAR_GAP_ATTR = "char_gap"; +static constexpr const char *LINE_GAP_ATTR = "line_gap"; +static constexpr const char *LINE_HEIGHT_ATTR = "line_height"; +static constexpr const char *BOLDNESS_ATTR = "boldness"; +static constexpr const char *SKEW_ATTR = "skew"; +static constexpr const char *PER_GLYPH_ATTR = "per_glyph"; +static constexpr const char *HORIZONTAL_ALIGN_ATTR = "horizontal"; +static constexpr const char *VERTICAL_ALIGN_ATTR = "vertical"; +static constexpr const char *COLLECTION_NUMBER_ATTR = "collection"; + +static constexpr const char *FONT_FAMILY_ATTR = "family"; +static constexpr const char *FONT_FACE_NAME_ATTR = "face_name"; +static constexpr const char *FONT_STYLE_ATTR = "style"; +static constexpr const char *FONT_WEIGHT_ATTR = "weight"; + +// Store / load of EmbossShape +static constexpr const char *SHAPE_TAG = "slic3rpe:shape"; +static constexpr const char *SHAPE_SCALE_ATTR = "scale"; +static constexpr const char *UNHEALED_ATTR = "unhealed"; +static constexpr const char *SVG_FILE_PATH_ATTR = "filepath"; +static constexpr const char *SVG_FILE_PATH_IN_3MF_ATTR = "filepath3mf"; + +// EmbossProjection +static constexpr const char *DEPTH_ATTR = "depth"; +static constexpr const char *USE_SURFACE_ATTR = "use_surface"; +// static constexpr const char *FIX_TRANSFORMATION_ATTR = "transform"; const unsigned int QDS_VALID_OBJECT_TYPES_COUNT = 2; const char* QDS_VALID_OBJECT_TYPES[] = @@ -719,7 +764,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) RepairedMeshErrors mesh_stats; ModelVolumeType part_type; TextInfo text_info; - + std::optional shape_configuration; VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id, ModelVolumeType type = ModelVolumeType::MODEL_PART) : first_triangle_id(first_triangle_id) , last_triangle_id(last_triangle_id) @@ -770,8 +815,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; typedef std::map IdToLayerConfigRangesMap; + typedef std::map IdToBrimPointsMap; /*typedef std::map> IdToSlaSupportPointsMap; typedef std::map> IdToSlaDrainHolesMap;*/ + using PathToEmbossShapeFileMap = std::map>; struct ObjectImporter { @@ -836,7 +883,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool extract_object_model() { mz_zip_archive archive; - mz_zip_archive_file_stat stat; + //mz_zip_archive_file_stat stat; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, zip_path)) { @@ -964,8 +1011,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) IdToCutObjectInfoMap m_cut_object_infos; IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerConfigRangesMap m_layer_config_ranges; + IdToBrimPointsMap m_brim_ear_points; /*IdToSlaSupportPointsMap m_sla_support_points; IdToSlaDrainHolesMap m_sla_drain_holes;*/ + PathToEmbossShapeFileMap m_path_to_emboss_shape_files; std::string m_curr_metadata_name; std::string m_curr_characters; std::string m_name; @@ -1019,6 +1068,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // add backup & restore logic bool _load_model_from_file(std::string filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Import3mfProgressFn proFn = nullptr, QDTProject* project = nullptr, int plate_id = 0); + bool _is_svg_shape_file(const std::string &filename) const; bool _extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function, bool restore = false); bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); @@ -1028,6 +1078,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_brim_ear_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); @@ -1039,6 +1090,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) void _extract_auxiliary_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); void _extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat); // handlers to parse the .model file void _handle_start_model_xml_element(const char* name, const char** attributes); @@ -1094,6 +1146,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_metadata(); + bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes); + bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Transform3d& transform); @@ -1225,6 +1279,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) m_objects_metadata.clear(); m_layer_heights_profiles.clear(); m_layer_config_ranges.clear(); + m_brim_ear_points.clear(); //m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); @@ -1720,6 +1775,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // extract slic3r layer config ranges file _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); } + else if (boost::algorithm::iequals(name, BRIM_EAR_POINTS_FILE)) { + // extract slic3r config file + _extract_brim_ear_points_from_archive(archive, stat); + } //QDS: disable SLA related files currently /*else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file @@ -1769,6 +1828,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) add_error("Archive does not contain a valid model config"); return false; } + } else if (_is_svg_shape_file(name)) { + _extract_embossed_svg_shape_file(name, archive, stat); } else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) { m_parsing_slice_info = true; @@ -1900,6 +1961,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (obj_layer_config_ranges != m_layer_config_ranges.end()) model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second); + IdToBrimPointsMap::iterator obj_brim_points = m_brim_ear_points.find(object.second + 1); + if (obj_brim_points != m_brim_ear_points.end()) + model_object->brim_points = std::move(obj_brim_points->second); + // m_sla_support_points are indexed by a 1 based model object index. /*IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { @@ -2167,6 +2232,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + bool _QDS_3MF_Importer::_is_svg_shape_file(const std::string &name) const { + return boost::starts_with(name, MODEL_FOLDER) && boost::ends_with(name, ".svg"); + } + bool _QDS_3MF_Importer::_extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function extract, bool restore) { mz_uint num_entries = mz_zip_reader_get_num_files(&archive); @@ -2710,6 +2779,78 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } } } + + + void _QDS_3MF_Importer::_extract_brim_ear_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading brim ear points data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + // Info on format versioning - see qds_3mf.hpp + int version = 0; + std::string key("brim_points_format_version="); + if (!objects.empty() && objects[0].find(key) != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + + for (const std::string& object : objects) { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + + if (object_data.size() != 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToBrimPointsMap::iterator object_item = m_brim_ear_points.find(object_id); + if (object_item != m_brim_ear_points.end()) { + add_error("Found duplicated brim ear points"); + continue; + } + + std::vector object_data_points; + boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); + + std::vector brim_ear_points; + if (version == 0) { + for (unsigned int i=0; i(stat.m_uncomp_size, '\0'); + mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void *) file->data(), stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading svg shape for emboss"); + return; + } + + // store for case svg is loaded before volume + m_path_to_emboss_shape_files[filename] = std::move(file); + + // find embossed volume, for case svg is loaded after volume + for (const ModelObject *object : m_model->objects) + for (ModelVolume *volume : object->volumes) { + std::optional &es = volume->emboss_shape; + if (!es.has_value()) continue; + std::optional &svg = es->svg_file; + if (!svg.has_value()) continue; + if (filename.compare(svg->path_in_3mf) == 0) svg->file_data = m_path_to_emboss_shape_files[filename]; + } + } + void _QDS_3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat) { //QDS: add plate tree related logic @@ -3088,7 +3253,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) res = _handle_start_assemble_item(attributes, num_attributes); else if (::strcmp(TEXT_INFO_TAG, name) == 0) res = _handle_start_text_info_item(attributes, num_attributes); - + else if (::strcmp(SHAPE_TAG, name) == 0) + res = _handle_start_shape_configuration(attributes, num_attributes); if (!res) _stop_xml_parser(); } @@ -3641,6 +3807,40 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + // Definition of read/write method for EmbossShape + static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive); + static std::optional read_emboss_shape(const char **attributes, unsigned int num_attributes); + + bool _QDS_3MF_Importer::_handle_start_shape_configuration(const char **attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Can not assign volume mesh to a valid object"); + return false; + } + auto &volumes = object->second.volumes; + if (volumes.empty()) { + add_error("Can not assign mesh to a valid volume"); + return false; + } + ObjectMetadata::VolumeMetadata &volume = volumes.back(); + volume.shape_configuration = read_emboss_shape(attributes, num_attributes); + if (!volume.shape_configuration.has_value()) return false; + + // Fill svg file content into shape_configuration + std::optional &svg = volume.shape_configuration->svg_file; + if (!svg.has_value()) return true; // do not contain svg file + + const std::string &path = svg->path_in_3mf; + if (path.empty()) return true; // do not contain svg file + + auto it = m_path_to_emboss_shape_files.find(path); + if (it == m_path_to_emboss_shape_files.end()) return true; // svg file is not loaded yet + + svg->file_data = it->second; + return true; + } + bool _QDS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; @@ -4205,7 +4405,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } ObjectMetadata::VolumeMetadata &volume = object->second.volumes[m_curr_config.volume_id]; - TextInfo text_info; text_info.m_text = xml_unescape(qds_get_attribute_value_string(attributes, num_attributes, TEXT_ATTR)); text_info.m_font_name = qds_get_attribute_value_string(attributes, num_attributes, FONT_NAME_ATTR); @@ -4293,9 +4492,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } else if (boost::starts_with(type, "http://schemas.openxmlformats.org/") && boost::ends_with(type, "thumbnail")) { if (boost::algorithm::ends_with(path, ".png")) m_thumbnail_path = path; - } else if (boost::starts_with(type, "http://schemas.qidilab.com/") && boost::ends_with(type, "cover-thumbnail-middle")) { + } else if (boost::starts_with(type, "http://schemas.qiditech.com/") && boost::ends_with(type, "cover-thumbnail-middle")) { m_thumbnail_middle = path; - } else if (boost::starts_with(type, "http://schemas.qidilab.com/") && boost::ends_with(type, "cover-thumbnail-small")) { + } else if (boost::starts_with(type, "http://schemas.qiditech.com/") && boost::ends_with(type, "cover-thumbnail-small")) { m_thumbnail_small = path; } return true; @@ -4510,7 +4709,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } volume->set_type(volume_data->part_type); - + if (auto &es = volume_data->shape_configuration; es.has_value()) + volume->emboss_shape = std::move(es); if (!volume_data->text_info.m_text.empty()) volume->set_text_info(volume_data->text_info); @@ -5279,7 +5479,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool save_model_to_file(StoreParams& store_params); // add backup logic bool save_object_mesh(const std::string& temp_path, ModelObject const & object, int obj_id); - + static void add_transformation(std::stringstream &stream, const Transform3d &tr); private: //QDS: add plate data related logic bool _save_model_to_file(const std::string& filename, @@ -5317,6 +5517,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const; bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_brim_ear_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); @@ -5725,6 +5926,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return false; } + if (!_add_brim_ear_points_file_to_archive(archive, model)) { + close_zip_writer(&archive); + return false; + } + // QDS progress point /*BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format("export 3mf EXPORT_STAGE_ADD_SUPPORT\n"); if (proFn) { @@ -6076,18 +6282,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (data._3mf_printer_thumbnail_middle.empty()) { stream << " \n"; + << "\" Id=\"rel-4\" Type=\"http://schemas.qiditech.com/package/2021/cover-thumbnail-middle\"/>\n"; } else { stream << " \n"; + << "\" Id=\"rel-4\" Type=\"http://schemas.qiditech.com/package/2021/cover-thumbnail-middle\"/>\n"; } if (data._3mf_printer_thumbnail_small.empty()) { stream << "\n"; + << "\" Id=\"rel-5\" Type=\"http://schemas.qiditech.com/package/2021/cover-thumbnail-small\"/>\n"; } else { stream << " \n"; + << "\" Id=\"rel-5\" Type=\"http://schemas.qiditech.com/package/2021/cover-thumbnail-small\"/>\n"; } } else { @@ -6098,11 +6304,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str(); stream << " \n"; + << "\" Id=\"rel-4\" Type=\"http://schemas.qiditech.com/package/2021/cover-thumbnail-middle\"/>\n"; thumbnail_file_str = (boost::format("Metadata/plate_%1%_small.png") % (export_plate_idx + 1)).str(); stream << " \n"; + << "\" Id=\"rel-5\" Type=\"http://schemas.qiditech.com/package/2021/cover-thumbnail-small\"/>\n"; } } else if (targets.empty()) { @@ -6182,7 +6388,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::stringstream stream; reset_stream(stream); stream << "\n"; - stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:QIDIStudio=\"http://schemas.qidilab.com/package/2021\""; + stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:QIDIStudio=\"http://schemas.qiditech.com/package/2021\""; if (m_production_ext) stream << " xmlns:p=\"http://schemas.microsoft.com/3dmanufacturing/production/2015/06\" requiredextensions=\"p\""; stream << ">\n"; @@ -6259,6 +6465,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) metadata_item_map[QDT_MAKERLAB_VERSION_TAG] = xml_escape(model.mk_version); BOOST_LOG_TRIVIAL(info) << "saved mk_version " << model.mk_version; } + + /*for ml*/ + if (model.mk_name.empty() && !model.makerlab_name.empty()) { + metadata_item_map[QDT_MAKERLAB_NAME] = model.makerlab_name; + metadata_item_map[QDT_MAKERLAB_REGION] = model.makerlab_region; + metadata_item_map[QDT_MAKERLAB_ID] = model.makerlab_id; + } + if (!model.md_name.empty()) { for (unsigned int i = 0; i < model.md_name.size(); i++) { @@ -6768,6 +6982,17 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return flush(output_buffer, true); } + void _QDS_3MF_Exporter::add_transformation(std::stringstream &stream, const Transform3d &tr) + { + for (unsigned c = 0; c < 4; ++c) { + for (unsigned r = 0; r < 3; ++r) { + stream << tr(r, c); + if (r != 2 || c != 3) + stream << " "; + } + } + } + bool _QDS_3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const { // This happens for empty projects @@ -6788,13 +7013,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (!item.path.empty()) stream << "\" " << PPATH_ATTR << "=\"" << xml_escape(item.path); stream << "\" " << TRANSFORM_ATTR << "=\""; - for (unsigned c = 0; c < 4; ++c) { - for (unsigned r = 0; r < 3; ++r) { - stream << item.transform(r, c); - if (r != 2 || c != 3) - stream << " "; - } - } + add_transformation(stream, item.transform); stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n"; } @@ -6838,6 +7057,40 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + bool _QDS_3MF_Exporter::_add_brim_ear_points_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + const BrimPoints& brim_points = object->brim_points; + if (!brim_points.empty()) { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < brim_points.size(); ++i) { + sprintf(buffer, (i==0 ? "%f %f %f %f" : " %f %f %f %f"), brim_points[i].pos(0), brim_points[i].pos(1), brim_points[i].pos(2), brim_points[i].head_front_radius); + out += buffer; + } + out += "\n"; + } + } + + if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("brim_points_format_version=") + std::to_string(brim_points_format_version) + std::string("\n") + out; + + if (!mz_zip_writer_add_mem(&archive, BRIM_EAR_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add brim ear points file to archive"); + return false; + } + } + return true; + } + bool _QDS_3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; @@ -7188,6 +7441,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << " <" << METADATA_TAG << " "<< KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; } + if (const std::optional &es = volume->emboss_shape; es.has_value()) + to_xml(stream, *es, *volume, archive); + const TextInfo &text_info = volume->get_text_info(); if (!text_info.m_text.empty()) _add_text_info_to_archive(stream, text_info); @@ -7307,7 +7563,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (!m_skip_model && instance_size > 0) { - for (unsigned int j = 0; j < instance_size; ++j) + for (int j = 0; j < instance_size; ++j) { stream << " <" << INSTANCE_TAG << ">\n"; int obj_id = plate_data->objects_and_instances[j].first; @@ -7352,7 +7608,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // write model rels if (save_gcode) - _add_relationships_file_to_archive(archive, QDS_MODEL_CONFIG_RELS_FILE, gcode_paths, {"http://schemas.qidilab.com/package/2021/gcode"}, Slic3r::PackingTemporaryData(), export_plate_idx); + _add_relationships_file_to_archive(archive, QDS_MODEL_CONFIG_RELS_FILE, gcode_paths, {"http://schemas.qiditech.com/package/2021/gcode"}, Slic3r::PackingTemporaryData(), export_plate_idx); if (!m_skip_model) { //QDS: store assemble related info @@ -8290,4 +8546,148 @@ SaveObjectGaurd::~SaveObjectGaurd() _QDS_Backup_Manager::get().pop_object_gaurd(); } +namespace { + +// Conversion with bidirectional map +// F .. first, S .. second +template F bimap_cvt(const boost::bimap &bmap, S s, const F &def_value) +{ + const auto &map = bmap.right; + auto found_item = map.find(s); + + // only for back and forward compatibility + assert(found_item != map.end()); + if (found_item == map.end()) return def_value; + + return found_item->second; +} + +template S bimap_cvt(const boost::bimap &bmap, F f, const S &def_value) +{ + const auto &map = bmap.left; + auto found_item = map.find(f); + + // only for back and forward compatibility + assert(found_item != map.end()); + if (found_item == map.end()) return def_value; + + return found_item->second; +} + +} // namespace + + + +namespace { +Transform3d create_fix(const std::optional &prev, const ModelVolume &volume) +{ + // IMPROVE: check if volume was modified (translated, rotated OR scaled) + // when no change do not calculate transformation only store original fix matrix + + // Create transformation used after load actual stored volume + // Orca: do not bake volume transformation into meshes + // const Transform3d &actual_trmat = volume.get_matrix(); + const Transform3d &actual_trmat = Transform3d::Identity(); + + const auto &vertices = volume.mesh().its.vertices; + Vec3d min = actual_trmat * vertices.front().cast(); + Vec3d max = min; + for (const Vec3f &v : vertices) { + Vec3d vd = actual_trmat * v.cast(); + for (size_t i = 0; i < 3; ++i) { + if (min[i] > vd[i]) min[i] = vd[i]; + if (max[i] < vd[i]) max[i] = vd[i]; + } + } + Vec3d center = (max + min) / 2; + Transform3d post_trmat = Transform3d::Identity(); + post_trmat.translate(center); + + Transform3d fix_trmat = actual_trmat.inverse() * post_trmat; + if (!prev.has_value()) return fix_trmat; + + // check whether fix somehow differ previous + if (fix_trmat.isApprox(Transform3d::Identity(), 1e-5)) return *prev; + + return *prev * fix_trmat; +} + +bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const ModelVolume &volume, mz_zip_archive &archive) +{ + if (svg.path_in_3mf.empty()) + return true; // EmbossedText OR unwanted store .svg file into .3mf (protection of copyRight) + + if (!svg.path.empty()) + stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path) << "\" "; + stream << SVG_FILE_PATH_IN_3MF_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path_in_3mf) << "\" "; + + std::shared_ptr file_data = svg.file_data; + assert(file_data != nullptr); + if (file_data == nullptr && !svg.path.empty()) file_data = read_from_disk(svg.path); + if (file_data == nullptr) { + BOOST_LOG_TRIVIAL(warning) << "Can't write svg file no filedata"; + return false; + } + const std::string &file_data_str = *file_data; + + return mz_zip_writer_add_mem(&archive, svg.path_in_3mf.c_str(), (const void *) file_data_str.c_str(), file_data_str.size(), MZ_DEFAULT_COMPRESSION); +} + +} // namespace + +void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive) +{ + stream << " <" << SHAPE_TAG << " "; + if (es.svg_file.has_value()) + if (!to_xml(stream, *es.svg_file, volume, archive)) BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf"; + + stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" "; + + if (!es.final_shape.is_healed) stream << UNHEALED_ATTR << "=\"" << 1 << "\" "; + + // projection + const EmbossProjection &p = es.projection; + stream << DEPTH_ATTR << "=\"" << p.depth << "\" "; + if (p.use_surface) stream << USE_SURFACE_ATTR << "=\"" << 1 << "\" "; + + // FIX of baked transformation + Transform3d fix = create_fix(es.fix_3mf_tr, volume); + stream << TRANSFORM_ATTR << "=\""; + _QDS_3MF_Exporter::add_transformation(stream, fix); + stream << "\" "; + + stream << "/>\n"; // end SHAPE_TAG +} + +std::optional read_emboss_shape(const char **attributes, unsigned int num_attributes) +{ + double scale = qds_get_attribute_value_float(attributes, num_attributes, SHAPE_SCALE_ATTR); + int unhealed = qds_get_attribute_value_int(attributes, num_attributes, UNHEALED_ATTR); + bool is_healed = unhealed != 1; + + EmbossProjection projection; + projection.depth = qds_get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR); + if (is_approx(projection.depth, 0.)) projection.depth = 10.; + + int use_surface = qds_get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR); + if (use_surface == 1) projection.use_surface = true; + + std::optional fix_tr_mat; + std::string fix_tr_mat_str = qds_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR); + if (!fix_tr_mat_str.empty()) { fix_tr_mat = qds_get_transform_from_3mf_specs_string(fix_tr_mat_str); } + + std::string file_path = qds_get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_ATTR); + std::string file_path_3mf = qds_get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_IN_3MF_ATTR); + + // MayBe: store also shapes to not store svg + // But be carefull curve will be lost -> scale will not change sampling + // shapes could be loaded from SVG + ExPolygonsWithIds shapes; + // final shape could be calculated from shapes + HealedExPolygons final_shape; + final_shape.is_healed = is_healed; + + EmbossShape::SvgFile svg{file_path, file_path_3mf}; + return EmbossShape{std::move(shapes), std::move(final_shape), scale, std::move(projection), std::move(fix_tr_mat), std::move(svg)}; +} } // namespace Slic3r diff --git a/src/libslic3r/Format/qds_3mf.hpp b/src/libslic3r/Format/qds_3mf.hpp index 2048d8f..037fb65 100644 --- a/src/libslic3r/Format/qds_3mf.hpp +++ b/src/libslic3r/Format/qds_3mf.hpp @@ -141,6 +141,10 @@ inline bool operator & (SaveStrategy & lhs, SaveStrategy rhs) return ((static_cast(lhs) & static_cast(rhs))) == static_cast(rhs); } +enum { + brim_points_format_version = 0 +}; + enum class LoadStrategy { Default = 0, diff --git a/src/libslic3r/Frustum.cpp b/src/libslic3r/Frustum.cpp new file mode 100644 index 0000000..10fae91 --- /dev/null +++ b/src/libslic3r/Frustum.cpp @@ -0,0 +1,97 @@ +#include "Frustum.hpp" +#include +namespace Slic3r { +Frustum::Plane::PlaneIntersects Frustum::Plane::intersects(const BoundingBoxf3 &box) const +{ + Vec3f center = ((box.min + box.max) * 0.5f).cast(); + Vec3f extent = ((box.max - box.min) * 0.5f).cast(); + float d = distance(center); + float r = fabsf(extent.x() * normal_.x()) + fabsf(extent.y() * normal_.y()) + fabsf(extent.z() * normal_.z()); + if (d == r) { + return Plane::Intersects_Tangent; + } else if (std::abs(d) < r) { + return Plane::Intersects_Cross; + } + return (d > 0.0f) ? Plane::Intersects_Front : Plane::Intersects_Back; +} +Frustum::Plane::PlaneIntersects Frustum::Plane::intersects(const Vec3f &p0) const +{ + float d = distance(p0); + if (d == 0) { + return Plane::Intersects_Tangent; + } + return (d > 0.0f) ? Plane::Intersects_Front : Plane::Intersects_Back; +} +Frustum::Plane::PlaneIntersects Frustum::Plane::intersects(const Vec3f &p0, const Vec3f &p1) const +{ + Plane::PlaneIntersects state0 = intersects(p0); + Plane::PlaneIntersects state1 = intersects(p1); + if (state0 == state1) { + return state0; + } + if (state0 == Plane::Intersects_Tangent || state1 == Plane::Intersects_Tangent) { + return Plane::Intersects_Tangent; + } + + return Plane::Intersects_Cross; +} +Frustum::Plane::PlaneIntersects Frustum::Plane::intersects(const Vec3f &p0, const Vec3f &p1, const Vec3f &p2) const +{ + Plane::PlaneIntersects state0 = intersects(p0, p1); + Plane::PlaneIntersects state1 = intersects(p0, p2); + Plane::PlaneIntersects state2 = intersects(p1, p2); + + if (state0 == state1 && state0 == state2) { + return state0; } + + if (state0 == Plane::Intersects_Cross || state1 == Plane::Intersects_Cross || state2 == Plane::Intersects_Cross) { + return Plane::Intersects_Cross; + } + + return Plane::Intersects_Tangent; +} + +bool Frustum::intersects(const BoundingBoxf3 &box, bool is_perspective) const +{ + if (is_perspective) { + for (auto &plane : planes) { + if (plane.intersects(box) == Plane::Intersects_Back) { + return false; + } + } + } + // check box intersects + if (!bbox.intersects(box)) { + return false; + } + return true; +} + +bool Frustum::intersects(const Vec3f &p0) const { + for (auto &plane : planes) { + if (plane.intersects(p0) == Plane::Intersects_Back) { return false; } + } + return true; +} + +bool Frustum::intersects(const Vec3f &p0, const Vec3f &p1) const +{ + for (auto &plane : planes) { + if (plane.intersects(p0, p1) == Plane::Intersects_Back) { + return false; + } + } + return true; +} + +bool Frustum::intersects(const Vec3f &p0, const Vec3f &p1, const Vec3f &p2) const +{ + for (auto &plane : planes) { + if (plane.intersects(p0, p1, p2) == Plane::Intersects_Back) { + return false; + } + } + return true; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Frustum.hpp b/src/libslic3r/Frustum.hpp new file mode 100644 index 0000000..26b12e1 --- /dev/null +++ b/src/libslic3r/Frustum.hpp @@ -0,0 +1,79 @@ +#ifndef slic3r_Frustum_hpp_ +#define slic3r_Frustum_hpp_ + +#include "Point.hpp" +#include "BoundingBox.hpp" +namespace Slic3r { +class Frustum +{ +public: + Frustum()=default; + ~Frustum() = default; + + class Plane { + public: + enum PlaneIntersects { Intersects_Cross = 0, Intersects_Tangent = 1, Intersects_Front = 2, Intersects_Back = 3 }; + void set(const Vec3f &n, const Vec3f &pt) + { + normal_ = n.normalized(); + center_ = pt; + d_ = -normal_.dot(pt); + } + + float distance(const Vec3f &pt) const { return normal_.dot(pt) + d_; } + + inline const Vec3f &getNormal() const { return normal_; } + const Vec3f & getCenter() const { return center_; } + Plane::PlaneIntersects intersects(const BoundingBoxf3 &box) const; + //// check intersect with point (world space) + Plane::PlaneIntersects intersects(const Vec3f &p0) const; + // check intersect with line segment (world space) + Plane::PlaneIntersects intersects(const Vec3f &p0, const Vec3f &p1) const; + // check intersect with triangle (world space) + Plane::PlaneIntersects intersects(const Vec3f &p0, const Vec3f &p1, const Vec3f &p2) const; + private: + Vec3f normal_; + Vec3f center_; + float d_ = 0; + }; + + bool intersects(const BoundingBoxf3 &box, bool is_perspective) const; + // check intersect with point (world space) + bool intersects(const Vec3f &p0) const; + // check intersect with line segment (world space) + bool intersects(const Vec3f &p0, const Vec3f &p1) const; + // check intersect with triangle (world space) + bool intersects(const Vec3f &p0, const Vec3f &p1, const Vec3f &p2) const; + + Plane planes[6]; + /* corners[0]: nearTopLeft; + * corners[1]: nearTopRight; + * corners[2]: nearBottomLeft; + * corners[3]: nearBottomRight; + * corners[4]: farTopLeft; + * corners[5]: farTopRight; + * corners[6]: farBottomLeft; + * corners[7]: farBottomRight; + */ + Vec3f corners[8]; + + BoundingBoxf3 bbox; +}; + +enum FrustumClipMask { + POSITIVE_X = 1 << 0, + NEGATIVE_X = 1 << 1, + POSITIVE_Y = 1 << 2, + NEGATIVE_Y = 1 << 3, + POSITIVE_Z = 1 << 4, + NEGATIVE_Z = 1 << 5, +}; + +const int FrustumClipMaskArray[6] = { + FrustumClipMask::POSITIVE_X, FrustumClipMask::NEGATIVE_X, FrustumClipMask::POSITIVE_Y, FrustumClipMask::NEGATIVE_Y, FrustumClipMask::POSITIVE_Z, FrustumClipMask::NEGATIVE_Z, +}; + +const Vec4f FrustumClipPlane[6] = {{-1, 0, 0, 1}, {1, 0, 0, 1}, {0, -1, 0, 1}, {0, 1, 0, 1}, {0, 0, -1, 1}, {0, 0, 1, 1}}; +} + +#endif diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index febf97d..669ae09 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -298,7 +298,10 @@ static std::vector get_path_of_change_filament(const Print& print) /* Reduce feedrate a bit; travel speed is often too high to move on existing material. Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ //OrcaSlicer - double wipe_speed = gcodegen.writer().config.travel_speed.value * gcodegen.config().wipe_speed.value / 100; + double cur_speed = gcodegen.writer().get_current_speed(); + double wipe_speed = gcodegen.config().role_base_wipe_speed && cur_speed > EPSILON ? cur_speed / 60 : + gcodegen.writer().config.travel_speed.value * gcodegen.config().wipe_speed.value / 100; + // get the retraction length double length = toolchange @@ -865,15 +868,23 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { double top_cd = object.config().support_top_z_distance; - //double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance; - double bottom_cd = top_cd; - + double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance; + //if (!object.print()->config().independent_support_layer_height) + { // the actual support gap may be larger than the configured one due to rounding to layer height for organic support, regardless of independent support layer height + top_cd = std::ceil(top_cd / object.config().layer_height) * object.config().layer_height; + bottom_cd = std::ceil(bottom_cd / object.config().layer_height) * object.config().layer_height; + } double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd); // raft contact distance should not trigger any warning - if(last_extrusion_layer && last_extrusion_layer->support_layer) + if (last_extrusion_layer && last_extrusion_layer->support_layer) { + double raft_gap = object.config().raft_contact_distance.value; + //if (!object.print()->config().independent_support_layer_height) + { + raft_gap = std::ceil(raft_gap / object.config().layer_height) * object.config().layer_height; + } extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); - + } double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height + std::max(0., extra_gap); @@ -1083,56 +1094,6 @@ bool GCode::is_QDT_Printer() //QDS : get the plate model's projection on first layer, contain plate offset,unscaled data BoundingBoxf GCode::first_layer_projection(const Print& print) const { - // too slow -#if 0 - // seperatre points into object for parallel - std::vectorpoints(print.objects().size()); - auto objects = print.objects(); - // use parallel for to speed the iterate - tbb::parallel_for(tbb::blocked_range(0, points.size()), [&points,&objects](tbb::blocked_range r) { - for (auto index = r.begin(); index < r.end(); ++index) { - Polygons obj_islands; - unsigned int estimate_size = (objects[index]->layers().empty() ? 0 : objects[index]->layers().size() * objects[index]->layers().front()->lslices.size()); - obj_islands.reserve(estimate_size); - for (auto& layer : objects[index]->layers()) - for (auto& expoly : layer->lslices) - obj_islands.emplace_back(expoly.contour); - if (!objects[index]->support_layers().empty()) { - for (auto& layer : objects[index]->support_layers()) { - if (layer->support_type == stInnerNormal) - layer->support_fills.polygons_covered_by_spacing(obj_islands, float(SCALED_EPSILON)); - else if (layer->support_type == stInnerTree) { - for (auto& expoly : layer->lslices) - obj_islands.emplace_back(expoly.contour); - } - } - } - // caculate the transform - for (auto& instance : objects[index]->instances()) { - for (Polygon &poly : obj_islands) { - poly.translate(instance.shift); - Pointfs poly_points; - poly_points.reserve(poly.points.size()); - for (auto& point : poly.points) - poly_points.emplace_back(unscale(point)); - append(points[index], std::move(poly_points)); - } - } - } - }); - - Pointfs total_points; - //consider first layers for skirt,brim,wipe tower - int estimate_size =std::accumulate(points.begin(), points.end(), print.first_layer_convex_hull().size(), [](int sum, const Pointfs& point) {return sum + point.size(); });; - total_points.reserve(estimate_size); - - for (const auto& pt : print.first_layer_convex_hull().points) - total_points.emplace_back(unscale(pt.x(),pt.y())); - for (auto& point : points) - append(total_points, std::move(point)); - return BoundingBoxf(total_points); -#endif - BoundingBoxf bbox; for (auto& obj : print.objects()) { for (auto& instance : obj->instances()) { @@ -1235,6 +1196,16 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); // Post-process the G-code to update time stamps. + // QDS: FIX: layers count error, while the last layer extrude is empty + // spiral_vase_layer can't get right height + while (!m_processor.result().spiral_vase_layers.empty()) { + if (m_processor.result().spiral_vase_layers.back().first != FLT_MAX) + break; + //record last move, update prev layer move range + int last_move = m_processor.result().spiral_vase_layers.back().second.second; + m_processor.result().spiral_vase_layers.pop_back(); + m_processor.result().spiral_vase_layers.back().second.second = last_move; + } m_timelapse_warning_code = 0; if (m_config.printer_structure.value == PrinterStructure::psI3 && m_spiral_vase) { @@ -1256,25 +1227,17 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; { //QDS:check bed and filament compatible - const ConfigOptionDef *bed_type_def = print_config_def.get("curr_bed_type"); - assert(bed_type_def != nullptr); - const t_config_enum_values *bed_type_keys_map = bed_type_def->enum_keys_map; - const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_key(m_config.curr_bed_type)); + const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key(m_config.curr_bed_type)); + std::vector conflict_filament; for(auto extruder_id : m_initial_layer_extruders){ int cur_bed_temp = bed_temp_opt->get_at(extruder_id); - if (cur_bed_temp == 0 && bed_type_keys_map != nullptr) { - for (auto item : *bed_type_keys_map) { - if (item.second == m_config.curr_bed_type) { - m_processor.result().bed_match_result = BedMatchResult(false, item.first, extruder_id); - break; - } - } + if (cur_bed_temp == 0) { + conflict_filament.push_back(extruder_id); } - if (m_processor.result().bed_match_result.match == false) - break; } + + m_processor.result().filament_printable_reuslt = FilamentPrintableResult(conflict_filament, bed_type_to_gcode_string(m_config.curr_bed_type)); } - //1.9.7.52 m_processor.set_filaments(m_writer.extruders()); m_processor.finalize(true); // DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); @@ -1573,6 +1536,7 @@ enum QIDIBedType { bbtEngineeringPlate = 2, bbtHighTemperaturePlate = 3, bbtTexturedPEIPlate = 4, + bbtSuperTackPlate = 5, }; static QIDIBedType to_qidi_bed_type(BedType type) @@ -1586,6 +1550,8 @@ static QIDIBedType to_qidi_bed_type(BedType type) qidi_bed_type = bbtHighTemperaturePlate; else if (type == btPTE) qidi_bed_type = bbtTexturedPEIPlate; + else if (type == btSuperTack) + qidi_bed_type = bbtSuperTackPlate; return qidi_bed_type; } @@ -2481,12 +2447,27 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //QDS void GCode::check_placeholder_parser_failed() { + bool has_machine_gcode = false, has_filament_gcode = false; if (! m_placeholder_parser_failed_templates.empty()) { // G-code export proceeded, but some of the PlaceholderParser substitutions failed. std::string msg = Slic3r::format(_(L("Failed to generate gcode for invalid custom G-code.\n\n"))); for (const auto &name_and_error : m_placeholder_parser_failed_templates) + { msg += name_and_error.first + " " + name_and_error.second + "\n"; - msg += Slic3r::format(_(L("Please check the custom G-code or use the default custom G-code."))); + if (("filament_end_gcode" == name_and_error.first) || ("filament_start_gcode" == name_and_error.first)) + has_filament_gcode = true; + else + has_machine_gcode = true; + } + msg += Slic3r::format(_(L("Please check the custom G-code or use the default custom G-code.\n"))); + if (has_machine_gcode) { + if (has_filament_gcode) + msg += Slic3r::format(_(L("You can find them from 'Printer settings' -> 'Machine G-code' and 'Filament settings' -> 'Advanced'."))); + else + msg += Slic3r::format(_(L("You can find it from 'Printer settings' -> 'Machine G-code'."))); + } + else + msg += Slic3r::format(_(L("You can find it from 'Filament settings' -> 'Advanced'."))); throw Slic3r::PlaceholderParserError(msg); } } @@ -3737,16 +3718,18 @@ GCode::LayerResult GCode::process_layer( for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; //QDS: add brim by obj by extruder - if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && !print_wipe_extrusions) { - this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity* ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) { - gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); + if (first_layer) { + if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && !print_wipe_extrusions) { + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp(); + for (const ExtrusionEntity* ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) { + gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); + } + m_avoid_crossing_perimeters.use_external_mp(false); + // Allow a straight travel move to the first object point. + m_avoid_crossing_perimeters.disable_once(); + this->m_objsWithBrim.erase(instance_to_print.print_object.id()); } - m_avoid_crossing_perimeters.use_external_mp(false); - // Allow a straight travel move to the first object point. - m_avoid_crossing_perimeters.disable_once(); - this->m_objsWithBrim.erase(instance_to_print.print_object.id()); } // When starting a new object, use the external motion planner for the first travel move. const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; @@ -4138,10 +4121,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou } else loop.split_at(last_pos, false); - const auto seam_scarf_type = m_config.seam_slope_type.value; // QDS: not apply on fist layer, too small E has stick issue with hotend plate - bool enable_seam_slope = ((seam_scarf_type == SeamScarfType::External && !is_hole) || - seam_scarf_type == SeamScarfType::All) && + int filament_scarf_type = EXTRUDER_CONFIG(filament_scarf_seam_type); + bool enable_seam_slope = (filament_scarf_type == int(SeamScarfType::External) && !is_hole) || + filament_scarf_type == int(SeamScarfType::All) && !m_config.spiral_mode && (loop.role() == erExternalPerimeter || (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && @@ -4149,7 +4132,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (enable_seam_slope && m_config.seam_slope_conditional.value) { //QDS: the seam has been decide, only check the seam position angle - enable_seam_slope = loop.check_seam_point_angle(m_config.scarf_angle_threshold.value * M_PI / 180.0); + const auto nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); + enable_seam_slope = loop.check_seam_point_angle(m_config.scarf_angle_threshold.value * M_PI / 180.0, nozzle_diameter); } // clip the path to avoid the extruder to get exactly on the first point of the loop; @@ -4183,23 +4167,26 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou //QDS: avoid overhang on conditional scarf mode bool slope_has_overhang = false; + // update scarf seam if (enable_seam_slope) { // Create seam slope double start_slope_ratio; - if (m_config.seam_slope_start_height.percent) { - start_slope_ratio = m_config.seam_slope_start_height.value / 100.; - } else { - // Get the ratio against current layer height - double h = paths.front().height; - start_slope_ratio = m_config.seam_slope_start_height.value / h; + if (EXTRUDER_CONFIG(filament_scarf_height).percent) + start_slope_ratio = EXTRUDER_CONFIG(filament_scarf_height).value / 100; + else { + start_slope_ratio = EXTRUDER_CONFIG(filament_scarf_height).value / paths.front().height; } + float slope_gap = EXTRUDER_CONFIG(filament_scarf_gap).get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))); + + double scarf_seam_length = EXTRUDER_CONFIG(filament_scarf_length); + double loop_length = 0.; for (const auto &path : paths) { loop_length += unscale_(path.length()); } const bool slope_entire_loop = m_config.seam_slope_entire_loop; - const double slope_min_length = slope_entire_loop ? loop_length : std::min(m_config.seam_slope_min_length.value, loop_length); + const double slope_min_length = slope_entire_loop ? loop_length : std::min(scarf_seam_length, loop_length); const int slope_steps = m_config.seam_slope_steps; const double slope_max_segment_length = scale_(slope_min_length / slope_steps); // QDS: check if has overhang on slope path @@ -4211,11 +4198,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou ExtrusionLoopSloped new_loop(paths, seam_gap, slope_min_length, slope_max_segment_length, start_slope_ratio, loop.loop_role()); //QDS: clip end and start to get better seam - new_loop.clip_slope(seam_gap); + new_loop.clip_slope(slope_gap); // QDS: slowdown speed to improve seam, to be fix, cooling need to be apply correctly //new_loop.target_speed = get_path_speed(new_loop.starts.back()); //new_loop.slowdown_slope_speed(); - //1.9.5 // QDS: smooth speed of discontinuity areas if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && (loop.role() == erExternalPerimeter || loop.role() == erPerimeter)) smooth_speed_discontinuity_area(new_loop.paths); @@ -4225,6 +4211,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou m_resonance_avoidance = m_config.resonance_avoidance; gcode += this->_extrude(*p, description, speed_for_path(*p, m_resonance_avoidance)); } + set_last_scarf_seam_flag(true); // Fix path for wipe if (!new_loop.ends.empty()) { @@ -4251,6 +4238,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou m_resonance_avoidance = m_config.resonance_avoidance; gcode += this->_extrude(*path, description, speed_for_path(*path, m_resonance_avoidance)); } + set_last_scarf_seam_flag(false); } //QDS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. @@ -4580,17 +4568,6 @@ static bool need_smooth_speed(const ExtrusionPath &other_path, const ExtrusionPa return false; } -static void append_split_line(bool split_from_left, Polyline &polyline, Point p1, Point p2) -{ - if (split_from_left) { - polyline.append(p1); - polyline.append(p2); - } else { - polyline.append(p2); - polyline.append(p1); - } -} - ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &final_v, ExtrusionPath &this_path, double max_smooth_length, bool split_from_left) { ExtrusionPaths splited_path; @@ -4633,63 +4610,52 @@ ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &fina return pos_x_speed; }; - while (end_pt_idx < input_polyline.points.size()) { + while (split_line_speed < final_v && end_pt_idx < input_polyline.size()) { // move to next line if (get_next_line) { line_start_pt = input_polyline.points[end_pt_idx - 1]; line_end_pt = input_polyline.points[end_pt_idx]; } - - Polyline polyline; + //This line is cut off as a speed transition area + Polyline cuted_polyline; Line line(line_start_pt, line_end_pt); + cuted_polyline.append(line_start_pt); // split polyline and set speed if (line.length() < max_step_length || line.length() - min_step_length < min_step_length / 2) { split_line_speed = insert_speed(line.length(), x_base, smooth_length_count, final_v); - append_split_line(split_from_left, polyline, line_start_pt, line_end_pt); end_pt_idx++; - get_next_line = true; + get_next_line = true; + cuted_polyline.append(line.b); } else { // path is too long, split it double rate = min_step_length / line.length(); - Point insert_p = line.a + (line.b - line.a) * rate; + Point insert_p = line.a + (line.b - line.a) * rate; split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v); - append_split_line(split_from_left, polyline, line_start_pt, insert_p); line_start_pt = insert_p; - get_next_line = false; + get_next_line = false; + cuted_polyline.append(insert_p); } - ExtrusionPath path_step(polyline, this_path); + ExtrusionPath path_step(cuted_polyline, this_path); path_step.smooth_speed = split_line_speed; splited_path.push_back(std::move(path_step)); - // stop condition - if (split_line_speed >= final_v) break; } - if (!split_from_left) - std::reverse(input_polyline.points.begin(), input_polyline.points.end()); - // get_remain_path - if (end_pt_idx < input_polyline.points.size()) { - // split at index or split at corr length - Polyline p1, p2; - if( !split_from_left ) { - input_polyline.split_at_length(input_polyline.length() - smooth_length_count, &p1, &p2); - this_path.polyline = p1; - } else { - input_polyline.split_at_length(smooth_length_count, &p1, &p2); - this_path.polyline = p2; - } - - } else { - this_path.polyline.clear(); - } - - // reverse paths if this start from right - if (!split_from_left) + // reverse path back + Polyline p1, p2; + Point & split_point = splited_path.back().polyline.points.back(); + this_path.polyline.split_at(split_point, &p1, &p2); + if (!split_from_left) { + this_path.polyline = p1; std::reverse(splited_path.begin(), splited_path.end()); + for (ExtrusionPath &path : splited_path) { std::reverse(path.polyline.points.begin(), path.polyline.points.end()); } + } else { + this_path.polyline = p2; + } return splited_path; } @@ -5036,21 +5002,22 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } std::string comment; + bool cooling_extrude = false; if (m_enable_cooling_markers) { if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { //QDS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; - if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter)) { + if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter || (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role())))) { gcode += ";_OVERHANG_FAN_START\n"; - comment = ";_EXTRUDE_SET_SPEED"; - } else if (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role())) - gcode += ";_OVERHANG_FAN_START\n"; - else - comment = ";_EXTRUDE_SET_SPEED"; + } } - else { + + int overhang_boundary_for_cooling = EXTRUDER_CONFIG(overhang_threshold_participating_cooling); + + if (!is_bridge(path.role()) && path.get_overhang_degree() <= overhang_boundary_for_cooling) { + cooling_extrude = true; comment = ";_EXTRUDE_SET_SPEED"; } @@ -5146,22 +5113,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } } if (m_enable_cooling_markers) { + if (cooling_extrude) + gcode += ";_EXTRUDE_END\n"; + + if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { //QDS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; - if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter)) { - gcode += ";_EXTRUDE_END\n"; + if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter || (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role())))) gcode += ";_OVERHANG_FAN_END\n"; - - } else if (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role())) - gcode += ";_OVERHANG_FAN_END\n"; - else - gcode += ";_EXTRUDE_END\n"; - } - else { - gcode += ";_EXTRUDE_END\n"; } } @@ -5235,7 +5197,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // generate G-code for the travel move std::string gcode; if (needs_retraction) { - if (m_config.reduce_crossing_wall && could_be_wipe_disabled) + if (m_config.reduce_crossing_wall && could_be_wipe_disabled && !m_last_scarf_seam_flag) m_wipe.reset_path(); Point last_post_before_retract = this->last_pos(); @@ -5422,11 +5384,6 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp for (const ExPolygon& support_island : support_layer->support_islands) if (support_island.contains(travel)) return false; - //reduce the retractions in lightning infills for tree support - if (support_layer != NULL && support_layer->support_type==stInnerTree) - for (auto &area : support_layer->base_areas) - if (area.contains(travel)) - return false; } //QDS: need retract when long moving to print perimeter to avoid dropping of material if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr && @@ -5515,6 +5472,8 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b // QDS: insert skip object label before change filament while by object if (by_object) m_writer.add_object_change_labels(gcode); + else + m_writer.add_object_end_labels(gcode); if (m_writer.extruder() != nullptr) { // Process the custom filament_end_gcode. set_extruder() is only called if there is no wipe tower diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 9b3662c..fcb115b 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -151,6 +151,7 @@ public: m_layer(nullptr), m_object_layer_over_raft(false), //m_volumetric_speed(0), + m_last_scarf_seam_flag(false), m_last_pos_defined(false), m_last_extrusion_role(erNone), m_last_width(0.0f), @@ -181,6 +182,7 @@ public: void set_origin(const Vec2d &pointf); void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); } const Point& last_pos() const { return m_last_pos; } + const bool& last_scarf_seam_flag() const { return m_last_scarf_seam_flag; } Vec2d point_to_gcode(const Point &point) const; Point gcode_to_point(const Vec2d &point) const; const FullPrintConfig &config() const { return m_config; } @@ -334,6 +336,7 @@ private: void check_placeholder_parser_failed(); void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } + void set_last_scarf_seam_flag(bool flag) { m_last_scarf_seam_flag = flag; } bool last_pos_defined() const { return m_last_pos_defined; } void set_extruders(const std::vector &extruder_ids); std::string preamble(); @@ -479,7 +482,7 @@ private: Point m_last_pos; bool m_last_pos_defined; - + bool m_last_scarf_seam_flag; std::unique_ptr m_cooling_buffer; std::unique_ptr m_spiral_vase; #ifdef HAS_PRESSURE_EQUALIZER diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index f6a0106..eeaaf63 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -4,6 +4,7 @@ #include "../libslic3r.h" #include #include +#include namespace Slic3r { diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 2e7f618..4da4dde 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -967,7 +967,6 @@ void GCodeProcessorResult::reset() { filament_costs = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); - bed_match_result = BedMatchResult(true); warnings.clear(); //QDS: add mutex for protection of gcode result @@ -1961,12 +1960,14 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool case '0': { process_M140(line); break; } // Set bed temperature default: break; } + break; case '9': switch (cmd[3]) { case '0': { process_M190(line); break; } // Wait bed temperature case '1': { process_M191(line); break; } // Wait chamber temperature default: break; - } + } + break; default: break; } @@ -3257,6 +3258,16 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset); } + //QDS: some layer may only has G3/G3, update right layer height + if (m_detect_layer_based_on_tag && !m_result.spiral_vase_layers.empty()) { + if (delta_pos[Z] >= 0.0 && type == EMoveType::Extrude && m_result.spiral_vase_layers.back().first == FLT_MAX) { + // replace layer height placeholder with correct value + m_result.spiral_vase_layers.back().first = static_cast(m_end_position[Z]); + } + if (!m_result.moves.empty()) + m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1 - m_seams_count; + } + if (m_detect_layer_based_on_tag && !m_result.spiral_vase_layers.empty()) { if (delta_pos[Z] >= 0.0 && type == EMoveType::Extrude) { const float current_z = static_cast(m_end_position[Z]); @@ -3706,7 +3717,8 @@ void GCodeProcessor::process_G29(const GCodeReader::GCodeLine& line) { //QDS: hardcode 260 seconds for G29 //Todo: use a machine related setting when we have second kind of QDT printer - const float value_s = 260.0; + //w39 + const float value_s = 0.0; simulate_st_synchronize(value_s); } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 9691d85..ff5aac9 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -117,23 +117,23 @@ namespace Slic3r { ConflictResult() = default; }; - struct BedMatchResult - { - bool match; - std::string bed_type_name; - int extruder_id; - BedMatchResult():match(true),bed_type_name(""),extruder_id(-1) {} - BedMatchResult(bool _match,const std::string& _bed_type_name="",int _extruder_id=-1) - :match(_match),bed_type_name(_bed_type_name),extruder_id(_extruder_id) - {} - }; - using ConflictResultOpt = std::optional; + struct FilamentPrintableResult + { + std::vector conflict_filament; + std::string plate_name; + FilamentPrintableResult(){}; + FilamentPrintableResult(std::vector &conflict_filament, std::string plate_name) : conflict_filament(conflict_filament), plate_name(plate_name) {} + bool has_value(){ + return !conflict_filament.empty(); + }; + }; + struct GCodeProcessorResult { ConflictResultOpt conflict_result; - BedMatchResult bed_match_result; + FilamentPrintableResult filament_printable_reuslt; struct SettingsIds { @@ -252,7 +252,7 @@ namespace Slic3r { spiral_vase_layers = other.spiral_vase_layers; warnings = other.warnings; bed_type = other.bed_type; - bed_match_result = other.bed_match_result; + filament_printable_reuslt = other.filament_printable_reuslt; #if ENABLE_GCODE_VIEWER_STATISTICS time = other.time; #endif diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 198703a..ce9951a 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -954,7 +954,7 @@ void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_lay // QDS: replace model custom gcode with current plate custom gcode static CustomGCode::Info custom_gcode_per_print_z; -void ToolOrdering::assign_custom_gcodes(const Print &print) +void ToolOrdering::assign_custom_gcodes(const Print& print) { // Only valid for non-sequential print. assert(print.config().print_sequence == PrintSequence::ByLayer); @@ -964,54 +964,75 @@ void ToolOrdering::assign_custom_gcodes(const Print &print) return; // QDS - auto num_filaments = unsigned(print.config().filament_diameter.size()); - CustomGCode::Mode mode = - (num_filaments == 1) ? CustomGCode::SingleExtruder : - print.object_extruders().size() == 1 ? CustomGCode::MultiAsSingle : CustomGCode::MultiExtruder; - CustomGCode::Mode model_mode = print.model().get_curr_plate_custom_gcodes().mode; - std::vector extruder_printing_above(num_filaments, false); - auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin(); - // Tool changes and color changes will be ignored, if the model's tool/color changes were entered in mm mode and the print is in non mm mode - // or vice versa. - bool ignore_tool_and_color_changes = (mode == CustomGCode::MultiExtruder) != (model_mode == CustomGCode::MultiExtruder); - // If printing on a single extruder machine, make the tool changes trigger color change (M600) events. - bool tool_changes_as_color_changes = mode == CustomGCode::SingleExtruder && model_mode == CustomGCode::MultiAsSingle; + auto num_filaments = unsigned(print.config().filament_diameter.size()); + CustomGCode::Mode mode = + (num_filaments == 1) ? CustomGCode::SingleExtruder : + print.object_extruders().size() == 1 ? CustomGCode::MultiAsSingle : CustomGCode::MultiExtruder; + CustomGCode::Mode model_mode = print.model().get_curr_plate_custom_gcodes().mode; + auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin(); + // Tool changes and color changes will be ignored, if the model's tool/color changes were entered in mm mode and the print is in non mm mode + // or vice versa. + bool ignore_tool_and_color_changes = (mode == CustomGCode::MultiExtruder) != (model_mode == CustomGCode::MultiExtruder); + // If printing on a single extruder machine, make the tool changes trigger color change (M600) events. + bool tool_changes_as_color_changes = mode == CustomGCode::SingleExtruder && model_mode == CustomGCode::MultiAsSingle; - // From the last layer to the first one: - for (auto it_lt = m_layer_tools.rbegin(); it_lt != m_layer_tools.rend(); ++ it_lt) { - LayerTools < = *it_lt; - // Add the extruders of the current layer to the set of extruders printing at and above this print_z. - for (unsigned int i : lt.extruders) - extruder_printing_above[i] = true; - // Skip all custom G-codes above this layer and skip all extruder switches. - for (; custom_gcode_it != custom_gcode_per_print_z.gcodes.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->type == CustomGCode::ToolChange); ++ custom_gcode_it); - if (custom_gcode_it == custom_gcode_per_print_z.gcodes.rend()) - // Custom G-codes were processed. - break; - // Some custom G-code is configured for this layer or a layer below. - const CustomGCode::Item &custom_gcode = *custom_gcode_it; - // print_z of the layer below the current layer. - coordf_t print_z_below = 0.; - if (auto it_lt_below = it_lt; ++ it_lt_below != m_layer_tools.rend()) - print_z_below = it_lt_below->print_z; - if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) { - // The custom G-code applies to the current layer. - bool color_change = custom_gcode.type == CustomGCode::ColorChange; - bool tool_change = custom_gcode.type == CustomGCode::ToolChange; - bool pause_or_custom_gcode = ! color_change && ! tool_change; - bool apply_color_change = ! ignore_tool_and_color_changes && - // If it is color change, it will actually be useful as the exturder above will print. - // QDS - (color_change ? - mode == CustomGCode::SingleExtruder || - (custom_gcode.extruder <= int(num_filaments) && extruder_printing_above[unsigned(custom_gcode.extruder - 1)]) : - tool_change && tool_changes_as_color_changes); - if (pause_or_custom_gcode || apply_color_change) - lt.custom_gcode = &custom_gcode; - // Consume that custom G-code event. - ++ custom_gcode_it; - } - } + auto apply_custom_gcode_to_layer = [mode, + ignore_tool_and_color_changes, + tool_changes_as_color_changes, + num_filaments](LayerTools& lt, const std::vector& extruder_printing_above, const CustomGCode::Item& item) + { + bool color_change = item.type == CustomGCode::ColorChange; + bool tool_change = item.type == CustomGCode::ToolChange; + bool pause_or_custom_gcode = !color_change && !tool_change; + bool apply_color_change = !ignore_tool_and_color_changes && + // If it is color change, it will actually be useful as the exturder above will print. + // BBS + (color_change ? + mode == CustomGCode::SingleExtruder || + (item.extruder <= int(num_filaments) && extruder_printing_above[unsigned(item.extruder - 1)]) : + tool_change && tool_changes_as_color_changes); + if (pause_or_custom_gcode || apply_color_change) + lt.custom_gcode = &item; + }; + + std::unordered_map> extruder_print_above_by_layer; + { + std::vector extruder_printing_above(num_filaments, false); + for (auto iter = m_layer_tools.rbegin(); iter != m_layer_tools.rend(); ++iter) { + for (unsigned int i : iter->extruders) + extruder_printing_above[i] = true; + int layer_idx = m_layer_tools.rend() - iter - 1; + extruder_print_above_by_layer.emplace(layer_idx, extruder_printing_above); + } + } + for (auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin(); custom_gcode_it != custom_gcode_per_print_z.gcodes.rend(); ++custom_gcode_it) { + if (custom_gcode_it->type == CustomGCode::ToolChange) + continue; + + auto layer_it_upper = std::upper_bound(m_layer_tools.begin(), m_layer_tools.end(), custom_gcode_it->print_z, [](double z,const LayerTools& lt) { + return z < lt.print_z; + }); + + int upper_layer_idx = layer_it_upper - m_layer_tools.begin(); + if (layer_it_upper == m_layer_tools.begin()) { + apply_custom_gcode_to_layer(*layer_it_upper, extruder_print_above_by_layer[0], *custom_gcode_it); + } + else if (layer_it_upper == m_layer_tools.end()) { + auto layer_it_lower = std::prev(layer_it_upper); + int lower_layer_idx = layer_it_lower - m_layer_tools.begin(); + apply_custom_gcode_to_layer(*layer_it_lower, extruder_print_above_by_layer[lower_layer_idx], *custom_gcode_it); + } + else { + auto layer_it_lower = std::prev(layer_it_upper); + int lower_layer_idx = layer_it_lower - m_layer_tools.begin(); + double gap_to_lower = std::fabs(custom_gcode_it->print_z - layer_it_lower->print_z); + double gap_to_upper = std::fabs(custom_gcode_it->print_z - layer_it_upper->print_z); + if (gap_to_lower < gap_to_upper) + apply_custom_gcode_to_layer(*layer_it_lower, extruder_print_above_by_layer[lower_layer_idx], *custom_gcode_it); + else + apply_custom_gcode_to_layer(*layer_it_upper, extruder_print_above_by_layer[upper_layer_idx], *custom_gcode_it); + } + } } const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index d28e02d..ab9e740 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -14,6 +14,7 @@ namespace Slic3r { +static const double wipe_tower_wall_infill_overlap = 0.0; inline float align_round(float value, float base) { @@ -115,7 +116,7 @@ public: WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; } - WipeTowerWriter& set_z(float z) + WipeTowerWriter& set_z(float z) { m_current_z = z; return *this; } WipeTowerWriter& set_extrusion_flow(float flow) @@ -171,7 +172,7 @@ public: // Extrude with an explicitely provided amount of extrusion. WipeTowerWriter& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) { - if (x == m_current_pos.x() && y == m_current_pos.y() && e == 0.f && (f == 0.f || f == m_current_feedrate)) + if ((std::abs(x - m_current_pos.x()) <= (float)EPSILON) && (std::abs(y - m_current_pos.y()) < (float)EPSILON) && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. return *this; @@ -234,7 +235,7 @@ public: WipeTowerWriter& travel(float x, float y, float f = 0.f) { return extrude_explicit(x, y, 0.f, f); } - WipeTowerWriter& travel(const Vec2f &dest, float f = 0.f) + WipeTowerWriter& travel(const Vec2f &dest, float f = 0.f) { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. @@ -245,7 +246,7 @@ public: return extrude_explicit(x, y, std::sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); } - WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) + WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) { return extrude(dest.x(), dest.y(), f); } WipeTowerWriter& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) @@ -296,7 +297,7 @@ public: do { ++i; if (i == 4) i = 0; - if (need_change_flow) { + if (need_change_flow) { if (i == 1) { // using bridge flow in bridge area, and add notes for gcode-check when flow changed set_extrusion_flow(wipe_tower->extrusion_flow(0.2)); @@ -355,7 +356,7 @@ public: // Elevate the extruder head above the current print_z position. WipeTowerWriter& z_hop(float hop, float f = 0.f) - { + { m_gcode += std::string("G1") + set_format_Z(m_current_z + hop); if (f != 0 && f != m_current_feedrate) m_gcode += set_format_F(f); @@ -364,7 +365,7 @@ public: } // Lower the extruder head back to the current print_z position. - WipeTowerWriter& z_hop_reset(float f = 0.f) + WipeTowerWriter& z_hop_reset(float f = 0.f) { return z_hop(0, f); } // Move to x1, +y_increment, @@ -415,26 +416,22 @@ public: return *this; } - // Let the firmware back up the active speed override value. - WipeTowerWriter& speed_override_backup() + // Let the firmware back up the active speed override value. + WipeTowerWriter& speed_override_backup() { // QDS: QDT machine don't support speed backup -#if 0 if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 B\n"; -#endif - return *this; + return *this; } - // Let the firmware restore the active speed override value. - WipeTowerWriter& speed_override_restore() - { - // QDS: QDT machine don't support speed restore -#if 0 + // Let the firmware restore the active speed override value. + WipeTowerWriter& speed_override_restore() + { + // QDS: QDT machine don't support speed restore if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 R\n"; -#endif - return *this; + return *this; } // Set digital trimpot motor @@ -452,14 +449,14 @@ public: } WipeTowerWriter& flush_planner_queue() - { + { m_gcode += "G4 S0\n"; return *this; } // Reset internal extruder counter. WipeTowerWriter& reset_extruder() - { + { m_gcode += "G92 E0\n"; return *this; } @@ -722,7 +719,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) // Returns gcode to prime the nozzles at the front edge of the print bed. std::vector WipeTower::prime( // print_z of the first layer. - float initial_layer_print_height, + float initial_layer_print_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. @@ -739,7 +736,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per float wipe_depth = 0.f; float wipe_length = 0.f; float purge_volume = 0.f; - + // Finds this toolchange info if (tool != (unsigned int)(-1)) { @@ -776,7 +773,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per .append(";--------------------\n"); writer.speed_override_backup(); - writer.speed_override(100); + writer.speed_override(100); Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); @@ -799,12 +796,30 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED) ? m_layer_info->toolchanges_depth() - m_layer_info->depth : 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); // align the perimeter + + Vec2f pos = initial_position; + switch (m_cur_layer_id % 4){ + case 0: + pos = wt_box.ld; + break; + case 1: + pos = wt_box.rd; + break; + case 2: + pos = wt_box.ru; + break; + case 3: + pos = wt_box.lu; + break; + default: break; + } + writer.set_initial_position(pos, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + wt_box = align_perimeter(wt_box); writer.rectangle(wt_box); - writer.travel(initial_position); } - if (first_toolchange_to_nonsoluble) { + { writer.travel(Vec2f(0, 0)); writer.travel(initial_position); } @@ -819,7 +834,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per //QDS //if (m_set_extruder_trimpot) // writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. - writer.speed_override_restore(); + writer.speed_override_restore(); writer.feedrate(m_travel_speed * 60.f) .flush_planner_queue() .reset_extruder() @@ -846,7 +861,7 @@ void WipeTower::toolchange_Unload( #if 0 float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; - + const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm @@ -1086,7 +1101,7 @@ void WipeTower::toolchange_Wipe( if (m_depth_traversed != 0) writer.travel(xl, writer.y() + dy); #endif - + bool need_change_flow = false; // now the wiping itself: for (int i = 0; true; ++i) { @@ -1105,9 +1120,9 @@ void WipeTower::toolchange_Wipe( } if (m_left_to_right) - writer.extrude(xr + 0.25f * m_perimeter_width, writer.y(), wipe_speed); + writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); else - writer.extrude(xl - 0.25f * m_perimeter_width, writer.y(), wipe_speed); + writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); // QDS: recover the flow in non-bridging area if (need_change_flow) { @@ -1136,7 +1151,7 @@ void WipeTower::toolchange_Wipe( //writer.add_wipe_point(writer.x(), writer.y()) // .add_wipe_point(writer.x(), writer.y() - dy) // .add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); - // QDS: modify the wipe_path after toolchange + // QDS: modify the wipe_path after toolchange writer.add_wipe_point(writer.x(), writer.y()) .add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y()); @@ -1193,6 +1208,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool bool first_layer = is_first_layer(); // QDS: speed up perimeter speed to 90mm/s for non-first layer float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, 5400.f); + writer.feedrate(feedrate); float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width; box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y), m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y); @@ -1470,7 +1486,7 @@ void WipeTower::plan_tower() m_wipe_tower_depth = 0.f; for (auto& layer : m_plan) layer.depth = 0.f; - + float max_depth_for_all = 0; for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) { @@ -1479,7 +1495,7 @@ void WipeTower::plan_tower() this_layer_depth = min_wipe_tower_depth; m_plan[layer_index].depth = this_layer_depth; - + if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width) m_wipe_tower_depth = this_layer_depth + m_perimeter_width; @@ -1489,7 +1505,7 @@ void WipeTower::plan_tower() m_plan[i].depth = this_layer_depth; } - if (m_enable_timelapse_print && layer_index == 0) + if (m_enable_timelapse_print && layer_index == 0) max_depth_for_all = m_plan[0].depth; } @@ -1551,7 +1567,7 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first, if (first.end_pos != second.start_pos) out.gcode += "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) - + " F7200\n"; + + "\n"; out.gcode += second.gcode; out.extrusions.insert(out.extrusions.end(), second.extrusions.begin(), second.extrusions.end()); out.end_pos = second.end_pos; @@ -1597,10 +1613,11 @@ void WipeTower::generate(std::vector> & used = 0.f; m_old_temperature = -1; // reset last temperature written in the gcode - + int index = 0; std::vector layer_result; for (auto layer : m_plan) { + m_cur_layer_id = index++; set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); // QDS //m_internal_rotation += 180.f; @@ -1624,14 +1641,14 @@ void WipeTower::generate(std::vector> & // if there is no toolchange switching to non-soluble, finish layer // will be called at the very beginning. That's the last possibility // where a nonsoluble tool can be. - if (m_enable_timelapse_print) { + if (m_enable_timelapse_print) { timelapse_wall = only_generate_out_wall(); } finish_layer_tcr = finish_layer(m_enable_timelapse_print ? false : true, layer.extruder_fill); } for (int i=0; i 0.); assert(F < 100000.); - + m_current_speed = F; GCodeG1Formatter w; w.emit_f(F); //QDS diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 15878e0..0e89f94 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -65,7 +65,8 @@ public: // printed with the same extruder. std::string toolchange_prefix() const; std::string toolchange(unsigned int extruder_id); - std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const; + std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()); + double get_current_speed() { return m_current_speed; }; std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()); std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string()); std::string travel_to_z(double z, const std::string &comment = std::string()); @@ -138,7 +139,7 @@ private: //QDS: x, y offset for gcode generated double m_x_offset{ 0 }; double m_y_offset{ 0 }; - + double m_current_speed{ 0 }; std::string m_gcode_label_objects_start; std::string m_gcode_label_objects_end; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 84b967e..9fba666 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -93,7 +93,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo // Use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm. const Vec2d cell_size(part_size(0) + gap, part_size(1) + gap); - const BoundingBoxf bed_bbox = (bed_bounding_box != NULL && bed_bounding_box->defined) ? + const BoundingBoxf bed_bbox = (bed_bounding_box != NULL && bed_bounding_box->defined) ? *bed_bounding_box : // Bogus bed size, large enough not to trigger the unsufficient bed size error. BoundingBoxf( @@ -105,12 +105,12 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1))); if (num_parts > cellw * cellh) throw Slic3r::InvalidArgument("%zu parts won't fit in your print area!\n", num_parts); - + // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); Vec2d cells_offset(bed_bbox.center() - 0.5 * cells_size); BoundingBoxf cells_bb(cells_offset, cells_size + cells_offset); - + // List of cells, sorted by distance from center. std::vector cellsorder(cellw * cellh, ArrangeItem()); for (size_t j = 0; j < cellh; ++ j) { @@ -167,7 +167,7 @@ arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const Boundin // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm part(0) += dist; part(1) += dist; - + Vec2d area(Vec2d::Zero()); if (bb != NULL && bb->defined) { area = bb->size(); @@ -176,46 +176,46 @@ arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const Boundin area(0) = part(0) * total_parts; area(1) = part(1) * total_parts; } - + // this is how many cells we have available into which to put parts size_t cellw = floor((area(0) + dist) / part(0)); size_t cellh = floor((area(1) + dist) / part(1)); if (total_parts > (cellw * cellh)) return false; - + // total space used by cells Vec2d cells(cellw * part(0), cellh * part(1)); - + // bounding box of total space used by cells BoundingBoxf cells_bb; cells_bb.merge(Vec2d(0,0)); // min cells_bb.merge(cells); // max - + // center bounding box to area cells_bb.translate( (area(0) - cells(0)) / 2, (area(1) - cells(1)) / 2 ); - + // list of cells, sorted by distance from center std::vector cellsorder; - + // work out distance for all cells, sort into list for (size_t i = 0; i <= cellw-1; ++i) { for (size_t j = 0; j <= cellh-1; ++j) { coordf_t cx = linint(i + 0.5, 0, cellw, cells_bb.min(0), cells_bb.max(0)); coordf_t cy = linint(j + 0.5, 0, cellh, cells_bb.min(1), cells_bb.max(1)); - + coordf_t xd = fabs((area(0) / 2) - cx); coordf_t yd = fabs((area(1) / 2) - cy); - + ArrangeItem c; c.pos(0) = cx; c.pos(1) = cy; c.index_x = i; c.index_y = j; c.dist = xd * xd + yd * yd - fabs((cellw / 2) - (i + 0.5)); - + // binary insertion sort { coordf_t index = c.dist; @@ -224,7 +224,7 @@ arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const Boundin while (low < high) { size_t mid = (low + ((high - low) / 2)) | 0; coordf_t midval = cellsorder[mid].index; - + if (midval < index) { low = mid + 1; } else if (midval > index) { @@ -239,7 +239,7 @@ arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const Boundin ENDSORT: ; } } - + // the extents of cells actually used by objects coordf_t lx = 0; coordf_t ty = 0; @@ -267,17 +267,17 @@ arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const Boundin cellsorder.erase(cellsorder.begin()); coordf_t cx = c.item.index_x - lx; coordf_t cy = c.item.index_y - ty; - + positions.push_back(Vec2d(cx * part(0), cy * part(1))); } - + if (bb != NULL && bb->defined) { for (Pointfs::iterator p = positions.begin(); p != positions.end(); ++p) { p->x() += bb->min(0); p->y() += bb->min(1); } } - + return true; } #endif @@ -330,6 +330,10 @@ Vec3d extract_euler_angles(const Eigen::Matrix& return angles; } +Vec3d extract_rotation(const Transform3d &transform) { + return extract_euler_angles(transform); +} + Vec3d extract_euler_angles(const Transform3d& transform) { // use only the non-translational part of the transform @@ -341,6 +345,56 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } +static Transform3d extract_rotation_matrix(const Transform3d &trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(rotation); +} + +static std::pair extract_rotation_scale(const Transform3d &trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return {Transform3d(rotation), Transform3d(scale)}; +} + +static Transform3d extract_scale(const Transform3d &trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(scale); +} + + + +static bool contains_skew(const Transform3d &trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + + if (scale.isDiagonal()) return false; + + if (scale.determinant() >= 0.0) return true; + + // the matrix contains mirror + const Matrix3d ratio = scale.cwiseQuotient(trafo.matrix().block<3, 3>(0, 0)); + + auto check_skew = [&ratio](int i, int j, bool &skew) { + if (!std::isnan(ratio(i, j)) && !std::isnan(ratio(j, i))) skew |= std::abs(ratio(i, j) * ratio(j, i) - 1.0) > EPSILON; + }; + + bool has_skew = false; + check_skew(0, 1, has_skew); + check_skew(0, 2, has_skew); + check_skew(1, 2, has_skew); + return has_skew; +} + 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(); @@ -385,95 +439,42 @@ Transform3d scale_transform(const Vec3d &scale) return transform; } -Transformation::Flags::Flags() - : dont_translate(true) - , dont_rotate(true) - , dont_scale(true) - , dont_mirror(true) +Transform3d Transformation::get_offset_matrix() const { return translation_transform(get_offset()); } + +const Vec3d &Transformation::get_rotation() const { + m_temp_rotation = extract_rotation(extract_rotation_matrix(m_matrix)); + return m_temp_rotation; } -bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const -{ - return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror); -} - -void Transformation::Flags::set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) -{ - this->dont_translate = dont_translate; - this->dont_rotate = dont_rotate; - this->dont_scale = dont_scale; - this->dont_mirror = dont_mirror; -} - -Transformation::Transformation() -{ - reset(); -} - -Transformation::Transformation(const Transform3d& transform) -{ - set_from_transform(transform); -} - -Transform3d Transformation::get_offset_matrix() const { - return translation_transform(get_offset()); -} - -void Transformation::set_offset(const Vec3d &offset) -{ - set_offset(X, offset(0)); - set_offset(Y, offset(1)); - set_offset(Z, offset(2)); -} - -void Transformation::set_offset(Axis axis, double offset) -{ - if (m_offset(axis) != offset) - { - m_offset(axis) = offset; - m_dirty = true; - } -} - -static Transform3d extract_rotation_matrix(const Transform3d &trafo) -{ - Matrix3d rotation; - Matrix3d scale; - trafo.computeRotationScaling(&rotation, &scale); - return Transform3d(rotation); -} - -static Transform3d extract_scale(const Transform3d &trafo) -{ - Matrix3d rotation; - Matrix3d scale; - trafo.computeRotationScaling(&rotation, &scale); - return Transform3d(scale); -} - -Transform3d Transformation::get_rotation_matrix() const { - return extract_rotation_matrix(m_matrix); -} +Transform3d Transformation::get_rotation_matrix() const { return extract_rotation_matrix(m_matrix); } void Transformation::set_rotation(const Vec3d &rotation) { - set_rotation(X, rotation(0)); - set_rotation(Y, rotation(1)); - set_rotation(Z, rotation(2)); + const Vec3d offset = get_offset(); + m_matrix = rotation_transform(rotation) * extract_scale(m_matrix); + m_matrix.translation() = offset; } void Transformation::set_rotation(Axis axis, double rotation) { rotation = angle_to_0_2PI(rotation); - if (is_approx(std::abs(rotation), 2.0 * (double)PI)) - rotation = 0.0; + if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; - if (m_rotation(axis) != rotation) - { - m_rotation(axis) = rotation; - m_dirty = true; - } + auto [curr_rotation, scale] = extract_rotation_scale(m_matrix); + Vec3d angles = extract_rotation(curr_rotation); + angles[axis] = rotation; + + const Vec3d offset = get_offset(); + m_matrix = rotation_transform(angles) * scale; + m_matrix.translation() = offset; +} + +const Vec3d &Transformation::get_scaling_factor() const +{ + const Transform3d scale = extract_scale(m_matrix); + m_temp_scaling_factor= {std::abs(scale(0, 0)), std::abs(scale(1, 1)), std::abs(scale(2, 2))}; + return m_temp_scaling_factor; } Transform3d Transformation::get_scaling_factor_matrix() const @@ -485,27 +486,65 @@ Transform3d Transformation::get_scaling_factor_matrix() const return scale; } -void Transformation::set_scaling_factor(const Vec3d& scaling_factor) +void Transformation::set_scaling_factor(const Vec3d &scaling_factor) { - set_scaling_factor(X, scaling_factor(0)); - set_scaling_factor(Y, scaling_factor(1)); - set_scaling_factor(Z, scaling_factor(2)); + assert(scaling_factor.x() > 0.0 && scaling_factor.y() > 0.0 && scaling_factor.z() > 0.0); + + const Vec3d offset = get_offset(); + m_matrix = extract_rotation_matrix(m_matrix) * scale_transform(scaling_factor); + m_matrix.translation() = offset; } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { - if (m_scaling_factor(axis) != std::abs(scaling_factor)) - { - m_scaling_factor(axis) = std::abs(scaling_factor); - m_dirty = true; - } + assert(scaling_factor > 0.0); + + auto [rotation, scale] = extract_rotation_scale(m_matrix); + scale(axis, axis) = scaling_factor; + + const Vec3d offset = get_offset(); + m_matrix = rotation * scale; + m_matrix.translation() = offset; } -void Transformation::set_mirror(const Vec3d& mirror) +const Vec3d &Transformation::get_mirror() const { - set_mirror(X, mirror(0)); - set_mirror(Y, mirror(1)); - set_mirror(Z, mirror(2)); + const Transform3d scale = extract_scale(m_matrix); + m_temp_mirror = {scale(0, 0) / std::abs(scale(0, 0)), scale(1, 1) / std::abs(scale(1, 1)), scale(2, 2) / std::abs(scale(2, 2))}; + return m_temp_mirror; +} + +Transform3d Transformation::get_mirror_matrix() const +{ + Transform3d scale = extract_scale(m_matrix); + scale(0, 0) = scale(0, 0) / std::abs(scale(0, 0)); + scale(1, 1) = scale(1, 1) / std::abs(scale(1, 1)); + scale(2, 2) = scale(2, 2) / std::abs(scale(2, 2)); + return scale; +} + +void Transformation::set_mirror(const Vec3d &mirror) +{ + Vec3d copy(mirror); + const Vec3d abs_mirror = copy.cwiseAbs(); + for (int i = 0; i < 3; ++i) { + if (abs_mirror(i) == 0.0) + copy(i) = 1.0; + else if (abs_mirror(i) != 1.0) + copy(i) /= abs_mirror(i); + } + + auto [rotation, scale] = extract_rotation_scale(m_matrix); + const Vec3d curr_scales = {scale(0, 0), scale(1, 1), scale(2, 2)}; + const Vec3d signs = curr_scales.cwiseProduct(copy); + + if (signs[0] < 0.0) scale(0, 0) = -scale(0, 0); + if (signs[1] < 0.0) scale(1, 1) = -scale(1, 1); + if (signs[2] < 0.0) scale(2, 2) = -scale(2, 2); + + const Vec3d offset = get_offset(); + m_matrix = rotation * scale; + m_matrix.translation() = offset; } void Transformation::set_mirror(Axis axis, double mirror) @@ -516,73 +555,35 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; - if (m_mirror(axis) != mirror) - { - m_mirror(axis) = mirror; - m_dirty = true; - } + auto [rotation, scale] = extract_rotation_scale(m_matrix); + const double curr_scale = scale(axis, axis); + const double sign = curr_scale * mirror; + + if (sign < 0.0) scale(axis, axis) = -scale(axis, axis); + + const Vec3d offset = get_offset(); + m_matrix = rotation * scale; + m_matrix.translation() = offset; } -void Transformation::set_from_transform(const Transform3d& transform) +bool Transformation::has_skew() const { return contains_skew(m_matrix); } + +void Transformation::reset() { m_matrix = Transform3d::Identity(); } + +void Transformation::reset_rotation() { - // offset - set_offset(transform.matrix().block(0, 3, 3, 1)); - - Eigen::Matrix m3x3 = transform.matrix().block(0, 0, 3, 3); - - // mirror - // it is impossible to reconstruct the original mirroring factors from a matrix, - // we can only detect if the matrix contains a left handed reference system - // in which case we reorient it back to right handed by mirroring the x axis - Vec3d mirror = Vec3d::Ones(); - if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) - { - mirror(0) = -1.0; - // remove mirror - m3x3.col(0) *= -1.0; - } - set_mirror(mirror); - - // scale - set_scaling_factor(Vec3d(m3x3.col(0).norm(), m3x3.col(1).norm(), m3x3.col(2).norm())); - - // remove scale - m3x3.col(0).normalize(); - m3x3.col(1).normalize(); - m3x3.col(2).normalize(); - - // rotation - set_rotation(Geometry::extract_euler_angles(m3x3)); - - // forces matrix recalculation matrix - m_matrix = get_matrix(); - -// // debug check -// if (!m_matrix.isApprox(transform)) -// std::cout << "something went wrong in extracting data from matrix" << std::endl; -} - -void Transformation::reset() -{ - m_offset = Vec3d::Zero(); - m_rotation = Vec3d::Zero(); - m_scaling_factor = Vec3d::Ones(); - m_mirror = Vec3d::Ones(); - m_matrix = Transform3d::Identity(); - m_dirty = false; -} - -void Transformation::reset_rotation() { const Geometry::TransformationSVD svd(*this); m_matrix = get_offset_matrix() * Transform3d(svd.v * svd.s * svd.v.transpose()) * svd.mirror_matrix(); } -void Transformation::reset_scaling_factor() { +void Transformation::reset_scaling_factor() +{ const Geometry::TransformationSVD svd(*this); m_matrix = get_offset_matrix() * Transform3d(svd.u) * Transform3d(svd.v.transpose()) * svd.mirror_matrix(); } -void Transformation::reset_skew() { +void Transformation::reset_skew() +{ auto new_scale_factor = [](const Matrix3d &s) { return pow(s(0, 0) * s(1, 1) * s(2, 2), 1. / 3.); // scale average }; @@ -591,22 +592,22 @@ void Transformation::reset_skew() { m_matrix = get_offset_matrix() * Transform3d(svd.u) * scale_transform(new_scale_factor(svd.s)) * Transform3d(svd.v.transpose()) * svd.mirror_matrix(); } -const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const +const Transform3d &Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { - if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) - { - m_matrix = Geometry::assemble_transform( - dont_translate ? Vec3d::Zero() : m_offset, - dont_rotate ? Vec3d::Zero() : m_rotation, - dont_scale ? Vec3d::Ones() : m_scaling_factor, - dont_mirror ? Vec3d::Ones() : m_mirror - ); - - m_flags.set(dont_translate, dont_rotate, dont_scale, dont_mirror); - m_dirty = false; + if (dont_translate == false && dont_rotate == false && dont_scale == false && dont_mirror == false) { + return m_matrix; } - - return m_matrix; + Transformation refence_tran(m_matrix); + if (dont_translate) + refence_tran.reset_offset(); + if (dont_rotate) + refence_tran.reset_rotation(); + if (dont_scale) + refence_tran.reset_scaling_factor(); + if (dont_mirror) + refence_tran.reset_mirror(); + m_temp_matrix = refence_tran.get_matrix(); + return m_temp_matrix; } Transform3d Transformation::get_matrix_no_offset() const @@ -623,10 +624,9 @@ Transform3d Transformation::get_matrix_no_scaling_factor() const return copy.get_matrix(); } -Transformation Transformation::operator * (const Transformation& other) const -{ - return Transformation(get_matrix() * other.get_matrix()); -} +Transformation Transformation::operator*(const Transformation &other) const { return Transformation(get_matrix() * other.get_matrix()); } +//end Transformation + Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) { @@ -808,5 +808,16 @@ Geometry::TransformationSVD::TransformationSVD(const Transform3d &trafo) return curMat; } +Transformation generate_transform(const Vec3d& x_dir, const Vec3d& y_dir, const Vec3d& z_dir, const Vec3d& origin) { + Matrix3d m; + m.col(0) = x_dir.normalized(); + m.col(1) = y_dir.normalized(); + m.col(2) = z_dir.normalized(); + Transform3d mm(m); + Transformation tran(mm); + tran.set_offset(origin); + return tran; +} + } // namespace Geometry } // namespace Slic3r diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index d8802bb..7ed9388 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -10,7 +10,7 @@ // Serialization through the Cereal library #include -namespace Slic3r { +namespace Slic3r { namespace ClipperLib { class PolyNode; @@ -115,9 +115,9 @@ inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const } inline bool segments_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &ip1, const Slic3r::Point &ip2, const Slic3r::Point &jp1, const Slic3r::Point &jp2) -{ +{ assert(ip1 != ip2); assert(jp1 != jp2); @@ -308,12 +308,22 @@ template T angle_to_0_2PI(T angle) return angle; } +template +void to_range_pi_pi(T &angle) +{ + if (angle > T(PI) || angle <= -T(PI)) { + int count = static_cast(std::round(angle / (2 * PI))); + angle -= static_cast(count * 2 * PI); + assert(angle <= T(PI) && angle > -T(PI)); + } +} + void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); double linint(double value, double oldmin, double oldmax, double newmin, double newmax); bool arrange( // input - size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, + size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, // output Pointfs &positions); @@ -365,64 +375,58 @@ Transform3d scale_transform(const Vec3d &scale); class Transformation { - struct Flags - { - bool dont_translate; - bool dont_rotate; - bool dont_scale; - bool dont_mirror; - - Flags(); - - bool needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const; - void set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror); - }; - - Vec3d m_offset; // In unscaled coordinates - Vec3d m_rotation; // Rotation around the three axes, in radians around mesh center point - Vec3d m_scaling_factor; // Scaling factors along the three axes - Vec3d m_mirror; // Mirroring along the three axes - - mutable Transform3d m_matrix; - mutable Flags m_flags; - mutable bool m_dirty; - + Transform3d m_matrix{Transform3d::Identity()}; + mutable Transform3d m_temp_matrix{Transform3d::Identity()};//just for return + mutable Vec3d m_temp_offset{Vec3d::Zero()}; // just for return + mutable Vec3d m_temp_rotation{Vec3d::Zero()}; // just for return + mutable Vec3d m_temp_scaling_factor{Vec3d::Ones()}; // just for return + mutable Vec3d m_temp_mirror{Vec3d::Ones()}; // just for return public: - Transformation(); - explicit Transformation(const Transform3d& transform); + Transformation() = default; + Transformation(const Transformation &trans) : m_matrix(trans.get_matrix()) {} + explicit Transformation(const Transform3d &transform) : m_matrix(transform) {} - //QDS: add get dirty function - bool is_dirty() { return m_dirty; } + const Vec3d& get_offset() const{ m_temp_offset = m_matrix.translation();return m_temp_offset;} + double get_offset(Axis axis) const { return get_offset()[axis]; } - const Vec3d& get_offset() const { return m_offset; } - double get_offset(Axis axis) const { return m_offset(axis); } Transform3d get_offset_matrix() const; - void set_offset(const Vec3d& offset); - void set_offset(Axis axis, double offset); - const Vec3d& get_rotation() const { return m_rotation; } - double get_rotation(Axis axis) const { return m_rotation(axis); } + void set_offset(const Vec3d &offset) { m_matrix.translation() = offset; } + void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } + + const Vec3d &get_rotation() const; + double get_rotation(Axis axis) const { return get_rotation()[axis]; } + Transform3d get_rotation_matrix() const; - void set_rotation(const Vec3d& rotation); + + void set_rotation(const Vec3d &rotation); void set_rotation(Axis axis, double rotation); - Transform3d get_scaling_factor_matrix() const; - const Vec3d& get_scaling_factor() const { return m_scaling_factor; } - double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } + const Vec3d &get_scaling_factor() const; + double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; } - void set_scaling_factor(const Vec3d& scaling_factor); + Transform3d get_scaling_factor_matrix() const; + + bool is_scaling_uniform() const + { + const Vec3d scale = get_scaling_factor(); + return std::abs(scale.x() - scale.y()) < 1e-8 && std::abs(scale.x() - scale.z()) < 1e-8; + } + + void set_scaling_factor(const Vec3d &scaling_factor); void set_scaling_factor(Axis axis, double scaling_factor); - bool is_scaling_uniform() const { return std::abs(m_scaling_factor.x() - m_scaling_factor.y()) < 1e-8 && std::abs(m_scaling_factor.x() - m_scaling_factor.z()) < 1e-8; } - const Vec3d& get_mirror() const { return m_mirror; } - double get_mirror(Axis axis) const { return m_mirror(axis); } - bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; } + const Vec3d &get_mirror() const; + double get_mirror(Axis axis) const { return get_mirror()[axis]; } - void set_mirror(const Vec3d& mirror); + Transform3d get_mirror_matrix() const; + + bool is_left_handed() const { return m_matrix.linear().determinant() < 0; } + + void set_mirror(const Vec3d &mirror); void set_mirror(Axis axis, double mirror); - void set_from_transform(const Transform3d& transform); - void set_matrix(const Transform3d &transform) { set_from_transform(transform); } + bool has_skew() const; void reset(); void reset_offset() { set_offset(Vec3d::Zero()); } @@ -431,32 +435,28 @@ public: void reset_mirror() { set_mirror(Vec3d::Ones()); } void reset_skew(); - const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; + const Transform3d &get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; + Transform3d get_matrix_no_offset() const; Transform3d get_matrix_no_scaling_factor() const; - Transformation operator * (const Transformation& other) const; - - // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity - // as possible in least squares norm in regard to the 8 corners of bbox. - // Bounding box is expected to be centered around zero in all axes. - static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); - - // QDS: backup use this compare - friend bool operator==(Transformation const& l, Transformation const& r) { - return l.m_offset == r.m_offset && l.m_rotation == r.m_rotation && l.m_scaling_factor == r.m_scaling_factor && l.m_mirror == r.m_mirror; - } - + void set_matrix(const Transform3d &transform) { m_matrix = transform; } + void set_from_transform(const Transform3d &transform) { m_matrix = transform; }//add + Transformation operator*(const Transformation &other) const; + static Transformation volume_to_bed_transformation(const Transformation &instance_transformation, const BoundingBoxf3 &bbox); + // QDS: backup use this compare + friend bool operator==(Transformation const &l, Transformation const &r) { return l.m_matrix.isApprox(r.m_matrix); } + friend bool operator!=(Transformation const &l, Transformation const &r) { return !(l == r); } private: - friend class cereal::access; - template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } - explicit Transformation(int) : m_dirty(true) {} - template static void load_and_construct(Archive &ar, cereal::construct &construct) - { - // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. - construct(1); - ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); - } + friend class cereal::access; + template void serialize(Archive &ar) { ar(m_matrix); } + explicit Transformation(int) {} + template static void load_and_construct(Archive &ar, cereal::construct &construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_matrix); + } }; struct TransformationSVD @@ -504,6 +504,7 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation) } Transformation mat_around_a_point_rotate(const Transformation& innMat, const Vec3d &pt, const Vec3d &axis, float rotate_theta_radian); +Transformation generate_transform(const Vec3d &x_dir, const Vec3d &y_dir, const Vec3d &z_dir, const Vec3d &origin); } } // namespace Slicer::Geometry #endif diff --git a/src/libslic3r/Geometry/VoronoiOffset.cpp b/src/libslic3r/Geometry/VoronoiOffset.cpp index 4610522..e772b15 100644 --- a/src/libslic3r/Geometry/VoronoiOffset.cpp +++ b/src/libslic3r/Geometry/VoronoiOffset.cpp @@ -117,7 +117,7 @@ namespace detail { // Return maximum two points, that are at distance "d" from both the line and point. Intersections line_point_equal_distance_points(const Line &line, const Point &ipt, const double d) - { + { assert(line.a != ipt && line.b != ipt); // Calculating two points of distance "d" to a ray and a point. // Point. @@ -667,10 +667,12 @@ void annotate_inside_outside(VD &vd, const Lines &lines) // Set a VertexCategory, verify validity of the operation. auto annotate_vertex = [](const VD::vertex_type *vertex, VertexCategory new_vertex_category) { + if (vertex == nullptr) + return; #ifndef NDEBUG VertexCategory vc = vertex_category(vertex); assert(vc == VertexCategory::Unknown || vc == new_vertex_category); - assert(new_vertex_category == VertexCategory::Inside || + assert(new_vertex_category == VertexCategory::Inside || new_vertex_category == VertexCategory::Outside || new_vertex_category == VertexCategory::OnContour); #endif // NDEBUG @@ -1509,9 +1511,9 @@ Polygons offset( } Polygons offset( - const VD &vd, - const Lines &lines, - double offset_distance, + const VD &vd, + const Lines &lines, + double offset_distance, double discretization_error) { annotate_inside_outside(const_cast(vd), lines); @@ -1528,7 +1530,7 @@ Polygons offset( // An infinite Voronoi Edge-Point (parabola) or Point-Point (line) bisector is split into // a center part close to the Voronoi sites (not skeleton) and the ends (skeleton), // though either part could be clipped by the Voronoi segment. -// +// // Further filtering of the skeleton may be necessary. std::vector skeleton_edges_rough( const VD &vd, @@ -1583,7 +1585,7 @@ std::vector skeleton_edges_rough( } } else { // An infinite Voronoi Edge-Point (parabola) or Point-Point (line) bisector, clipped to a finite Voronoi segment. - // The infinite bisector has a distance (skeleton radius) minimum, which is also a minimum + // The infinite bisector has a distance (skeleton radius) minimum, which is also a minimum // of the skeleton function dr / dt. assert(cell->contains_point() || cell2->contains_point()); if (cell->contains_point() != cell2->contains_point()) { diff --git a/src/libslic3r/IntersectionPoints.cpp b/src/libslic3r/IntersectionPoints.cpp new file mode 100644 index 0000000..cf5b870 --- /dev/null +++ b/src/libslic3r/IntersectionPoints.cpp @@ -0,0 +1,45 @@ +#include "IntersectionPoints.hpp" +#include + +//NOTE: using CGAL SweepLines is slower !!! (example in git history) + +namespace { +using namespace Slic3r; +IntersectionsLines compute_intersections(const Lines &lines) +{ + if (lines.size() < 3) + return {}; + + auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + IntersectionsLines result; + for (uint32_t li = 0; li < lines.size()-1; ++li) { + const Line &l = lines[li]; + auto intersections = AABBTreeLines::get_intersections_with_line(lines, tree, l); + for (const auto &[p, node_index] : intersections) { + if (node_index - 1 <= li) + continue; + if (const Line &l_ = lines[node_index]; + l_.a == l.a || + l_.a == l.b || + l_.b == l.a || + l_.b == l.b ) + // it is duplicit point not intersection + continue; + + // NOTE: fix AABBTree to compute intersection with double preccission!! + Vec2d intersection_point = p.cast(); + + result.push_back(IntersectionLines{li, static_cast(node_index), intersection_point}); + } + } + return result; +} +} // namespace + +namespace Slic3r { +IntersectionsLines get_intersections(const Lines &lines) { return compute_intersections(lines); } +IntersectionsLines get_intersections(const Polygon &polygon) { return compute_intersections(to_lines(polygon)); } +IntersectionsLines get_intersections(const Polygons &polygons) { return compute_intersections(to_lines(polygons)); } +IntersectionsLines get_intersections(const ExPolygon &expolygon) { return compute_intersections(to_lines(expolygon)); } +IntersectionsLines get_intersections(const ExPolygons &expolygons) { return compute_intersections(to_lines(expolygons)); } +} diff --git a/src/libslic3r/IntersectionPoints.hpp b/src/libslic3r/IntersectionPoints.hpp new file mode 100644 index 0000000..49694c8 --- /dev/null +++ b/src/libslic3r/IntersectionPoints.hpp @@ -0,0 +1,23 @@ +#ifndef slic3r_IntersectionPoints_hpp_ +#define slic3r_IntersectionPoints_hpp_ + +#include "ExPolygon.hpp" + +namespace Slic3r { + +struct IntersectionLines { + uint32_t line_index1; + uint32_t line_index2; + Vec2d intersection; +}; +using IntersectionsLines = std::vector; + +// collect all intersecting points +IntersectionsLines get_intersections(const Lines &lines); +IntersectionsLines get_intersections(const Polygon &polygon); +IntersectionsLines get_intersections(const Polygons &polygons); +IntersectionsLines get_intersections(const ExPolygon &expolygon); +IntersectionsLines get_intersections(const ExPolygons &expolygons); + +} // namespace Slic3r +#endif // slic3r_IntersectionPoints_hpp_ \ No newline at end of file diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index e9db4bf..4dc2799 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -179,12 +179,9 @@ void Layer::make_perimeters() && config.fuzzy_skin == other_config.fuzzy_skin && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance - && config.seam_slope_type == other_config.seam_slope_type && config.seam_slope_conditional == other_config.seam_slope_conditional && config.scarf_angle_threshold == other_config.scarf_angle_threshold - && config.seam_slope_start_height == other_config.seam_slope_start_height && config.seam_slope_entire_loop == other_config.seam_slope_entire_loop - && config.seam_slope_min_length == other_config.seam_slope_min_length && config.seam_slope_steps == other_config.seam_slope_steps && config.seam_slope_inner_walls == other_config.seam_slope_inner_walls) { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 9cb97cf..aa180d9 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -262,11 +262,6 @@ private: LayerRegionPtrs m_regions; }; -enum SupportInnerType { - stInnerNormal, - stInnerTree -}; - class SupportLayer : public Layer { public: @@ -275,7 +270,6 @@ public: ExPolygons support_islands; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; - SupportInnerType support_type = stInnerNormal; // for tree supports ExPolygons base_areas; @@ -296,7 +290,7 @@ protected: // The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower // between the raft and the object first layer. SupportLayer(size_t id, size_t interface_id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : - Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id), support_type(stInnerNormal) {} + Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id) {} virtual ~SupportLayer() = default; size_t m_interface_id; diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 90f5648..513087e 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -258,6 +258,23 @@ public: using Scalar = Vec3d::Scalar; }; +class Line3float +{ +public: + Line3float() : a(Vec3f::Zero()), b(Vec3f::Zero()) {} + Line3float(const Vec3f &_a, const Vec3f &_b) : a(_a), b(_b) {} + + Vec3f vector() const { return this->b - this->a; } + Vec3f unit_vector() const { return (length() == 0.0) ? Vec3f::Zero() : vector().normalized(); } + double length() const { return vector().norm(); } + + Vec3f a; + Vec3f b; + + static const constexpr int Dim = 3; + using Scalar = Vec3f::Scalar; +}; +typedef std::vector Line3floats; BoundingBox get_extents(const Lines &lines); } // namespace Slic3r diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 441af3c..331c7df 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -93,7 +93,7 @@ public: bool features_extracted = false; }; - std::optional get_feature(size_t face_idx, const Vec3d& point, const Transform3d &world_tran); + std::optional get_feature(size_t face_idx, const Vec3d &point, const Transform3d &world_tran,bool only_select_plane); int get_num_of_planes() const; const std::vector& get_plane_triangle_indices(int idx) const; std::vector* get_plane_tri_indices(int idx); @@ -526,7 +526,7 @@ void MeasuringImpl::extract_features(int plane_idx) plane.features_extracted = true; } -std::optional MeasuringImpl::get_feature(size_t face_idx, const Vec3d& point, const Transform3d &world_tran) +std::optional MeasuringImpl::get_feature(size_t face_idx, const Vec3d &point, const Transform3d &world_tran,bool only_select_plane) { if (face_idx >= m_face_to_plane.size()) return std::optional(); @@ -544,46 +544,48 @@ std::optional MeasuringImpl::get_feature(size_t face_idx, const assert(plane.surface_features.empty() || plane.surface_features.back().get_type() == SurfaceFeatureType::Plane); - for (size_t i=0; idist; - if (dist < feature_hover_limit && dist < min_dist) { - min_dist = std::min(dist, min_dist); - closest_feature_idx = i; + if (!only_select_plane) { + for (size_t i = 0; i < plane.surface_features.size() - 1; ++i) { + // The -1 is there to prevent measuring distance to the plane itself, + // which is needless and relatively expensive. + res = get_measurement(plane.surface_features[i], point_sf); + if (res.distance_strict) { // TODO: this should become an assert after all combinations are implemented. + double dist = res.distance_strict->dist; + if (dist < feature_hover_limit && dist < min_dist) { + min_dist = std::min(dist, min_dist); + closest_feature_idx = i; + } } } - } - if (closest_feature_idx != size_t(-1)) { - const SurfaceFeature& f = plane.surface_features[closest_feature_idx]; - if (f.get_type() == SurfaceFeatureType::Edge) { - // If this is an edge, check if we are not close to the endpoint. If so, - // we will include the endpoint as well. Close = 10% of the lenghth of - // the edge, clamped between 0.025 and 0.5 mm. - const auto& [sp, ep] = f.get_edge(); - double len_sq = (ep-sp).squaredNorm(); - double limit_sq = std::max(0.025*0.025, std::min(0.5*0.5, 0.1 * 0.1 * len_sq)); - if ((point - sp).squaredNorm() < limit_sq) { - SurfaceFeature local_f(sp); - local_f.origin_surface_feature = std::make_shared(local_f); - local_f.translate(world_tran); - return std::make_optional(local_f); + if (closest_feature_idx != size_t(-1)) { + const SurfaceFeature &f = plane.surface_features[closest_feature_idx]; + if (f.get_type() == SurfaceFeatureType::Edge) { + // If this is an edge, check if we are not close to the endpoint. If so, + // we will include the endpoint as well. Close = 10% of the lenghth of + // the edge, clamped between 0.025 and 0.5 mm. + const auto &[sp, ep] = f.get_edge(); + double len_sq = (ep - sp).squaredNorm(); + double limit_sq = std::max(0.025 * 0.025, std::min(0.5 * 0.5, 0.1 * 0.1 * len_sq)); + if ((point - sp).squaredNorm() < limit_sq) { + SurfaceFeature local_f(sp); + local_f.origin_surface_feature = std::make_shared(local_f); + local_f.translate(world_tran); + return std::make_optional(local_f); + } + + if ((point - ep).squaredNorm() < limit_sq) { + SurfaceFeature local_f(ep); + local_f.origin_surface_feature = std::make_shared(local_f); + local_f.translate(world_tran); + return std::make_optional(local_f); + } } - - if ((point - ep).squaredNorm() < limit_sq) { - SurfaceFeature local_f(ep); - local_f.origin_surface_feature = std::make_shared(local_f); - local_f.translate(world_tran); - return std::make_optional(local_f); - } + SurfaceFeature f_tran(f); + f_tran.origin_surface_feature = std::make_shared(f); + f_tran.translate(world_tran); + return std::make_optional(f_tran); } - SurfaceFeature f_tran(f); - f_tran.origin_surface_feature = std::make_shared(f); - f_tran.translate(world_tran); - return std::make_optional(f_tran); } // Nothing detected, return the plane as a whole. @@ -646,8 +648,12 @@ Measuring::~Measuring() {} -std::optional Measuring::get_feature(size_t face_idx, const Vec3d &point, const Transform3d &world_tran) const { - return priv->get_feature(face_idx, point,world_tran); +std::optional Measuring::get_feature(size_t face_idx, const Vec3d &point, const Transform3d &world_tran, bool only_select_plane) const +{ + if (face_idx == 7516 || face_idx == 7517) { + std::cout << ""; + } + return priv->get_feature(face_idx, point, world_tran, only_select_plane); } diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index 5b0acf1..bc6b811 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -129,7 +129,7 @@ public: // Given a face_idx where the mouse cursor points, return a feature that // should be highlighted (if any). - std::optional get_feature(size_t face_idx, const Vec3d& point, const Transform3d & world_tran) const; + std::optional get_feature(size_t face_idx, const Vec3d& point, const Transform3d & world_tran,bool only_select_plane) const; // Return total number of planes. int get_num_of_planes() const; diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 878913f..927207b 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -200,12 +200,12 @@ indexed_triangle_set cgal_to_indexed_triangle_set(const _Mesh &cgalmesh) const auto &vertices = cgalmesh.vertices(); int vsize = int(vertices.size()); - for (auto &vi : vertices) { + for (const auto &vi : vertices) { auto &v = cgalmesh.point(vi); // Don't ask... its.vertices.emplace_back(to_vec3f(v)); } - for (auto &face : faces) { + for (const auto &face : faces) { auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); int i = 0; diff --git a/src/libslic3r/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index d874a0e..bcfa1a5 100644 --- a/src/libslic3r/MeshSplitImpl.hpp +++ b/src/libslic3r/MeshSplitImpl.hpp @@ -146,7 +146,7 @@ void its_split(const Its &m, OutputIt out_it) visitor.visit([&facets](size_t idx) { facets.emplace_back(idx); return true; }); if (facets.empty()) break; - + std::sort(facets.begin(),facets.end()); // Create a new mesh for the part that was just split off. indexed_triangle_set mesh; mesh.indices.reserve(facets.size()); @@ -184,6 +184,82 @@ std::vector its_split(const Its &its) return ret; } +// Splits a mesh into multiple meshes when possible. +template +void its_split_and_keep_relationship(const Its &m, OutputIt out_it, OutputIt_ship out_ship) +{ + using namespace meshsplit_detail; + + const indexed_triangle_set &its = ItsWithNeighborsIndex_::get_its(m); + + struct VertexConv + { + size_t part_id = std::numeric_limits::max(); + size_t vertex_image; + }; + std::vector vidx_conv(its.vertices.size()); + + meshsplit_detail::NeighborVisitor visitor(its, meshsplit_detail::ItsWithNeighborsIndex_::get_index(m)); + + std::vector facets; + for (size_t part_id = 0;; ++part_id) { + // Collect all faces of the next patch. + facets.clear(); + visitor.visit([&facets](size_t idx) { + facets.emplace_back(idx); + return true; + }); + if (facets.empty()) break; + std::sort(facets.begin(), facets.end()); + // Create a new mesh for the part that was just split off. + indexed_triangle_set mesh; + mesh.indices.reserve(facets.size()); + mesh.vertices.reserve(std::min(facets.size() * 3, its.vertices.size())); + std::unordered_map relationship; + // Assign the facets to the new mesh. + for (size_t face_id : facets) { + const auto &face = its.indices[face_id]; + Vec3i new_face; + for (size_t v = 0; v < 3; ++v) { + auto vi = face(v); + + if (vidx_conv[vi].part_id != part_id) { + vidx_conv[vi] = {part_id, mesh.vertices.size()}; + mesh.vertices.emplace_back(its.vertices[size_t(vi)]); + } + + new_face(v) = vidx_conv[vi].vertex_image; + } + relationship[mesh.indices.size()] = face_id; + mesh.indices.emplace_back(new_face); + } + + *out_it = std::move(mesh); + *out_ship = std::move(relationship); + ++out_it; + } +} +class MeshAndShip +{ +public: + std::vector itses; + std::vector> ships; +}; + +template +MeshAndShip its_split_and_save_relationship(const Its &its) +{ + auto ret = reserve_vector(3); + auto ret_ship = reserve_vector>(3); + + its_split_and_keep_relationship(its, std::back_inserter(ret), std::back_inserter(ret_ship)); + MeshAndShip mesh_ship; + mesh_ship.itses = ret; + mesh_ship.ships = ret_ship; + return mesh_ship; +} + + template bool its_is_splittable(const Its &m) { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3947215..d839bd8 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -30,6 +30,7 @@ #include "SVG.hpp" #include +#include #include "GCodeWriter.hpp" // QDS: for segment @@ -89,8 +90,15 @@ Model& Model::assign_copy(const Model &rhs) this->design_id = rhs.design_id; this->stl_design_id = rhs.stl_design_id; this->stl_design_country = rhs.stl_design_country; + this->makerlab_region = rhs.makerlab_region; + this->makerlab_name = rhs.makerlab_name; + this->makerlab_id = rhs.makerlab_id; this->profile_info = rhs.profile_info; + this->makerlab_region = rhs.makerlab_region; + this->makerlab_name = rhs.makerlab_name; + this->makerlab_id = rhs.makerlab_id; + this->mk_name = rhs.mk_name; this->mk_version = rhs.mk_version; this->md_name = rhs.md_name; @@ -126,6 +134,9 @@ Model& Model::assign_copy(Model &&rhs) this->design_id = rhs.design_id; this->stl_design_id = rhs.stl_design_id; this->stl_design_country = rhs.stl_design_country; + this->makerlab_region = rhs.makerlab_region; + this->makerlab_name = rhs.makerlab_name; + this->makerlab_id = rhs.makerlab_id; this->mk_name = rhs.mk_name; this->mk_version = rhs.mk_version; this->md_name = rhs.md_name; @@ -173,17 +184,67 @@ Model::~Model() Slic3r::remove_backup(*this, true); } +Model Model::read_from_step(const std::string& input_file, + LoadStrategy options, + ImportStepProgressFn stepFn, + StepIsUtf8Fn stepIsUtf8Fn, + std::function step_mesh_fn, + double linear_defletion, + double angle_defletion) +{ + Model model; + bool result = false; + bool is_cb_cancel = false; + std::string message; + Step step_file(input_file); + step_file.load(); + if (step_mesh_fn) { + if (step_mesh_fn(step_file, linear_defletion, angle_defletion) == -1) { + Model empty_model; + return empty_model; + } + } + result = load_step(input_file.c_str(), &model, is_cb_cancel, linear_defletion, angle_defletion, stepFn, stepIsUtf8Fn); + if (is_cb_cancel) { + Model empty_model; + return empty_model; + } + + if (!result) { + if (message.empty()) + throw Slic3r::RuntimeError(_L("Loading of a model file failed.")); + else + throw Slic3r::RuntimeError(message); + } + + if (model.objects.empty()) + throw Slic3r::RuntimeError(_L("The supplied file couldn't be read because it's empty")); + + for (ModelObject *o : model.objects) + o->input_file = input_file; + + if (options & LoadStrategy::AddDefaultInstances) + model.add_default_instances(); + + return model; +} + // QDS: add part plate related logic // QDS: backup & restore // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. -Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, - LoadStrategy options, PlateDataPtrs* plate_data, std::vector* project_presets, bool *is_xxx, Semver* file_version, Import3mfProgressFn proFn, - ImportstlProgressFn stlFn, - ImportStepProgressFn stepFn, - StepIsUtf8Fn stepIsUtf8Fn, - QDTProject * project, - int plate_id, - ObjImportColorFn objFn) +Model Model::read_from_file(const std::string& input_file, + DynamicPrintConfig* config, + ConfigSubstitutionContext* config_substitutions, + LoadStrategy options, + PlateDataPtrs* plate_data, + std::vector* project_presets, + bool *is_xxx, + Semver* file_version, + Import3mfProgressFn proFn, + ImportstlProgressFn stlFn, + QDTProject * project, + int plate_id, + ObjImportColorFn objFn) { Model model; @@ -207,10 +268,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c bool result = false; bool is_cb_cancel = false; std::string message; - if (boost::algorithm::iends_with(input_file, ".stp") || - boost::algorithm::iends_with(input_file, ".step")) - result = load_step(input_file.c_str(), &model, is_cb_cancel, stepFn, stepIsUtf8Fn); - else if (boost::algorithm::iends_with(input_file, ".stl")) + if (boost::algorithm::iends_with(input_file, ".stl")) result = load_stl(input_file.c_str(), &model, nullptr, stlFn); else if (boost::algorithm::iends_with(input_file, ".oltp")) result = load_stl(input_file.c_str(), &model, nullptr, stlFn,256); @@ -222,7 +280,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c if (obj_info.vertex_colors.size() > 0) { std::vector vertex_filament_ids; if (objFn) { // 1.result is ok and pop up a dialog - objFn(obj_info.vertex_colors, false, vertex_filament_ids, first_extruder_id); + objFn(obj_info.vertex_colors, false, vertex_filament_ids, first_extruder_id, obj_info.ml_region, obj_info.ml_name, obj_info.ml_id); if (vertex_filament_ids.size() > 0) { result = obj_import_vertex_color_deal(vertex_filament_ids, first_extruder_id, & model); } @@ -230,7 +288,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c } else if (obj_info.face_colors.size() > 0 && obj_info.has_uv_png == false) { // mtl file std::vector face_filament_ids; if (objFn) { // 1.result is ok and pop up a dialog - objFn(obj_info.face_colors, obj_info.is_single_mtl, face_filament_ids, first_extruder_id); + objFn(obj_info.face_colors, obj_info.is_single_mtl, face_filament_ids, first_extruder_id, obj_info.ml_region, obj_info.ml_name, obj_info.ml_id); if (face_filament_ids.size() > 0) { result = obj_import_face_color_deal(face_filament_ids, first_extruder_id, &model); } @@ -244,8 +302,6 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c }*/ } } - else if (boost::algorithm::iends_with(input_file, ".svg")) - result = load_svg(input_file.c_str(), &model, message); //QDS: remove the old .amf.xml files //else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) else if (boost::algorithm::iends_with(input_file, ".amf")) @@ -452,6 +508,21 @@ ModelObject* Model::add_object(const ModelObject &other) return new_object; } +void Model::set_assembly_pos(ModelObject *model_object) +{ + if (!model_object) {return;} + auto cur_assemble_scene_box = bounding_box_in_assembly_view(); + if (cur_assemble_scene_box.defined) { + auto offset = cur_assemble_scene_box.center(); + auto mo_box = model_object->bounding_box_in_assembly_view(); + offset[0] += ((cur_assemble_scene_box.size()[0] / 2.0f + mo_box.size()[0] / 2.0f) * 1.2); + offset[2] = cur_assemble_scene_box.min[2] + mo_box.size()[2]; + model_object->instances[0]->set_assemble_offset(offset); + offset[1] += cur_assemble_scene_box.center().y() - model_object->bounding_box_in_assembly_view().center().y(); + model_object->instances[0]->set_assemble_offset(offset); + } +} + void Model::delete_object(size_t idx) { ModelObjectPtrs::iterator i = this->objects.begin() + idx; @@ -603,6 +674,13 @@ BoundingBoxf3 Model::bounding_box() const return bb; } +BoundingBoxf3 Model::bounding_box_in_assembly_view() const { + BoundingBoxf3 bb; + for (ModelObject *o : this->objects) + bb.merge(o->bounding_box_in_assembly_view()); + return bb; +} + unsigned int Model::update_print_volume_state(const BuildVolume &build_volume) { unsigned int num_printable = 0; @@ -966,6 +1044,9 @@ void Model::load_from(Model& model) design_id = model.design_id; stl_design_id = model.stl_design_id; stl_design_country = model.stl_design_country; + makerlab_region = model.makerlab_region; + makerlab_name = model.makerlab_name; + makerlab_id = model.makerlab_id; model_info = model.model_info; profile_info = model.profile_info; mk_name = model.mk_name; @@ -1046,6 +1127,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->sla_support_points = rhs.sla_support_points; this->sla_points_status = rhs.sla_points_status; this->sla_drain_holes = rhs.sla_drain_holes; + this->brim_points = rhs.brim_points; this->layer_config_ranges = rhs.layer_config_ranges; this->layer_height_profile = rhs.layer_height_profile; this->printable = rhs.printable; @@ -1090,6 +1172,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) this->sla_support_points = std::move(rhs.sla_support_points); this->sla_points_status = std::move(rhs.sla_points_status); this->sla_drain_holes = std::move(rhs.sla_drain_holes); + this->brim_points = std::move(brim_points); this->layer_config_ranges = std::move(rhs.layer_config_ranges); this->layer_height_profile = std::move(rhs.layer_height_profile); this->printable = std::move(rhs.printable); @@ -1158,23 +1241,27 @@ bool ModelObject::make_boolean(ModelObject *cut_object, const std::string &boole return true; } -ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) +ModelVolume *ModelObject::add_volume(const TriangleMesh &mesh, bool modify_to_center_geometry) { ModelVolume* v = new ModelVolume(this, mesh); this->volumes.push_back(v); - v->center_geometry_after_creation(); - this->invalidate_bounding_box(); + if (modify_to_center_geometry) { + v->center_geometry_after_creation(); + this->invalidate_bounding_box(); + } // QDS: backup Slic3r::save_object_mesh(*this); return v; } -ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh, ModelVolumeType type /*= ModelVolumeType::MODEL_PART*/) +ModelVolume *ModelObject::add_volume(TriangleMesh &&mesh, ModelVolumeType type /*= ModelVolumeType::MODEL_PART*/, bool modify_to_center_geometry) { ModelVolume* v = new ModelVolume(this, std::move(mesh), type); this->volumes.push_back(v); - v->center_geometry_after_creation(); - this->invalidate_bounding_box(); + if (modify_to_center_geometry) { + v->center_geometry_after_creation(); + this->invalidate_bounding_box(); + } // QDS: backup Slic3r::save_object_mesh(*this); return v; @@ -1354,6 +1441,15 @@ const BoundingBoxf3& ModelObject::bounding_box() const return m_bounding_box; } +const BoundingBoxf3 &ModelObject::bounding_box_in_assembly_view() const +{ + m_bounding_box_in_assembly_view.reset(); + BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box(); + for (const ModelInstance *i : this->instances) + m_bounding_box_in_assembly_view.merge(i->transform_bounding_box_in_assembly_view(raw_bbox)); + return m_bounding_box_in_assembly_view; +} + // A mesh containing all transformed instances of this object. TriangleMesh ModelObject::mesh() const { @@ -1673,6 +1769,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con new_object->sla_support_points.clear(); new_object->sla_drain_holes.clear(); new_object->sla_points_status = sla::PointsStatus::NoPoints; + new_object->brim_points.clear(); new_object->clear_volumes(); new_object->input_file.clear(); @@ -1883,6 +1980,22 @@ void ModelObject::clone_for_cut(ModelObject **obj) (*obj)->input_file.clear(); } +bool ModelVolume::is_the_only_one_part() const +{ + if (m_type != ModelVolumeType::MODEL_PART) + return false; + if (object == nullptr) return false; + for (const ModelVolume *v : object->volumes) { + if (v == nullptr) continue; + // is this volume? + if (v->id() == this->id()) continue; + // exist another model part in object? + if (v->type() == ModelVolumeType::MODEL_PART) return false; + } + return true; +} + + Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array& plane_points) { Vec3d mid_point = {0.0, 0.0, 0.0}; @@ -2271,6 +2384,7 @@ ModelObjectPtrs ModelObject::segment(size_t instance, unsigned int max_extruders upper->sla_support_points.clear(); upper->sla_drain_holes.clear(); upper->sla_points_status = sla::PointsStatus::NoPoints; + upper->brim_points.clear(); upper->clear_volumes(); upper->input_file.clear(); @@ -2375,6 +2489,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) if (volume->type() != ModelVolumeType::MODEL_PART) continue; + // splited volume should not be text object if (!is_multi_volume_object) { //QDS: not multi volume object, then split mesh. std::vector volume_meshes = volume->mesh().split(); @@ -2510,6 +2625,7 @@ ModelObjectPtrs ModelObject::merge_volumes(std::vector& vol_indeces) upper->sla_support_points.clear(); upper->sla_drain_holes.clear(); upper->sla_points_status = sla::PointsStatus::NoPoints; + upper->brim_points.clear(); upper->clear_volumes(); upper->input_file.clear(); @@ -3129,22 +3245,31 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t) // This is useful to assign different materials to different volumes of an object. size_t ModelVolume::split(unsigned int max_extruders) { - std::vector meshes = this->mesh().split(); + std::vector> ships; + std::vector meshes = this->mesh().split_and_save_relationship(ships); if (meshes.size() <= 1) return 1; - + if (meshes.size() != ships.size()) + return 1; + // splited volume should not be text object size_t idx = 0; size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); const std::string name = this->name; unsigned int extruder_counter = 0; const Vec3d offset = this->get_offset(); - + std::vector tris_split_strs; + auto face_count = m_mesh->its.indices.size(); + tris_split_strs.reserve(face_count); + for (size_t i = 0; i < face_count; i++) { + tris_split_strs.emplace_back(mmu_segmentation_facets.get_triangle_as_string(i)); + } + int last_all_mesh_face_count = 0; for (TriangleMesh &mesh : meshes) { if (mesh.empty()) // Repair may have removed unconnected triangles, thus emptying the mesh. continue; - + auto cur_face_count = mesh.its.indices.size(); if (idx == 0) { this->set_mesh(std::move(mesh)); this->calculate_convex_hull(); @@ -3159,9 +3284,26 @@ size_t ModelVolume::split(unsigned int max_extruders) this->exterior_facets.reset(); this->supported_facets.reset(); this->seam_facets.reset(); + for (size_t i = 0; i < cur_face_count; i++) { + if (ships[idx].find(i) != ships[idx].end()) { + auto index = ships[idx][i]; + if (tris_split_strs[index].size() > 0) { + mmu_segmentation_facets.set_triangle_from_string(i, tris_split_strs[index]); + } + } + } + } else { + auto new_mv =new ModelVolume(object, *this, std::move(mesh)); + this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new_mv); + for (size_t i = 0; i < new_mv->mesh_ptr()->its.indices.size(); i++) { + if (ships[idx].find(i) != ships[idx].end()) { + auto index = ships[idx][i]; + if (tris_split_strs[index].size() > 0) { + new_mv->mmu_segmentation_facets.set_triangle_from_string(i, tris_split_strs[index]); + } + } + } } - else - this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh))); this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); this->object->volumes[ivolume]->center_geometry_after_creation(); @@ -3172,6 +3314,7 @@ size_t ModelVolume::split(unsigned int max_extruders) //this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); this->object->volumes[ivolume]->m_is_splittable = 0; ++ idx; + last_all_mesh_face_count += cur_face_count; } // discard volumes for which the convex hull was not generated or is degenerate @@ -3301,6 +3444,11 @@ void ModelVolume::convert_from_meters() this->source.is_converted_from_meters = true; } +const Transform3d &ModelVolume::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const +{ + return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); +} + void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->transform(get_matrix(dont_translate)); @@ -3338,6 +3486,10 @@ BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, b return bbox.transformed(get_matrix(dont_translate)); } +BoundingBoxf3 ModelInstance::transform_bounding_box_in_assembly_view(const BoundingBoxf3 &bbox, bool dont_translate) const { + return bbox.transformed(get_assemble_transformation().get_matrix()); +} + Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const { return get_matrix(dont_translate) * v; @@ -3351,6 +3503,11 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } +const Transform3d &ModelInstance::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const +{ + return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); +} + //QDS // QDS set print speed table and find maximum speed void Model::setPrintSpeedTable(const DynamicPrintConfig& config, const PrintConfig& print_config) { @@ -3643,7 +3800,7 @@ double Model::getThermalLength(const ModelVolume* modelVolumePtr) { if (Model::extruderParamsMap.at(aa).materialName == "PC") { thermalLength = 40; } - if (Model::extruderParamsMap.at(aa).materialName == "TPU") { + if (Model::extruderParamsMap.at(aa).materialName == "TPU" || Model::extruderParamsMap.at(aa).materialName == "TPU-AMS") { thermalLength = 1000; } @@ -3716,7 +3873,7 @@ double getadhesionCoeff(const ModelVolumePtrs objectVolumes) Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "PCTG") { adhesionCoeff = 2; } - else if (Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "TPU") { + else if (Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "TPU" || Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName == "TPU-AMS") { adhesionCoeff = 0.5; } } @@ -4039,6 +4196,17 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); }); } +bool model_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new) +{ + if (mo.brim_points.size() != mo_new.brim_points.size()) + return true; + for (size_t i = 0; i < mo.brim_points.size(); ++i) { + if (mo.brim_points[i] != mo_new.brim_points[i]) + return true; + } + return false; +} + bool model_has_multi_part_objects(const Model &model) { for (const ModelObject *model_object : model.objects) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e77b91e..6d41f38 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -6,14 +6,16 @@ #include "Geometry.hpp" #include "ObjectID.hpp" #include "Point.hpp" +#include "AppConfig.hpp" #include "PrintConfig.hpp" #include "Slicing.hpp" #include "SLA/SupportPoint.hpp" #include "SLA/Hollowing.hpp" +#include "BrimEarsPoint.hpp" #include "TriangleMesh.hpp" #include "CustomGCode.hpp" #include "enum_bitmask.hpp" - +#include "EmbossShape.hpp" //QDS: add qds 3mf #include "Format/qds_3mf.hpp" //QDS: add step @@ -31,7 +33,7 @@ #include #include #include - +#include namespace cereal { class BinaryInputArchive; class BinaryOutputArchive; @@ -372,6 +374,8 @@ public: // Holes to be drilled into the object so resin can flow out sla::DrainHoles sla_drain_holes; + BrimPoints brim_points; + /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment @@ -396,8 +400,8 @@ public: return global_config.option(config_option); } - ModelVolume* add_volume(const TriangleMesh &mesh); - ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART); + ModelVolume* add_volume(const TriangleMesh &mesh, bool modify_to_center_geometry = true); + ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART, bool modify_to_center_geometry = true); ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID); ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh); ModelVolume* add_volume_with_shared_mesh(const ModelVolume &other, ModelVolumeType type = ModelVolumeType::MODEL_PART); @@ -427,6 +431,7 @@ public: // This bounding box is approximate and not snug. // This bounding box is being cached. const BoundingBoxf3& bounding_box() const; + const BoundingBoxf3& bounding_box_in_assembly_view() const; void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } // A mesh containing all transformed instances of this object. @@ -647,6 +652,7 @@ private: // Bounding box, cached. mutable BoundingBoxf3 m_bounding_box; + mutable BoundingBoxf3 m_bounding_box_in_assembly_view; mutable bool m_bounding_box_valid; mutable BoundingBoxf3 m_raw_bounding_box; mutable bool m_raw_bounding_box_valid; @@ -674,7 +680,7 @@ private: Internal::StaticSerializationWrapper config_wrapper(config); Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, - sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, + sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, brim_points, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid, cut_connectors, cut_id); } @@ -685,7 +691,7 @@ private: // QDS: add backup, check modify SaveObjectGaurd gaurd(*this); ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, - sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, + sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, brim_points, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid, cut_connectors, cut_id); std::vector volume_ids2; @@ -897,6 +903,7 @@ public: void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } void reset_mesh() { m_mesh = std::make_shared(); } + const std::shared_ptr &get_mesh_shared_ptr() const { return m_mesh; } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. ModelConfigObject config; @@ -917,6 +924,11 @@ public: // List of exterior faces FacetsAnnotation exterior_facets; + + // Is set only when volume is Embossed Shape + // Contain 2d information about embossed shape to be editabled + std::optional emboss_shape; + // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; } ModelVolumeType type() const { return m_type; } @@ -927,6 +939,8 @@ public: bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } + bool is_svg() const { return emboss_shape.has_value(); } + bool is_the_only_one_part() const; // behave like an object t_model_material_id material_id() const { return m_material_id; } void set_material_id(t_model_material_id material_id); void reset_extra_facets(); @@ -998,7 +1012,7 @@ public: void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } - Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } + const Vec3d &get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } @@ -1016,8 +1030,7 @@ public: void set_text_info(const TextInfo& text_info) { m_text_info = text_info; } const TextInfo& get_text_info() const { return m_text_info; } - const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - + const Transform3d &get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); @@ -1116,7 +1129,7 @@ private: name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), - m_text_info(other.m_text_info) + m_text_info(other.m_text_info), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); @@ -1136,7 +1149,8 @@ private: } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), + emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); @@ -1183,7 +1197,6 @@ private: auto tr = m_transformation; ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info); mesh_changed |= !(tr == m_transformation); - if (mesh_changed) m_transformation.get_matrix(true, true, true, true); // force dirty auto t = supported_facets.timestamp(); cereal::load_by_value(ar, supported_facets); mesh_changed |= t != supported_facets.timestamp(); @@ -1194,6 +1207,7 @@ private: cereal::load_by_value(ar, mmu_segmentation_facets); mesh_changed |= t != mmu_segmentation_facets.timestamp(); cereal::load_by_value(ar, config); + cereal::load(ar, emboss_shape); assert(m_mesh); if (has_convex_hull) { cereal::load_optional(ar, m_convex_hull); @@ -1212,6 +1226,7 @@ private: cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, mmu_segmentation_facets); cereal::save_by_value(ar, config); + cereal::save(ar, emboss_shape); if (has_convex_hull) cereal::save_optional(ar, m_convex_hull); } @@ -1273,12 +1288,12 @@ public: m_assemble_initialized = true; m_assemble_transformation = transformation; } - void set_assemble_from_transform(Transform3d& transform) { + void set_assemble_from_transform(const Transform3d& transform) { m_assemble_initialized = true; m_assemble_transformation.set_from_transform(transform); } const Vec3d& get_assemble_offset() {return m_assemble_transformation.get_offset(); } - void set_assemble_offset(const Vec3d& offset) { m_assemble_transformation.set_offset(offset); } + void set_assemble_offset(const Vec3d &offset){ m_assemble_initialized = true;m_assemble_transformation.set_offset(offset);} void set_assemble_rotation(const Vec3d &rotation) { m_assemble_transformation.set_rotation(rotation); } void rotate_assemble(double angle, const Vec3d& axis) { m_assemble_transformation.set_rotation(m_assemble_transformation.get_rotation() + Geometry::extract_euler_angles(Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)).toRotationMatrix())); @@ -1302,10 +1317,13 @@ public: // QDS void rotate(Matrix3d rotation_matrix) { - // note: must remove scaling from transformation, otherwise auto-orientation with scaled objects will have problem - auto R = get_matrix(true,false,true).matrix().block<3, 3>(0, 0); + auto R = m_transformation.get_rotation_matrix(); auto R_new = rotation_matrix * R; auto euler_angles = Geometry::extract_euler_angles(R_new); + //BOOST_LOG_TRIVIAL(debug) << "old R:\n" + // << R.matrix() << "\nnew R:\n" + // << R_new.matrix() << "\nold euler angles: " << m_transformation.get_rotation().transpose() << "\n" + // << "new euler angles: " << euler_angles.transpose(); set_rotation(euler_angles); } @@ -1328,13 +1346,13 @@ public: BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const; // Transform an external bounding box. BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; + BoundingBoxf3 transform_bounding_box_in_assembly_view(const BoundingBoxf3 &bbox, bool dont_translate = false) const; // Transform an external vector. Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const; // To be called on an external polygon. It does not translate the polygon, only rotates and scales. void transform_polygon(Polygon* polygon) const; - const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - + const Transform3d &get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } bool is_assemble_initialized() { return m_assemble_initialized; } @@ -1519,6 +1537,11 @@ public: std::string stl_design_id; std::string design_id; std::string stl_design_country; + + std::string makerlab_region; + std::string makerlab_name; + std::string makerlab_id; + std::shared_ptr design_info = nullptr; std::shared_ptr model_info = nullptr; std::shared_ptr profile_info = nullptr; @@ -1565,6 +1588,15 @@ public: OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) + static Model read_from_step(const std::string& input_file, + LoadStrategy options, + ImportStepProgressFn stepFn, + StepIsUtf8Fn stepIsUtf8Fn, + std::function step_mesh_fn, + double linear_defletion, + double angle_defletion); + + //QDS: add part plate related logic // QDS: backup //QDS: is_xxx is used for is_qds_3mf when loading 3mf, is used for is_inches when loading amf @@ -1574,8 +1606,6 @@ public: LoadStrategy options = LoadStrategy::AddDefaultInstances, PlateDataPtrs* plate_data = nullptr, std::vector* project_presets = nullptr, bool* is_xxx = nullptr, Semver* file_version = nullptr, Import3mfProgressFn proFn = nullptr, ImportstlProgressFn stlFn = nullptr, - ImportStepProgressFn stepFn = nullptr, - StepIsUtf8Fn stepIsUtf8Fn = nullptr, QDTProject * project = nullptr, int plate_id = 0, ObjImportColorFn objFn = nullptr @@ -1602,6 +1632,7 @@ public: ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); ModelObject* add_object(const ModelObject &other); + void set_assembly_pos(ModelObject * model_object); void delete_object(size_t idx); bool delete_object(ObjectID id); bool delete_object(ModelObject* object); @@ -1624,6 +1655,7 @@ public: bool add_default_instances(); // Returns approximate axis aligned bounding box of this model BoundingBoxf3 bounding_box() const; + BoundingBoxf3 bounding_box_in_assembly_view() const; // Set the print_volume_state of PrintObject::instances, // return total number of printable objects. unsigned int update_print_volume_state(const BuildVolume &build_volume); @@ -1728,6 +1760,8 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo // The function assumes that volumes list is synchronized. extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); +bool model_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new); + // If the model has multi-part objects, then it is currently not supported by the SLA mode. // Either the model cannot be loaded, or a SLA printer has to be activated. bool model_has_multi_part_objects(const Model &model); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index d389557..358d9f4 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -16,6 +16,7 @@ #include #include +//#define MM_SEGMENTATION_DEBUG_PAINT_LINE //#define MM_SEGMENTATION_DEBUG_GRAPH //#define MM_SEGMENTATION_DEBUG_REGIONS //#define MM_SEGMENTATION_DEBUG_INPUT @@ -1363,18 +1364,21 @@ static inline std::vector> mmu_segmentation_top_and_bott if (!zs.empty() && is_volume_sinking(painted, volume_trafo)) { std::vector zs_sinking = {0.f}; Slic3r::append(zs_sinking, zs); - slice_mesh_slabs(painted, zs_sinking, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback); + slice_mesh_slabs(painted, zs_sinking, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, nullptr, throw_on_cancel_callback); - MeshSlicingParams slicing_params; - slicing_params.trafo = volume_trafo; - Polygons bottom_slice = slice_mesh(painted, zs[0], slicing_params); + if (top.size() > 0) + top.erase(top.begin()); - top.erase(top.begin()); - bottom.erase(bottom.begin()); + if (bottom.size() > 1) { + MeshSlicingParams slicing_params; + slicing_params.trafo = volume_trafo; + Polygons bottom_slice = slice_mesh(painted, zs[0], slicing_params); - bottom[0] = union_(bottom[0], bottom_slice); + bottom.erase(bottom.begin()); + bottom[0] = union_(bottom[0], bottom_slice); + } } else - slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback); + slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, nullptr, throw_on_cancel_callback); auto merge = [](std::vector &&src, std::vector &dst) { auto it_src = find_if(src.begin(), src.end(), [](const Polygons &p){ return ! p.empty(); }); if (it_src != src.end()) { @@ -2151,23 +2155,31 @@ std::vector> multi_material_segmentation_by_painting(con if (layer_idx > 1) bbox.merge(layer_bboxes[layer_idx - 1]); if (layer_idx < num_layers - 1) bbox.merge(layer_bboxes[layer_idx + 1]); // Projected triangles may slightly exceed the input polygons. - bbox.offset(20 * SCALED_EPSILON); + bbox.offset(30 * SCALED_EPSILON); edge_grids[layer_idx].set_bbox(bbox); edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); } BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - begin"; for (const ModelVolume *mv : print_object.model_object()->volumes) { +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { +#else + for (size_t extruder_idx = 1; extruder_idx < num_extruders + 1; ++extruder_idx) { +#endif throw_on_cancel_callback(); const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); if (!mv->is_model_part() || custom_facets.indices.empty()) continue; const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE tbb::parallel_for(tbb::blocked_range(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &layers, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range &range) { for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) { +#else + for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { +#endif float min_z = std::numeric_limits::max(); float max_z = std::numeric_limits::lowest(); @@ -2225,8 +2237,7 @@ std::vector> multi_material_segmentation_by_painting(con // be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618). // To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases // when any of the endpoints of the line are outside the EdgeGrid's BoundingBox. - BoundingBox edge_grid_bbox = edge_grids[layer_idx].bbox(); - edge_grid_bbox.offset(10 * scale_(EPSILON)); + const BoundingBox& edge_grid_bbox = edge_grids[layer_idx].bbox(); if (!edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) { // If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line. if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) || @@ -2240,12 +2251,16 @@ std::vector> multi_material_segmentation_by_painting(con PaintedLineVisitor visitor(edge_grids[layer_idx], painted_lines[layer_idx], painted_lines_mutex[mutex_idx], 16); visitor.line_to_test = line_to_test; visitor.color = int(extruder_idx); - edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor); + edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor, true); } } +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE }); // end of parallel_for +#endif } +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE }); // end of parallel_for +#endif } BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - end"; BOOST_LOG_TRIVIAL(debug) << "MM segmentation - painted layers count: " diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp new file mode 100644 index 0000000..14b144d --- /dev/null +++ b/src/libslic3r/NSVGUtils.cpp @@ -0,0 +1,543 @@ +#include "NSVGUtils.hpp" +#include +#include // to_chars + +#include +#include +#include "ClipperUtils.hpp" +#include "Emboss.hpp" // heal for shape + +namespace { +using namespace Slic3r; // Polygon +// see function nsvg__lineTo(NSVGparser* p, float x, float y) +bool is_line(const float *p, float precision = 1e-4f); +// convert curve in path to lines +struct LinesPath{ + Polygons polygons; + Polylines polylines; }; +LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m); +HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m); +HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m); +} // namespace + +namespace Slic3r { + +ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m) +{ + ExPolygonsWithIds result; + size_t shape_id = 0; + for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) { + const NSVGshape &shape = *shape_ptr; + if (!(shape.flags & NSVG_FLAGS_VISIBLE)) + continue; + + bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE; + bool is_stroke_used = + shape.stroke.type != NSVG_PAINT_NONE && + shape.strokeWidth > 1e-5f; + + if (!is_fill_used && !is_stroke_used) + continue; + + const LinesPath lines_path = linearize_path(shape.paths, param); + + if (is_fill_used) { + unsigned unique_id = static_cast(2 * shape_id); + HealedExPolygons expoly = fill_to_expolygons(lines_path, shape, param); + result.push_back({unique_id, expoly.expolygons, expoly.is_healed}); + } + if (is_stroke_used) { + unsigned unique_id = static_cast(2 * shape_id + 1); + HealedExPolygons expoly = stroke_to_expolygons(lines_path, shape, param); + result.push_back({unique_id, expoly.expolygons, expoly.is_healed}); + } + } + + // SVG is used as centered + // Do not disturb user by settings of pivot position + center(result); + return result; +} + +Polygons to_polygons(const NSVGimage &image, const NSVGLineParams ¶m) +{ + Polygons result; + for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + if (shape->fill.type == NSVG_PAINT_NONE) + continue; + const LinesPath lines_path = linearize_path(shape->paths, param); + polygons_append(result, lines_path.polygons); + // close polyline to create polygon + polygons_append(result, to_polygons(lines_path.polylines)); + } + return result; +} + +void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max) +{ + for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) + for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) { + if (min.x() > path->bounds[0]) + min.x() = path->bounds[0]; + if (min.y() > path->bounds[1]) + min.y() = path->bounds[1]; + if (max.x() < path->bounds[2]) + max.x() = path->bounds[2]; + if (max.y() < path->bounds[3]) + max.y() = path->bounds[3]; + } +} + +NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi) +{ + NSVGimage *image = ::nsvgParseFromFile(filename.c_str(), units, dpi); + return {image, &nsvgDelete}; +} + +std::unique_ptr read_from_disk(const std::string &path) +{ + boost::nowide::ifstream fs{path}; + if (!fs.is_open()) + return nullptr; + std::stringstream ss; + ss << fs.rdbuf(); + return std::make_unique(ss.str()); +} + +NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float dpi){ + // NOTE: nsvg parser consume data from input(char *) + size_t size = file_data.size(); + // file data could be big, so it is allocated on heap + std::unique_ptr data_copy(new char[size+1]); + memcpy(data_copy.get(), file_data.c_str(), size); + data_copy[size] = '\0'; // data for nsvg must be null terminated + NSVGimage *image = ::nsvgParse(data_copy.get(), units, dpi); + return {image, &nsvgDelete}; +} + +NSVGimage *init_image(EmbossShape::SvgFile &svg_file){ + // is already initialized? + if (svg_file.image.get() != nullptr) + return svg_file.image.get(); + + if (svg_file.file_data == nullptr) { + // chech if path is known + if (svg_file.path.empty()) + return nullptr; + svg_file.file_data = read_from_disk(svg_file.path); + if (svg_file.file_data == nullptr) + return nullptr; + } + + // init svg image + svg_file.image = nsvgParse(*svg_file.file_data); + if (svg_file.image.get() == NULL) + return nullptr; + + return svg_file.image.get(); +} + +size_t get_shapes_count(const NSVGimage &image) +{ + size_t count = 0; + for (NSVGshape * s = image.shapes; s != NULL; s = s->next) + ++count; + return count; +} + +//void save(const NSVGimage &image, std::ostream &data) +//{ +// data << ""; +// +// // tl .. top left +// Vec2f tl(std::numeric_limits::max(), std::numeric_limits::max()); +// // br .. bottom right +// Vec2f br(std::numeric_limits::min(), std::numeric_limits::min()); +// bounds(image, tl, br); +// +// tl.x() = std::floor(tl.x()); +// tl.y() = std::floor(tl.y()); +// +// br.x() = std::ceil(br.x()); +// br.y() = std::ceil(br.y()); +// Vec2f s = br - tl; +// Point size = s.cast(); +// +// data << "\n"; +// data << "\n"; +// +// std::array buffer; +// auto write_point = [&tl, &buffer](std::string &d, const float *p) { +// float x = p[0] - tl.x(); +// float y = p[1] - tl.y(); +// auto to_string = [&buffer](float f) -> std::string { +// auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), f); +// if (ec != std::errc{}) +// return "0"; +// return std::string(buffer.data(), ptr); +// }; +// d += to_string(x) + "," + to_string(y) + " "; +// }; +// +// for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) { +// enum struct Type { move, line, curve, close }; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d +// Type type = Type::move; +// std::string d = "M "; // move on start point +// for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) { +// if (path->npts <= 1) +// continue; +// +// if (type == Type::close) { +// type = Type::move; +// // NOTE: After close must be a space +// d += " M "; // move on start point +// } +// write_point(d, path->pts); +// size_t path_size = static_cast(path->npts - 1); +// +// if (path->closed) { +// // Do not use last point in path it is duplicit +// if (path->npts <= 4) +// continue; +// path_size = static_cast(path->npts - 4); +// } +// +// for (size_t i = 0; i < path_size; i += 3) { +// const float *p = &path->pts[i * 2]; +// if (!::is_line(p)) { +// if (type != Type::curve) { +// type = Type::curve; +// d += "C "; // start sequence of triplets defining curves +// } +// write_point(d, &p[2]); +// write_point(d, &p[4]); +// } else { +// +// if (type != Type::line) { +// type = Type::line; +// d += "L "; // start sequence of line points +// } +// } +// write_point(d, &p[6]); +// } +// if (path->closed) { +// type = Type::close; +// d += "Z"; // start sequence of line points +// } +// } +// if (type != Type::close) { +// //type = Type::close; +// d += "Z"; // closed path +// } +// data << "\n"; +// } +// data << "\n"; +//} +// +//bool save(const NSVGimage &image, const std::string &svg_file_path) +//{ +// std::ofstream file{svg_file_path}; +// if (!file.is_open()) +// return false; +// save(image, file); +// return true; +//} +} // namespace Slic3r + +namespace { +using namespace Slic3r; // Polygon + Vec2f + +Point::coord_type to_coor(float val, double scale) { return static_cast(std::round(val * scale)); } + +bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) { + // f .. first + // s .. second + auto det = [](const Vec2f &f, const Vec2f &s) { + return std::fabs(f.x() * s.y() - f.y() * s.x()); + }; + + Vec2f pd = (p4 - p1); + Vec2f pd2 = (p2 - p4); + float d2 = det(pd2, pd); + Vec2f pd3 = (p3 - p4); + float d3 = det(pd3, pd); + float d23 = d2 + d3; + + return (d23 * d23) >= tessTol * pd.squaredNorm(); +} + +// see function nsvg__lineTo(NSVGparser* p, float x, float y) +bool is_line(const float *p, float precision){ + //Vec2f p1(p[0], p[1]); + //Vec2f p2(p[2], p[3]); + //Vec2f p3(p[4], p[5]); + //Vec2f p4(p[6], p[7]); + float dx_3 = (p[6] - p[0]) / 3.f; + float dy_3 = (p[7] - p[1]) / 3.f; + + return + is_approx(p[2], p[0] + dx_3, precision) && + is_approx(p[4], p[6] - dx_3, precision) && + is_approx(p[3], p[1] + dy_3, precision) && + is_approx(p[5], p[7] - dy_3, precision); +} + +/// +/// Convert cubic curve to lines +/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez +/// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335 +/// +/// Result points +/// Tesselation tolerance +/// Curve point +/// Curve point +/// Curve point +/// Curve point +/// Actual depth of recursion +void flatten_cubic_bez(Points &points, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level) +{ + if (!need_flattening(tessTol, p1, p2, p3, p4)) { + Point::coord_type x = static_cast(std::round(p4.x())); + Point::coord_type y = static_cast(std::round(p4.y())); + points.emplace_back(x, y); + return; + } + + --level; + if (level == 0) + return; + + Vec2f p12 = (p1 + p2) * 0.5f; + Vec2f p23 = (p2 + p3) * 0.5f; + Vec2f p34 = (p3 + p4) * 0.5f; + Vec2f p123 = (p12 + p23) * 0.5f; + Vec2f p234 = (p23 + p34) * 0.5f; + Vec2f p1234 = (p123 + p234) * 0.5f; + flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level); + flatten_cubic_bez(points, tessTol, p1234, p234, p34, p4, level); +} + +LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m) +{ + LinesPath result; + Polygons &polygons = result.polygons; + Polylines &polylines = result.polylines; + + // multiple use of allocated memmory for points between paths + Points points; + for (NSVGpath *path = first_path; path != NULL; path = path->next) { + // Flatten path + Point::coord_type x = to_coor(path->pts[0], param.scale); + Point::coord_type y = to_coor(path->pts[1], param.scale); + points.emplace_back(x, y); + size_t path_size = (path->npts > 1) ? static_cast(path->npts - 1) : 0; + for (size_t i = 0; i < path_size; i += 3) { + const float *p = &path->pts[i * 2]; + if (is_line(p)) { + // point p4 + Point::coord_type xx = to_coor(p[6], param.scale); + Point::coord_type yy = to_coor(p[7], param.scale); + points.emplace_back(xx, yy); + continue; + } + Vec2f p1(p[0], p[1]); + Vec2f p2(p[2], p[3]); + Vec2f p3(p[4], p[5]); + Vec2f p4(p[6], p[7]); + flatten_cubic_bez(points, param.tesselation_tolerance, + p1 * param.scale, p2 * param.scale, p3 * param.scale, p4 * param.scale, + param.max_level); + } + assert(!points.empty()); + if (points.empty()) + continue; + + if (param.is_y_negative) + for (Point &p : points) + p.y() = -p.y(); + + if (path->closed) { + polygons.emplace_back(points); + } else { + polylines.emplace_back(points); + } + // prepare for new path - recycle alocated memory + points.clear(); + } + remove_same_neighbor(polygons); + remove_same_neighbor(polylines); + return result; +} + +HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m) +{ + Polygons fill = lines_path.polygons; // copy + + // close polyline to create polygon + polygons_append(fill, to_polygons(lines_path.polylines)); + if (fill.empty()) + return {}; + + // if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO) + bool is_non_zero = true; + if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD) + is_non_zero = false; + + return Emboss::heal_polygons(fill, is_non_zero, param.max_heal_iteration); +} + +struct DashesParam{ + // first dash length + float dash_length = 1.f; // scaled + + // is current dash .. true + // is current space .. false + bool is_line = true; + + // current index to array + unsigned char dash_index = 0; + static constexpr size_t max_dash_array_size = 8; // limitation of nanosvg strokeDashArray + std::array dash_array; // scaled + unsigned char dash_count = 0; // count of values in array + + explicit DashesParam(const NSVGshape &shape, double scale) : + dash_count(shape.strokeDashCount) + { + assert(dash_count > 0); + assert(dash_count <= max_dash_array_size); // limitation of nanosvg strokeDashArray + for (size_t i = 0; i < dash_count; ++i) + dash_array[i] = static_cast(shape.strokeDashArray[i] * scale); + + // Figure out dash offset. + float all_dash_length = 0; + for (unsigned char j = 0; j < dash_count; ++j) + all_dash_length += dash_array[j]; + + if (dash_count%2 == 1) // (shape.strokeDashCount & 1) + all_dash_length *= 2.0f; + + // Find location inside pattern + float dash_offset = fmodf(static_cast(shape.strokeDashOffset * scale), all_dash_length); + if (dash_offset < 0.0f) + dash_offset += all_dash_length; + + while (dash_offset > dash_array[dash_index]) { + dash_offset -= dash_array[dash_index]; + dash_index = (dash_index + 1) % shape.strokeDashCount; + is_line = !is_line; + } + + dash_length = dash_array[dash_index] - dash_offset; + } +}; + +Polylines to_dashes(const Polyline &polyline, const DashesParam& param) +{ + Polylines dashes; + Polyline dash; // cache for one dash in dashed line + Point prev_point; + + bool is_line = param.is_line; + unsigned char dash_index = param.dash_index; + float dash_length = param.dash_length; // current rest of dash distance + for (const Point &point : polyline.points) { + if (&point == &polyline.points.front()) { + // is first point + prev_point = point; // copy + continue; + } + + Point diff = point - prev_point; + float line_segment_length = diff.cast().norm(); + while (dash_length < line_segment_length) { + // Calculate intermediate point + float d = dash_length / line_segment_length; + Point move_point = diff * d; + Point intermediate = prev_point + move_point; + + // add Dash in stroke + if (is_line) { + if (dash.empty()) { + dashes.emplace_back(Points{prev_point, intermediate}); + } else { + dash.append(prev_point); + dash.append(intermediate); + dashes.push_back(dash); + dash.clear(); + } + } + + diff -= move_point; + line_segment_length -= dash_length; + prev_point = intermediate; + + // Advance dash pattern + is_line = !is_line; + dash_index = (dash_index + 1) % param.dash_count; + dash_length = param.dash_array[dash_index]; + } + + if (is_line) + dash.append(prev_point); + dash_length -= line_segment_length; + prev_point = point; // copy + } + + // add last dash + if (is_line){ + assert(!dash.empty()); + dash.append(prev_point); // prev_point == polyline.points.back() + dashes.push_back(dash); + } + return dashes; +} + +HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m) +{ + // convert stroke to polygon + ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare; + switch (static_cast(shape.strokeLineJoin)) { + case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = ClipperLib::JoinType::jtSquare; break; + case NSVGlineJoin::NSVG_JOIN_MITER: join_type = ClipperLib::JoinType::jtMiter; break; + case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = ClipperLib::JoinType::jtRound; break; + } + + double mitter = shape.miterLimit * param.scale; + if (join_type == ClipperLib::JoinType::jtRound) { + // mitter is used as ArcTolerance + // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm + mitter = std::pow(param.tesselation_tolerance, 1/3.); + } + float stroke_width = static_cast(shape.strokeWidth * param.scale); + + ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt; + switch (static_cast(shape.strokeLineCap)) { + case NSVGlineCap::NSVG_CAP_BUTT: end_type = ClipperLib::EndType::etOpenButt; break; + case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break; + case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::etOpenSquare; break; + } + + Polygons result; + if (shape.strokeDashCount > 0) { + DashesParam params(shape, param.scale); + Polylines dashes; + for (const Polyline &polyline : lines_path.polylines) + polylines_append(dashes, to_dashes(polyline, params)); + for (const Polygon &polygon : lines_path.polygons) + polylines_append(dashes, to_dashes(to_polyline(polygon), params)); + result = offset(dashes, stroke_width / 2, join_type, mitter, end_type); + } else { + result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter); + polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type)); + } + + bool is_non_zero = true; + return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration); +} + +} // namespace diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp new file mode 100644 index 0000000..8d84815 --- /dev/null +++ b/src/libslic3r/NSVGUtils.hpp @@ -0,0 +1,82 @@ +#ifndef slic3r_NSVGUtils_hpp_ +#define slic3r_NSVGUtils_hpp_ + +#include +#include +#include +#include "Polygon.hpp" +#include "ExPolygon.hpp" +#include "EmbossShape.hpp" // ExPolygonsWithIds +#include "nanosvg/nanosvg.h" // load SVG file + +// Helper function to work with nano svg +namespace Slic3r { + +/// +/// Paramreters for conversion curve from SVG to lines in Polygon +/// +struct NSVGLineParams +{ + // Smaller will divide curve to more lines + // NOTE: Value is in image scale + double tesselation_tolerance = 10.f; + + // Maximal depth of recursion for conversion curve to lines + int max_level = 10; + + // Multiplicator of point coors + // NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer --> Slic3r::Point + double scale = 1. / SCALING_FACTOR; + + // Flag wether y is negative, when true than y coor is multiplied by -1 + bool is_y_negative = true; + + // Is used only with rounded Stroke + double arc_tolerance = 1.; + + // Maximal count of heal iteration + unsigned max_heal_iteration = 10; + + explicit NSVGLineParams(double tesselation_tolerance): + tesselation_tolerance(tesselation_tolerance), + arc_tolerance(std::pow(tesselation_tolerance, 1/3.)) + {} +}; + +/// +/// Convert .svg opened by nanoSvg to shapes stored in expolygons with ids +/// +/// Parsed svg file by NanoSvg +/// Smaller will divide curve to more lines +/// NOTE: Value is in image scale +/// Maximal depth for conversion curve to lines +/// Multiplicator of point coors +/// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer +/// Shapes from svg image - fill + stroke +ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m); + +// help functions - prepare to be tested +/// Flag is y negative, when true than y coor is multiplied by -1 +Polygons to_polygons(const NSVGimage &image, const NSVGLineParams ¶m); + +void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max); + +// read text data from file +std::unique_ptr read_from_disk(const std::string &path); + +using NSVGimage_ptr = std::unique_ptr; +NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f); +NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units = "mm", float dpi = 96.0f); +NSVGimage *init_image(EmbossShape::SvgFile &svg_file); + +/// +/// Iterate over shapes and calculate count +/// +/// Contain pointer to first shape +/// Count of shapes +size_t get_shapes_count(const NSVGimage &image); + +//void save(const NSVGimage &image, std::ostream &data); +//bool save(const NSVGimage &image, const std::string &svg_file_path); +} // namespace Slic3r +#endif // slic3r_NSVGUtils_hpp_ diff --git a/src/libslic3r/Orient.cpp b/src/libslic3r/Orient.cpp index 5747b00..21f044f 100644 --- a/src/libslic3r/Orient.cpp +++ b/src/libslic3r/Orient.cpp @@ -230,10 +230,10 @@ public: { std::unordered_map alignments; // init to 0 - for (size_t i = 0; i < areas_.size(); i++) + for (auto i = 0; i < areas_.size(); i++) alignments.insert(std::pair(normals_.row(i), 0)); // cumulate areas - for (size_t i = 0; i < areas_.size(); i++) + for (auto i = 0; i < areas_.size(); i++) { alignments[normals_.row(i)] += areas_(i); } @@ -257,11 +257,11 @@ public: Vec3f n1 = { 0, 0, 0 }; std::vector current_areas = {0, 0}; // init to 0 - for (size_t i = 0; i < areas_.size(); i++) { + for (auto i = 0; i < areas_.size(); i++) { alignments_.insert(std::pair(quantize_normals_.row(i), std::pair(current_areas, n1))); } // cumulate areas - for (size_t i = 0; i < areas_.size(); i++) + 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]){ @@ -339,7 +339,7 @@ public: z_max_hull.resize(mesh_convex_hull.facets_count(), 1); its = mesh_convex_hull.its; - for (size_t i = 0; i < z_max_hull.rows(); i++) + for (auto i = 0; i < z_max_hull.rows(); i++) { float z0 = its.get_vertex(i,0).dot(orientation); float z1 = its.get_vertex(i,1).dot(orientation); @@ -393,7 +393,7 @@ public: // filter overhang Eigen::VectorXf normal_projection(normals.rows(), 1);// = this->normals.dot(orientation); - for (size_t i = 0; i < normals.rows(); i++) + for (auto i = 0; i < normals.rows(); i++) { normal_projection(i) = normals.row(i).dot(orientation); } @@ -497,7 +497,8 @@ 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); - 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.transpose() << "; axis: " << mesh_.axis.transpose() << "; angle: " << mesh_.angle + << "; euler: " << mesh_.euler_angles.transpose() << ", rotation_matrix:\n" << mesh_.rotation_matrix; }}); } } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index b4b590b..10797b0 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -82,7 +82,7 @@ static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuz double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); } - dist_left_over = p0p1_size - p0pa_dist; + dist_left_over = p0pa_dist - p0p1_size; p0 = &p1; } while (out.size() < 3) { @@ -341,8 +341,7 @@ public: static std::deque detect_overahng_degree(Polygons lower_polygons, Polylines middle_overhang_polyines, const double &lower_bound, - const double &upper_bound, - Polylines &too_short_polylines) + const double &upper_bound) { // QDS: collect lower_polygons points //Polylines; @@ -357,10 +356,6 @@ static std::deque detect_overahng_degree(Polygons low for (size_t polyline_idx = 0; polyline_idx < middle_overhang_polyines.size(); ++polyline_idx) { //filter too short polyline Polyline middle_poly = middle_overhang_polyines[polyline_idx]; - if (middle_poly.length() < scale_(1.0)) { - too_short_polylines.push_back(middle_poly); - continue; - } Polyline polyline_with_insert_points; points_overhang.clear(); @@ -472,6 +467,13 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime loop_role = loop.is_contour? elrDefault: elrPerimeterHole; } + if( loop.depth == 1 ) { + if (loop_role == elrDefault) + loop_role = elrSecondPerimeter; + else + loop_role = loop_role | elrSecondPerimeter; + } + // detect overhanging/bridging perimeters ExtrusionPaths paths; @@ -519,13 +521,29 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime //QDS: don't calculate overhang degree when enable fuzzy skin. It's unmeaning Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_polygons_series->back(), bbox); - Polylines inside_polines = intersection_pl({polygon}, lower_polygons_series_clipped); + Polylines inside_polines = intersection_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped); - remain_polines = diff_pl({polygon}, lower_polygons_series_clipped); + remain_polines = diff_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped); bool detect_overhang_degree = perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None; + //QDS: fuzziy skin may generate a line that approximates a point, which can cause the clipper to get empty results + if (loop.fuzzify && remain_polines.empty() && inside_polines.empty()) { + bool inside_contour = false; + for (const Polygon cliped_polygon : lower_polygons_series_clipped) { + if (cliped_polygon.contains(polygon.first_point())) { + inside_contour = true; + break; + } + } + + if (inside_contour) + inside_polines.push_back(to_polyline(polygon)); + else + remain_polines.push_back(to_polyline(polygon)); + } + if (!detect_overhang_degree) { if (!inside_polines.empty()) extrusion_paths_append( @@ -540,9 +558,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_polygons_series->front(), bbox); - Polylines middle_overhang_polyines = diff_pl({inside_polines}, lower_polygons_series_clipped); + Polylines middle_overhang_polyines = diff_pl_2(inside_polines, lower_polygons_series_clipped); //QDS: add zero_degree_path - Polylines zero_degree_polines = intersection_pl({inside_polines}, lower_polygons_series_clipped); + Polylines zero_degree_polines = intersection_pl_2(inside_polines, lower_polygons_series_clipped); if (!zero_degree_polines.empty()) extrusion_paths_append( paths, @@ -555,21 +573,11 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime (float)perimeter_generator.layer_height); //QDS: detect middle line overhang if (!middle_overhang_polyines.empty()) { - Polylines too_short_polylines; std::deque polylines_degree_collection = detect_overahng_degree(lower_polygons_series->front(), middle_overhang_polyines, overhang_dist_boundary->first, - overhang_dist_boundary->second, - too_short_polylines); - if (!too_short_polylines.empty()) - extrusion_paths_append(paths, - std::move(too_short_polylines), - 0, - int(0), - role, - extrusion_mm3_per_mm, - extrusion_width, - (float) perimeter_generator.layer_height); + overhang_dist_boundary->second); + // QDS: add path with overhang degree for (PolylineWithDegree polylines_collection : polylines_degree_collection) { extrusion_paths_append(paths, @@ -962,9 +970,9 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p for (int j = 0; j < overhang_sampling_number; j++) { Polygons limiton_polygons = offset(lower_slcier_chopped, float(scale_(start_pos + (j + 0.5) * (end_pos - start_pos) / (overhang_sampling_number - 1)))); - Polylines inside_polines = j == 0 ? intersection_pl(be_clipped, limiton_polygons) : intersection_pl(remain_polylines, limiton_polygons); + Polylines inside_polines = j == 0 ? intersection_pl_2(be_clipped, limiton_polygons) : intersection_pl_2(remain_polylines, limiton_polygons); - remain_polylines = j == 0 ? diff_pl(be_clipped, limiton_polygons) : diff_pl(remain_polylines, limiton_polygons); + remain_polylines = j == 0 ? diff_pl_2(be_clipped, limiton_polygons) : diff_pl_2(remain_polylines, limiton_polygons); extrusion_paths_append(paths, std::move(inside_polines), j, int(0), role, it.second.front().mm3_per_mm, it.second.front().width, it.second.front().height); @@ -1327,22 +1335,25 @@ void PerimeterGenerator::process_classic() // get the real top surface ExPolygons grown_lower_slices; ExPolygons bridge_checker; + + ExPolygons top_polygons = diff_ex(last, upper_polygons_series_clipped, ApplySafetyOffset::Yes); + //get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the min_width_top_surface we removed a bit before) + ExPolygons temp_gap = diff_ex(top_polygons, fill_clip); + ExPolygons inner_polygons = diff_ex(last, + offset_ex(top_polygons, offset_top_surface + min_width_top_surface - double(ext_perimeter_spacing / 2)), + ApplySafetyOffset::Yes); // QDS: check whether surface be bridge or not if (this->lower_slices != NULL) { // QDS: get the Polygons below the polygon this layer Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, last_box); double bridge_offset = std::max(double(ext_perimeter_spacing), (double(perimeter_width))); - bridge_checker = offset_ex(diff_ex(last, lower_polygons_series_clipped, ApplySafetyOffset::Yes), 1.5 * bridge_offset); - } - ExPolygons delete_bridge = diff_ex(last, bridge_checker, ApplySafetyOffset::Yes); + bridge_checker = offset_ex(diff_ex(last, lower_polygons_series_clipped, ApplySafetyOffset::Yes), 1.5 * bridge_offset); - ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes); - //get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the min_width_top_surface we removed a bit before) - ExPolygons temp_gap = diff_ex(top_polygons, fill_clip); - ExPolygons inner_polygons = diff_ex(last, - offset_ex(top_polygons, offset_top_surface + min_width_top_surface - double(ext_perimeter_spacing / 2)), - ApplySafetyOffset::Yes); + // QDS: Check which piece the bridge belongs to. If the bridge has a connection with the non-top area, it should belong to the non-top area, otherwise it should belong to the top area to get a better surface. + if (!bridge_checker.empty() && !intersection_ex(bridge_checker, inner_polygons).empty()) + inner_polygons = union_ex(inner_polygons, bridge_checker); + } // get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly the polygons to fill. top_polygons = diff_ex(fill_clip, inner_polygons, ApplySafetyOffset::Yes); // increase by half peri the inner space to fill the frontier between last and stored. @@ -1443,13 +1454,22 @@ void PerimeterGenerator::process_classic() //QDS. adjust wall generate seq else if (this->object_config->wall_sequence == WallSequence::InnerOuterInner) if (entities.entities.size() > 1){ - int last_outer=0; - int outer = 0; - for (; outer < entities.entities.size(); ++outer) - if (entities.entities[outer]->role() == erExternalPerimeter && outer - last_outer > 1) { - std::swap(entities.entities[outer], entities.entities[outer - 1]); - last_outer = outer; + int second_wall = -1; + ExtrusionEntitiesPtr entities_reorder; + ExtrusionEntitiesPtr entities_second_wall; + for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) { + ExtrusionLoop *eloop = static_cast(entities.entities[entity_idx]); + if (eloop->loop_role() & elrSecondPerimeter) { + entities_second_wall.push_back(entities.entities[entity_idx]); + } else { + entities_reorder.push_back(entities.entities[entity_idx]); + if (entities.entities[entity_idx]->role() == erExternalPerimeter && !entities_second_wall.empty()) { + entities_reorder.insert(entities_reorder.end(), entities_second_wall.begin(), entities_second_wall.end()); + entities_second_wall.clear(); + } } + } + entities.entities = std::move( entities_reorder); } // append perimeters for this slice as a collection if (! entities.empty()) @@ -1662,7 +1682,7 @@ void PerimeterGenerator::process_arachne() } int remain_loops = -1; - if (this->object_config->top_one_wall_type == TopOneWallType::Alltop) { + if (loop_number > 0 && this->object_config->top_one_wall_type == TopOneWallType::Alltop) { if (this->upper_slices != nullptr) remain_loops = loop_number - 1; diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 8799ad0..a43fab7 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -70,24 +70,24 @@ int Point::nearest_point_index(const PointConstPtrs &points) const { int idx = -1; double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough - + for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) { /* If the X distance of the candidate is > than the total distance of the best previous candidate, we know we don't want it */ double d = sqr((*this)(0) - (*it)->x()); if (distance != -1 && d > distance) continue; - + /* If the Y distance of the candidate is > than the total distance of the best previous candidate, we know we don't want it */ d += sqr((*this)(1) - (*it)->y()); if (distance != -1 && d > distance) continue; - + idx = it - points.begin(); distance = d; - + if (distance < EPSILON) break; } - + return idx; } @@ -142,7 +142,7 @@ Point Point::projection_onto(const MultiPoint &poly) const { Point running_projection = poly.first_point(); double running_min = (running_projection - *this).cast().norm(); - + Lines lines = poly.lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point point_temp = this->projection_onto(*line); @@ -157,7 +157,7 @@ Point Point::projection_onto(const MultiPoint &poly) const Point Point::projection_onto(const Line &line) const { if (line.a == line.b) return line.a; - + /* (Ported from VisiLibity by Karl J. Obermeyer) The projection of point_temp onto the line determined by @@ -174,7 +174,7 @@ Point Point::projection_onto(const Line &line) const if (0.0 <= theta && theta <= 1.0) return (theta * line.a.cast() + (1.0-theta) * line.b.cast()).cast(); - + // Else pick closest endpoint. return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b; } @@ -188,6 +188,23 @@ bool has_duplicate_points(std::vector &&pts) return false; } +Points collect_duplicates(Points pts /* Copy */) +{ + std::sort(pts.begin(), pts.end()); + Points duplicits; + const Point *prev = &pts.front(); + for (size_t i = 1; i < pts.size(); ++i) { + const Point *act = &pts[i]; + if (*prev == *act) { + // duplicit point + if (!duplicits.empty() && duplicits.back() == *act) continue; // only unique duplicits + duplicits.push_back(*act); + } + prev = act; + } + return duplicits; +} + template BoundingBox get_extents(const Points &pts) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 24220b6..1e97e70 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -143,6 +143,30 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + floa std::vector transform(const std::vector& points, const Transform3f& t); Pointf3s transform(const Pointf3s& points, const Transform3d& t); +/// +/// Check whether transformation matrix contains odd number of mirroring. +/// NOTE: In code is sometime function named is_left_handed +/// +/// Transformation to check +/// Is positive determinant +inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; } + +/// +/// Getter on base of transformation matrix +/// +/// column index +/// source transformation +/// Base of transformation matrix +inline const Vec3d get_base(unsigned index, const Transform3d &transform) { return transform.linear().col(index); } +inline const Vec3d get_x_base(const Transform3d &transform) { return get_base(0, transform); } +inline const Vec3d get_y_base(const Transform3d &transform) { return get_base(1, transform); } +inline const Vec3d get_z_base(const Transform3d &transform) { return get_base(2, transform); } +inline const Vec3d get_base(unsigned index, const Transform3d::LinearPart &transform) { return transform.col(index); } +inline const Vec3d get_x_base(const Transform3d::LinearPart &transform) { return get_base(0, transform); } +inline const Vec3d get_y_base(const Transform3d::LinearPart &transform) { return get_base(1, transform); } +inline const Vec3d get_z_base(const Transform3d::LinearPart &transform) { return get_base(2, transform); } + + template using Vec = Eigen::Matrix; class Point : public Vec2crd @@ -311,6 +335,9 @@ inline bool has_duplicate_successive_points_closed(const std::vector &pts return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); } +// Collect adjecent(duplicit points) +Points collect_duplicates(Points pts /* Copy */); + inline bool shorter_then(const Point& p0, const coord_t len) { if (p0.x() > len || p0.x() < -len) @@ -559,7 +586,29 @@ inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) { return base + align_to_grid(coord - base, spacing); } inline Point align_to_grid(Point coord, Point spacing, Point base) { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } - + // MinMaxLimits + template struct MinMax + { + T min; + T max; + }; + template static bool apply(std::optional &val, const MinMax &limit) + { + if (!val.has_value()) return false; + return apply(*val, limit); + } + template static bool apply(T &val, const MinMax &limit) + { + if (val > limit.max) { + val = limit.max; + return true; + } + if (val < limit.min) { + val = limit.min; + return true; + } + return false; + } } // namespace Slic3r // start Boost diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 0f2be6f..376dbc6 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -237,7 +237,7 @@ Points filter_points_by_vectors(const Points &poly, FilterFn filter) // p2 is next point to the currently visited point p1. Vec2d v2 = (p2 - p1).cast(); if (filter(v1, v2)) - out.emplace_back(p2); + out.emplace_back(p1); v1 = v2; p1 = p2; } @@ -249,7 +249,7 @@ template Points filter_convex_concave_points_by_angle_threshold(const Points &poly, double angle_threshold, ConvexConcaveFilterFn convex_concave_filter) { assert(angle_threshold >= 0.); - if (angle_threshold < EPSILON) { + if (angle_threshold > EPSILON) { double cos_angle = cos(angle_threshold); return filter_points_by_vectors(poly, [convex_concave_filter, cos_angle](const Vec2d &v1, const Vec2d &v2){ return convex_concave_filter(v1, v2) && v1.normalized().dot(v2.normalized()) < cos_angle; @@ -454,6 +454,32 @@ bool has_duplicate_points(const Polygons &polys) #endif } +bool remove_same_neighbor(Polygon &polygon) +{ + Points &points = polygon.points; + if (points.empty()) return false; + auto last = std::unique(points.begin(), points.end()); + + // remove first and last neighbor duplication + if (const Point &last_point = *(last - 1); last_point == points.front()) { --last; } + + // no duplicits + if (last == points.end()) return false; + + points.erase(last, points.end()); + return true; +} + +bool remove_same_neighbor(Polygons &polygons) +{ + if (polygons.empty()) return false; + bool exist = false; + for (Polygon &polygon : polygons) exist |= remove_same_neighbor(polygon); + // remove empty polygons + polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.points.size() <= 2; }), polygons.end()); + return exist; +} + static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3) { Point v1 = p2 - p1; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 2657db8..ae6f71d 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -112,6 +112,10 @@ inline bool has_duplicate_points(Polygon &&poly) { return has_duplicate_poi inline bool has_duplicate_points(const Polygon &poly) { return has_duplicate_points(poly.points); } bool has_duplicate_points(const Polygons &polys); +// Return True when erase some otherwise False. +bool remove_same_neighbor(Polygon &polygon); +bool remove_same_neighbor(Polygons &polygons); + inline double total_length(const Polygons &polylines) { double total = 0; for (Polygons::const_iterator it = polylines.begin(); it != polylines.end(); ++it) @@ -245,6 +249,18 @@ inline Polylines to_polylines(Polygons &&polys) return polylines; } +// close polyline to polygon (connect first and last point in polyline) +inline Polygons to_polygons(const Polylines &polylines) +{ + Polygons out; + out.reserve(polylines.size()); + for (const Polyline &polyline : polylines) { + if (polyline.size()) out.emplace_back(polyline.points); + } + return out; +} + + inline Polygons to_polygons(const std::vector &paths) { Polygons out; @@ -270,6 +286,21 @@ bool polygons_match(const Polygon &l, const Polygon &r); Polygon make_circle(double radius, double error); Polygon make_circle_num_segments(double radius, size_t num_segments); +/// +/// Define point laying on polygon +/// keep index of polygon line and point coordinate +/// +struct PolygonPoint +{ + // index of line inside of polygon + // 0 .. from point polygon[0] to polygon[1] + size_t index; + + // Point, which lay on line defined by index + Point point; +}; +using PolygonPoints = std::vector; + bool overlaps(const Polygons& polys1, const Polygons& polys2); } // Slic3r diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 00c5885..2f24e04 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -530,6 +530,30 @@ BoundingBox get_extents(const Polylines &polylines) return bb; } +// Return True when erase some otherwise False. +bool remove_same_neighbor(Polyline &polyline) +{ + Points &points = polyline.points; + if (points.empty()) return false; + auto last = std::unique(points.begin(), points.end()); + + // no duplicits + if (last == points.end()) return false; + + points.erase(last, points.end()); + return true; +} + +bool remove_same_neighbor(Polylines &polylines) +{ + if (polylines.empty()) return false; + bool exist = false; + for (Polyline &polyline : polylines) exist |= remove_same_neighbor(polyline); + // remove empty polylines + polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [](const Polyline &p) { return p.points.size() <= 1; }), polylines.end()); + return exist; +} + const Point& leftmost_point(const Polylines &polylines) { if (polylines.empty()) diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 9902ede..7b12c6d 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -154,6 +154,11 @@ public: extern BoundingBox get_extents(const Polyline &polyline); extern BoundingBox get_extents(const Polylines &polylines); +// Return True when erase some otherwise False. +bool remove_same_neighbor(Polyline &polyline); +bool remove_same_neighbor(Polylines &polylines); + + inline double total_length(const Polylines &polylines) { double total = 0; for (const Polyline &pl : polylines) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index a970353..a9be36d 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -495,11 +495,11 @@ void Preset::remove_files() } //QDS: add logic for only difference save -void Preset::save(DynamicPrintConfig* parent_config) +bool Preset::save(DynamicPrintConfig* parent_config) { //QDS: add project embedded preset logic if (this->is_project_embedded) - return; + return false; //QDS: change to json format //this->config.save(this->file); std::string from_str; @@ -512,7 +512,11 @@ void Preset::save(DynamicPrintConfig* parent_config) else from_str = std::string("Default"); - boost::filesystem::create_directories(fs::path(this->file).parent_path()); + boost::system::error_code ec; + if (!boost::filesystem::exists(fs::path(this->file).parent_path()) && !boost::filesystem::create_directories(fs::path(this->file).parent_path(), ec)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << this->file << " " << ec.message(); + return false; + } //QDS: only save difference if it has parent if (parent_config) { @@ -538,6 +542,7 @@ void Preset::save(DynamicPrintConfig* parent_config) fs::path idx_file(this->file); idx_file.replace_extension(".info"); this->save_info(idx_file.string()); + return true; } void Preset::reload(Preset const &parent) @@ -802,7 +807,7 @@ static std::vector s_Preset_print_options { "seam_position", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle", "minimum_sparse_infill_area", "reduce_infill_retraction", "ironing_pattern", "ironing_type", - "ironing_flow", "ironing_speed", "ironing_spacing","ironing_direction", + "ironing_flow", "ironing_speed", "ironing_spacing","ironing_direction", "ironing_inset", "max_travel_detour_distance", "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", #ifdef HAS_PRESSURE_EQUALIZER @@ -833,9 +838,8 @@ static std::vector s_Preset_print_options { "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "flush_into_infill", "flush_into_objects", "flush_into_support","process_notes", // QDS - "tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance", - //1.9.5 - "tree_support_branch_diameter", + "tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance", "tree_support_branch_diameter", + "tree_support_branch_diameter_angle", "detect_narrow_internal_solid_infill", "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination", /*"adaptive_layer_height",*/ //1.9.5 @@ -851,8 +855,8 @@ static std::vector s_Preset_print_options { // calib "print_flow_ratio", //Orca - "exclude_object", "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold", "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", - "seam_slope_steps", "seam_slope_inner_walls" + "exclude_object", /*"seam_slope_type",*/ "seam_slope_conditional", "scarf_angle_threshold", /*"seam_slope_start_height", */"seam_slope_entire_loop",/* "seam_slope_min_length",*/ + "seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed"/*, "seam_slope_gap"*/ //w16 ,"resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed" //w13 @@ -860,16 +864,17 @@ static std::vector s_Preset_print_options { }; static std::vector s_Preset_filament_options { - /*"filament_colour", */ "default_filament_colour","required_nozzle_HRC","filament_diameter", "filament_type", "filament_soluble", "filament_is_support", + /*"filament_colour", */ "default_filament_colour","required_nozzle_HRC","filament_diameter", "filament_type", "filament_soluble", "filament_is_support","filament_scarf_seam_type", "filament_scarf_height", "filament_scarf_gap","filament_scarf_length", "filament_max_volumetric_speed", "filament_flow_ratio", "filament_density", "filament_cost", "filament_minimal_purge_on_wipe_tower", "nozzle_temperature", "nozzle_temperature_initial_layer", // QDS "cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer","textured_plate_temp_initial_layer", + "supertack_plate_temp_initial_layer", "supertack_plate_temp", // "bed_type", //QDS:temperature_vitrification "temperature_vitrification", "reduce_fan_stop_start_freq", "slow_down_for_layer_cooling", "fan_min_speed", - "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", + "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "overhang_threshold_participating_cooling","close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", "filament_start_gcode", "filament_end_gcode", //exhaust fan control "activate_air_filtration","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", @@ -883,7 +888,7 @@ static std::vector s_Preset_filament_options { "nozzle_temperature_range_low", "nozzle_temperature_range_high", //OrcaSlicer "enable_pressure_advance", "pressure_advance", "chamber_temperatures","filament_notes", - "filament_long_retractions_when_cut","filament_retraction_distances_when_cut", + "filament_long_retractions_when_cut","filament_retraction_distances_when_cut","filament_shrink", //w13 "additional_cooling_fan_speed_unseal" //w14 @@ -902,7 +907,7 @@ static std::vector s_Preset_printer_options { "printer_technology", "printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", "single_extruder_multi_material", "machine_start_gcode", "machine_end_gcode","printing_by_object_gcode","before_layer_change_gcode", "layer_change_gcode", "time_lapse_gcode", "change_filament_gcode", - "printer_model", "printer_variant", "printable_height", "extruder_clearance_radius", "extruder_clearance_max_radius","extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", + "printer_model", "printer_variant", "printable_height", "extruder_clearance_dist_to_rod", "extruder_clearance_max_radius","extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", "nozzle_height", "default_print_profile", "inherits", "silent_mode", @@ -1342,6 +1347,14 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map 350 * 1024) return -2; + return 0; } @@ -1496,7 +1509,10 @@ void PresetCollection::set_sync_info_and_save(std::string name, std::string sett preset->setting_id = setting_id; if (update_time > 0) preset->updated_time = update_time; - preset->sync_info == "update" ? preset->save(nullptr) : preset->save_info(); + if (preset->sync_info == "update") + preset->save(nullptr); + else + preset->save_info(); break; } } @@ -2254,7 +2270,7 @@ std::map> PresetCollection::get_filamen } //QDS: add project embedded preset logic -void PresetCollection::save_current_preset(const std::string &new_name, bool detach, bool save_to_project, Preset* _curr_preset) +void PresetCollection::save_current_preset(const std::string &new_name, bool detach, bool save_to_project, Preset *_curr_preset, std::map *extra_map) { Preset curr_preset = _curr_preset ? *_curr_preset : m_edited_preset; //QDS: add lock logic for sync preset in background @@ -2293,8 +2309,14 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det preset.config.option("print_settings_id", true)->value = new_name; else if (m_type == Preset::TYPE_FILAMENT) preset.config.option("filament_settings_id", true)->values[0] = new_name; - else if (m_type == Preset::TYPE_PRINTER) + else if (m_type == Preset::TYPE_PRINTER) { preset.config.option("printer_settings_id", true)->value = new_name; + if (extra_map) { + for (auto iter : *extra_map) { + preset.config.option(iter.first, true)->value = iter.second; + } + } + } final_inherits = preset.inherits(); unlock(); // TODO: apply change from custom root to devided presets. @@ -2338,8 +2360,14 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det preset.config.option("print_settings_id", true)->value = new_name; else if (m_type == Preset::TYPE_FILAMENT) preset.config.option("filament_settings_id", true)->values[0] = new_name; - else if (m_type == Preset::TYPE_PRINTER) + else if (m_type == Preset::TYPE_PRINTER) { preset.config.option("printer_settings_id", true)->value = new_name; + if (extra_map) { + for (auto iter : *extra_map) { + preset.config.option(iter.first, true)->value = iter.second; + } + } + } //QDS: add lock logic for sync preset in background final_inherits = inherits; unlock(); @@ -2688,6 +2716,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi case coFloats: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; case coStrings: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; case coPercents:add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coFloatsOrPercents: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; case coPoints: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; // QDS case coEnums: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 0a57ec6..f7e3caa 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -258,7 +258,7 @@ public: //QDS: add logic for only difference save //if parent_config is null, save all keys, otherwise, only save difference - void save(DynamicPrintConfig* parent_config); + bool save(DynamicPrintConfig* parent_config); void reload(Preset const & parent); // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. @@ -507,7 +507,7 @@ public: // a new preset is stored into the list of presets. // All presets are marked as not modified and the new preset is activated. //QDS: add project embedded preset logic - void save_current_preset(const std::string &new_name, bool detach = false, bool save_to_project = false, Preset* _curr_preset = nullptr); + void save_current_preset(const std::string &new_name, bool detach = false, bool save_to_project = false, Preset *_curr_preset = nullptr, std::map* extra_map =nullptr); // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 70efc9a..18faa82 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1764,6 +1764,7 @@ unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns) std::vector filament_presets; std::vector filament_colors; ams_multi_color_filment.clear(); + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_ams_list size: %1%") % filament_ams_list.size(); for (auto &entry : filament_ams_list) { auto & ams = entry.second; auto filament_id = ams.opt_string("filament_id", 0u); @@ -1880,7 +1881,7 @@ std::set PresetBundle::get_printer_names_by_printer_type_and_nozzle if (printer_it->name.find(nozzle_diameter_str) != std::string::npos) printer_names.insert(printer_it->name); } - //assert(printer_names.size() == 1); + assert(printer_names.size() == 1); for (auto& printer_name : printer_names) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " printer name: " << printer_name; @@ -3371,7 +3372,8 @@ std::pair PresetBundle::load_vendor_configs_ std::map& config_maps, std::map& filament_id_maps, PresetCollection* presets_collection, - size_t& count) -> std::string { + size_t& count, + std::map&description_maps) -> std::string { std::string subfile = path + "/" + vendor_name + "/" + subfile_iter.second; // Load the print, filament or printer preset. @@ -3393,7 +3395,6 @@ std::pair PresetBundle::load_vendor_configs_ return reason; } preset_name = key_values[QDT_JSON_KEY_NAME]; - description = key_values[QDT_JSON_KEY_DESCRIPTION]; instantiation = key_values[QDT_JSON_KEY_INSTANTIATION]; auto setting_it = key_values.find(QDT_JSON_KEY_SETTING_ID); if (setting_it != key_values.end()) @@ -3421,6 +3422,8 @@ std::pair PresetBundle::load_vendor_configs_ reason = "Can not find inherits: " + inherits; return reason; } + if (auto ds_iter = description_maps.find(inherits); ds_iter != description_maps.end()) + description = ds_iter->second; } else { if (presets_collection->type() == Preset::TYPE_PRINTER) @@ -3429,6 +3432,13 @@ std::pair PresetBundle::load_vendor_configs_ default_config = &presets_collection->default_preset().config; } config = *default_config; + + if ( auto ds_iter=key_values.find(QDT_JSON_KEY_DESCRIPTION); ds_iter != key_values.end()) + description = ds_iter->second; + + if (!description.empty()) + description_maps.emplace(preset_name, description); + config.apply(config_src); if (instantiation == "false" && "Template" != vendor_name) { config_maps.emplace(preset_name, std::move(config)); @@ -3553,13 +3563,14 @@ std::pair PresetBundle::load_vendor_configs_ std::map configs; std::map filament_id_maps; + std::map description_maps; //3.1) paste the process presets = &this->prints; configs.clear(); filament_id_maps.clear(); for (auto& subfile : process_subfiles) { - std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded); + std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded, description_maps); if (!reason.empty()) { //parse error std::string subfile_path = path + "/" + vendor_name + "/" + subfile.second; @@ -3574,7 +3585,7 @@ std::pair PresetBundle::load_vendor_configs_ filament_id_maps.clear(); for (auto& subfile : filament_subfiles) { - std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded); + std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded, description_maps); if (!reason.empty()) { //parse error std::string subfile_path = path + "/" + vendor_name + "/" + subfile.second; @@ -3589,7 +3600,7 @@ std::pair PresetBundle::load_vendor_configs_ filament_id_maps.clear(); for (auto& subfile : machine_subfiles) { - std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded); + std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded, description_maps); if (!reason.empty()) { //parse error std::string subfile_path = path + "/" + vendor_name + "/" + subfile.second; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c207b7d..8de33d2 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -91,6 +91,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "enable_overhang_bridge_fan" "overhang_fan_speed", "overhang_fan_threshold", + "overhang_threshold_participating_cooling", "slow_down_for_layer_cooling", "default_acceleration", "deretraction_speed", @@ -101,7 +102,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "post_process", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid", - "extruder_clearance_radius", + "extruder_clearance_dist_to_rod", "nozzle_height", "extruder_clearance_max_radius", "extruder_colour", @@ -121,6 +122,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "accel_to_decel_enable", "accel_to_decel_factor", // QDS + "supertack_plate_temp_initial_layer", "cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer", @@ -224,6 +226,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "nozzle_diameter" || opt_key == "resolution" || opt_key == "precise_z_height" + || opt_key == "filament_shrink" // Spiral Vase forces different kind of slicing than the normal model: // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. // Therefore toggling the Spiral Vase on / off requires complete reslicing. @@ -239,6 +242,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "single_extruder_multi_material" || opt_key == "nozzle_temperature" // QDS + || opt_key == "supertack_plate_temp" || opt_key == "cool_plate_temp" || opt_key == "eng_plate_temp" || opt_key == "hot_plate_temp" @@ -266,6 +270,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n steps.emplace_back(psSkirtBrim); } else if (opt_key == "filament_soluble" || opt_key == "filament_is_support" + || opt_key == "filament_scarf_seam_type" + || opt_key == "filament_scarf_height" + || opt_key == "filament_scarf_gap" + || opt_key == "filament_scarf_length" || opt_key == "independent_support_layer_height") { steps.emplace_back(psWipeTower); // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. @@ -538,7 +546,7 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print bool all_objects_are_short = print.is_all_objects_are_short(); // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - float obj_distance = all_objects_are_short ? scale_(0.5*MAX_OUTER_NOZZLE_RADIUS-0.1) : scale_(0.5*print.config().extruder_clearance_radius.value-0.1); + float obj_distance = all_objects_are_short ? scale_(0.5*MAX_OUTER_NOZZLE_RADIUS-0.1) : scale_(0.5*print.config().extruder_clearance_max_radius.value-0.1); for (const PrintObject *print_object : print.objects()) { assert(! print_object->model_object()->instances.empty()); @@ -771,7 +779,7 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print { auto inst = print_instance_with_bounding_box[k].print_instance; // 只需要考虑喷嘴到滑杆的偏移量,这个比整个工具头的碰撞半径要小得多 - auto bbox = print_instance_with_bounding_box[k].bounding_box.inflated(-scale_(0.5 * print.config().extruder_clearance_max_radius.value)); + auto bbox = print_instance_with_bounding_box[k].bounding_box.inflated(scale_(print_config.extruder_clearance_dist_to_rod.value - print_config.extruder_clearance_max_radius.value)); auto iy1 = bbox.min.y(); auto iy2 = bbox.max.y(); (const_cast(inst->model_instance))->arrange_order = k+1; @@ -2177,12 +2185,8 @@ Polygons Print::first_layer_islands() const for (ExPolygon &expoly : object->m_layers.front()->lslices) object_islands.push_back(expoly.contour); if (!object->support_layers().empty()) { - if (object->support_layers().front()->support_type==stInnerNormal) - object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); - else if(object->support_layers().front()->support_type==stInnerTree) { - ExPolygons &expolys_first_layer = object->m_support_layers.front()->lslices; - for (ExPolygon &expoly : expolys_first_layer) { object_islands.push_back(expoly.contour); } - } + ExPolygons &expolys_first_layer = object->m_support_layers.front()->support_islands; + for (ExPolygon &expoly : expolys_first_layer) { object_islands.push_back(expoly.contour); } } islands.reserve(islands.size() + object_islands.size() * object->instances().size()); for (const PrintInstance &instance : object->instances()) @@ -2624,7 +2628,6 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co #define JSON_SUPPORT_LAYER_ISLANDS "support_islands" #define JSON_SUPPORT_LAYER_FILLS "support_fills" #define JSON_SUPPORT_LAYER_INTERFACE_ID "interface_id" -#define JSON_SUPPORT_LAYER_TYPE "support_type" #define JSON_LAYER_REGION_CONFIG_HASH "config_hash" #define JSON_LAYER_REGION_SLICES "slices" @@ -3286,7 +3289,6 @@ void extract_layer(const json& layer_json, Layer& layer) { void extract_support_layer(const json& support_layer_json, SupportLayer& support_layer) { extract_layer(support_layer_json, support_layer); - support_layer.support_type = support_layer_json[JSON_SUPPORT_LAYER_TYPE]; //support_islands int islands_count = support_layer_json[JSON_SUPPORT_LAYER_ISLANDS].size(); for (int islands_index = 0; islands_index < islands_count; islands_index++) @@ -3466,7 +3468,6 @@ int Print::export_cached_data(const std::string& directory, bool with_space) convert_layer_to_json(support_layer_json, support_layer); support_layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID] = support_layer->interface_id(); - support_layer_json[JSON_SUPPORT_LAYER_TYPE] = support_layer->support_type; //support_islands for (const ExPolygon& support_island : support_layer->support_islands) { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index e584610..3c4f052 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -430,7 +430,7 @@ public: std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } // Helpers to project custom facets on slices - void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; + void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys, std::vector>* vertical_points=nullptr) const; //QDS BoundingBox get_first_layer_bbox(float& area, float& layer_height, std::string& name); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 6a8bf6d..d7d14be 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1047,24 +1047,62 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ else m_support_used = false; + // check if filament ebnable scarf seam { - const auto &o = model.objects; - const auto opt_has_scarf_joint_seam = [](const DynamicConfig &c) { - return c.has("seam_slope_type") && c.opt_enum("seam_slope_type") != SeamScarfType::None; + std::vector scarf_seam_type = new_full_config.option("filament_scarf_seam_type")->values; + auto check_object_scarf_seam_type = [](const std::vector scarf_seam_type, const ModelObject *mo) { + //get filament scarf seam type + //check volumes + for (ModelVolume *mv : mo->volumes) { + std::vector volume_extruders = mv->get_extruders(); + for (int filament_id : volume_extruders) { + if( scarf_seam_type[filament_id - 1] != 0 ) { + return true; + } + + } + } + + // check layer range + for (auto layer_range : mo->layer_config_ranges) { + if(layer_range.second.has("extruder")) + if (int id = layer_range.second.option("extruder")->getInt(); id > 0 && scarf_seam_type[id - 1] != 0) + return true; + } + + return false; + }; - const bool has_scarf_joint_seam = std::any_of(o.begin(), o.end(), [&new_full_config, &opt_has_scarf_joint_seam](ModelObject *obj) { - return obj->get_config_value>(new_full_config, "seam_slope_type")->value != SeamScarfType::None || - std::any_of(obj->volumes.begin(), obj->volumes.end(), - [&opt_has_scarf_joint_seam](const ModelVolume *v) { return opt_has_scarf_joint_seam(v->config.get()); }) || - std::any_of(obj->layer_config_ranges.begin(), obj->layer_config_ranges.end(), - [&opt_has_scarf_joint_seam](const auto &r) { return opt_has_scarf_joint_seam(r.second.get()); }); + + //check custom gcode + auto check_gcode_scarf_seam_type = [](const std::vector scarf_seam_type, const Model &model) { + auto it = model.plates_custom_gcodes.begin(); + + while (it != model.plates_custom_gcodes.end()) { + const CustomGCode::Info &gcode_info = it->second; + auto item = gcode_info.gcodes.begin(); + while (item != gcode_info.gcodes.end()) { + if (item->type == CustomGCode::Type::ToolChange && item->extruder <= scarf_seam_type.size() && scarf_seam_type[item->extruder - 1] != 0) + return true; + item++; + } + it++; + } + + return false; + }; + + // check custon_gcode + bool has_scarf_joint_seam = check_gcode_scarf_seam_type(scarf_seam_type, model) || + std::any_of(model.objects.begin(), model.objects.end(), [scarf_seam_type, &check_object_scarf_seam_type](const ModelObject *obj) { + return check_object_scarf_seam_type(scarf_seam_type, obj); }); + + if (has_scarf_joint_seam) { new_full_config.set("has_scarf_joint_seam", true); } - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", has_scarf_joint_seam:" << has_scarf_joint_seam; } // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. @@ -1255,6 +1293,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation; + bool brim_points_differ = model_brim_points_data_changed(model_object, model_object_new); auto print_objects_range = print_object_status_db.get_range(model_object); // The list actually can be empty if all instances are out of the print bed. //assert(print_objects_range.begin() != print_objects_range.end()); @@ -1301,6 +1340,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } else if (model_custom_seam_data_changed(model_object, model_object_new)) { update_apply_status(this->invalidate_step(psGCodeExport)); } + if (brim_points_differ) { + model_object.brim_points = model_object_new.brim_points; + update_apply_status(this->invalidate_all_steps()); + } } if (! solid_or_modifier_differ) { // Synchronize Object's config. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5bd26bb..3bb7dec 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -41,7 +41,12 @@ std::set SplitStringAndRemoveDuplicateElement(const std::string &st void ReplaceString(std::string &resource_str, const std::string &old_str, const std::string &new_str) { std::string::size_type pos = 0; - while ((pos = resource_str.find(old_str)) != std::string::npos) { resource_str.replace(pos, old_str.length(), new_str); } + size_t new_size = 0; + while ((pos = resource_str.find(old_str, pos + new_size)) != std::string::npos) + { + resource_str.replace(pos, old_str.length(), new_str); + new_size = new_str.size(); + } } } @@ -272,7 +277,8 @@ static const t_config_enum_values s_keys_map_BrimType = { {"outer_only", btOuterOnly}, {"inner_only", btInnerOnly}, {"outer_and_inner", btOuterAndInner}, - {"auto_brim", btAutoBrim} // QDS + {"auto_brim", btAutoBrim}, // QDS + {"brim_ears", btBrimEars} // QDS }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType) @@ -307,13 +313,25 @@ static const t_config_enum_values s_keys_map_OverhangFanThreshold = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(OverhangFanThreshold) +//QDS +static const t_config_enum_values s_keys_map_OverhangThresholdParticipatingCooling = { + { "0%", Overhang_threshold_participating_cooling_none }, + { "10%", Overhang_threshold_participating_cooling_1_4 }, + { "25%", Overhang_threshold_participating_cooling_2_4 }, + { "50%", Overhang_threshold_participating_cooling_3_4 }, + { "75%", Overhang_threshold_participating_cooling_4_4 }, + { "95%", Overhang_threshold_participating_cooling_bridge } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(OverhangThresholdParticipatingCooling) + // QDS static const t_config_enum_values s_keys_map_BedType = { { "Default Plate", btDefault }, { "Cool Plate", btPC }, { "Engineering Plate", btEP }, { "High Temp Plate", btPEI }, - { "Textured PEI Plate", btPTE } + { "Textured PEI Plate", btPTE }, + {"Supertack Plate", btSuperTack} }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedType) @@ -581,6 +599,16 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloatOrPercent(0., false)); // QDS + def = this->add("supertack_plate_temp", coInts); + def->label = L("Other layers"); + def->tooltip = L("Bed temperature for layers except the initial one. " + "Value 0 means the filament does not support to print on the Cool Plate"); + def->sidetext = "°C"; + def->full_label = L("Bed temperature"); + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{35}); + def = this->add("cool_plate_temp", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers except the initial one. " @@ -621,11 +649,21 @@ void PrintConfigDef::init_fff_params() def->max = 120; def->set_default_value(new ConfigOptionInts{45}); + def = this->add("supertack_plate_temp_initial_layer", coInts); + def->label = L("Initial layer"); + def->full_label = L("Initial layer bed temperature"); + def->tooltip = L("Bed temperature of the initial layer. " + "Value 0 means the filament does not support to print on the QIDI Cool Plate SuperTack"); + def->sidetext = "°C"; + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{ 35 }); + def = this->add("cool_plate_temp_initial_layer", coInts); def->label = L("Initial layer"); def->full_label = L("Initial layer bed temperature"); def->tooltip = L("Bed temperature of the initial layer. " - "Value 0 means the filament does not support to print on the Cool Plate"); + "Value 0 means the filament does not support to print on the QIDI Cool Plate SuperTack"); def->sidetext = "°C"; def->min = 0; def->max = 120; @@ -670,10 +708,12 @@ void PrintConfigDef::init_fff_params() def->enum_values.emplace_back("Engineering Plate"); def->enum_values.emplace_back("High Temp Plate"); def->enum_values.emplace_back("Textured PEI Plate"); - def->enum_labels.emplace_back(L("Cool Plate / PLA Plate")); + def->enum_values.emplace_back("Supertack Plate"); + def->enum_labels.emplace_back(L("Cool Plate")); def->enum_labels.emplace_back(L("Engineering Plate")); def->enum_labels.emplace_back(L("Smooth PEI Plate / High Temp Plate")); def->enum_labels.emplace_back(L("Textured PEI Plate")); + def->enum_labels.emplace_back(L("QIDI Cool Plate SuperTack")); def->set_default_value(new ConfigOptionEnum(btPC)); // QDS @@ -783,6 +823,28 @@ void PrintConfigDef::init_fff_params() def->enum_labels.emplace_back("95%"); def->set_default_value(new ConfigOptionEnumsGeneric{ (int)Overhang_threshold_bridge }); + def = this->add("overhang_threshold_participating_cooling", coEnums); + def->label = L("Overhang threshold for participating cooling"); + def->tooltip = L("Decide which overhang part join the cooling function to slow down the speed." + "Expressed as percentage which indicides how much width of the line without support from lower layer. " + "100% means forcing cooling for all outer wall no matter how much overhang degree"); + def->sidetext = ""; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->mode = comAdvanced; + def->enum_values.emplace_back("0%"); + def->enum_values.emplace_back("10%"); + def->enum_values.emplace_back("25%"); + def->enum_values.emplace_back("50%"); + def->enum_values.emplace_back("75%"); + def->enum_values.emplace_back("100%"); + def->enum_labels.emplace_back("0%"); + def->enum_labels.emplace_back("10%"); + def->enum_labels.emplace_back("25%"); + def->enum_labels.emplace_back("50%"); + def->enum_labels.emplace_back("75%"); + def->enum_labels.emplace_back("100%"); + def->set_default_value(new ConfigOptionEnumsGeneric{(int) Overhang_threshold_participating_cooling_bridge}); + def = this->add("bridge_angle", coFloat); def->label = L("Bridge direction"); def->category = L("Strength"); @@ -934,6 +996,7 @@ void PrintConfigDef::init_fff_params() "Auto means the brim width is analysed and calculated automatically."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.emplace_back("auto_brim"); + def->enum_values.emplace_back("brim_ears"); def->enum_values.emplace_back("outer_only"); #if 1 //!QDT_RELEASE_TO_PUBLIC // QDS: The following two types are disabled @@ -943,6 +1006,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.emplace_back("no_brim"); def->enum_labels.emplace_back(L("Auto")); + def->enum_labels.emplace_back(L("Painted")); def->enum_labels.emplace_back(L("Outer brim only")); #if 1 //!QDT_RELEASE_TO_PUBLIC // QDS: The following two types are disabled @@ -1316,9 +1380,9 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(120)); - def = this->add("extruder_clearance_radius", coFloat); - def->label = L("Radius"); - def->tooltip = L("Clearance radius around extruder. Used for collision avoidance in by-object printing."); + def = this->add("extruder_clearance_dist_to_rod", coFloat); + def->label = L("Distance to rod"); + def->tooltip = L("Horizontal distance of the nozzle tip to the rod's farther edge. Used for collision avoidance in by-object printing."); def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; @@ -1356,6 +1420,8 @@ void PrintConfigDef::init_fff_params() // "from the XY coordinate)."); def->sidetext = L("mm"); def->mode = comAdvanced; + def->min = -5; + def->max = 5; def->set_default_value(new ConfigOptionPoints { Vec2d(0,0) }); def = this->add("filament_flow_ratio", coFloats); @@ -1526,6 +1592,19 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionFloats { 1.75 }); + def = this->add("filament_shrink", coPercents); + def->label = L("Shrinkage"); + // xgettext:no-c-format, no-boost-format + def->tooltip = L("Enter the shrinkage percentage that the filament will get after cooling (94% if you measure 94mm instead of 100mm)." + " The part will be scaled in xy to compensate." + " Only the filament used for the perimeter is taken into account." + "\nBe sure to allow enough space between objects, as this compensation is done after the checks."); + def->sidetext = L("%"); + def->ratio_over = ""; + def->min = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercents{ 100 }); + def = this->add("filament_density", coFloats); def->label = L("Density"); def->tooltip = L("Filament density. For statistics only"); @@ -1542,9 +1621,11 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("PLA"); def->enum_values.push_back("ABS"); def->enum_values.push_back("ASA"); + def->enum_values.push_back("ASA-CF"); def->enum_values.push_back("PETG"); def->enum_values.push_back("PCTG"); def->enum_values.push_back("TPU"); + def->enum_values.push_back("TPU-AMS"); def->enum_values.push_back("PC"); def->enum_values.push_back("PA"); def->enum_values.push_back("PA-CF"); @@ -1580,11 +1661,50 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionBools { false }); - def = this->add("filament_is_support", coBools); - def->label = L("Support material"); + def = this->add("filament_scarf_seam_type", coEnums); + def->label = L("Scarf seam type"); + def->tooltip = L("Set scarf seam type for this filament. This setting could minimize seam visibiliy."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("none"); + def->enum_values.push_back("external"); + def->enum_values.push_back("all"); + def->enum_labels.push_back(L("None")); + def->enum_labels.push_back(L("Contour")); + def->enum_labels.push_back(L("Contour and hole")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnumsGeneric{0}); + + def = this->add("filament_scarf_height", coFloatsOrPercents); + def->label = L("Scarf start height"); + def->tooltip = L("This amount can be specified in millimeters or as a percentage of the current layer height."); + def->min = 0; + def->ratio_over = "layer_height"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatsOrPercents{FloatOrPercent( 0, 10)}); + + def = this->add("filament_scarf_gap", coFloatsOrPercents); + def->label = L("Scarf slope gap"); + def->tooltip = L("In order to reduce the visiblity of the seam in closed loop, the inner wall and outer wall are shortened by a specified amount."); + def->min = 0; + def->ratio_over = "nozzle_diameter"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatsOrPercents{FloatOrPercent(0, 0)}); + + def = this->add("filament_scarf_length", coFloats); + def->label = L("Scarf length"); + def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); + def->min = 0; + def->sidetext = "mm"; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{10}); + + def = this->add("filament_is_support", coBools); + def->label = L("Support material"); def->tooltip = L("Support material is commonly used to print support and support interface"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBools { false }); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBools{false}); // QDS def = this->add("temperature_vitrification", coInts); @@ -2282,6 +2402,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.1)); + def = this->add("ironing_inset", coFloat); + def->label = L("Ironing inset"); + def->category = L("Quality"); + def->tooltip = L("The distance to keep the from the edges of ironing line. 0 means not apply."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("ironing_speed", coFloat); def->label = L("Ironing speed"); def->category = L("Quality"); @@ -2761,7 +2891,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Raft layers"); def->category = L("Support"); def->tooltip = L("Object will be raised by this number of support layers. " - "Use this function to avoid wrapping when print ABS"); + "Use this function to avoid warping when print ABS"); def->sidetext = L("layers"); def->min = 0; def->max = 100; @@ -2948,27 +3078,14 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionPercent(15)); - def = this->add("seam_slope_type", coEnum); - def->label = L("Scarf joint seam (experimental)"); - def->tooltip = L("Use scarf joint to minimize seam visibility and increase seam strength."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("none"); - def->enum_values.push_back("external"); - def->enum_values.push_back("all"); - def->enum_labels.push_back(L("None")); - def->enum_labels.push_back(L("Contour")); - def->enum_labels.push_back(L("Contour and hole")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(SeamScarfType::None)); - def = this->add("seam_slope_conditional", coBool); - def->label = L("Conditional scarf joint"); + def->label = L("Smart scarf seam application"); def->tooltip = L("Apply scarf joints only to smooth perimeters where traditional seams do not conceal the seams at sharp corners effectively."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); def = this->add("scarf_angle_threshold", coInt); - def->label = L("Conditional angle threshold"); + def->label = L("Scarf application angle threshold"); def->tooltip = L("This option sets the threshold angle for applying a conditional scarf joint seam.\nIf the seam angle within the perimeter loop " "exceeds this value (indicating the absence of sharp corners), a scarf joint seam will be used. The default value is 155°."); def->mode = comAdvanced; def->sidetext = L("°"); @@ -2976,29 +3093,12 @@ void PrintConfigDef::init_fff_params() def->max = 180; def->set_default_value(new ConfigOptionInt(155)); - def = this->add("seam_slope_start_height", coFloatOrPercent); - def->label = L("Scarf start height"); - def->tooltip = L("Start height of the scarf.\n" - "This amount can be specified in millimeters or as a percentage of the current layer height. The default value for this parameter is 0."); - def->sidetext = L("mm or %"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(50, true)); - def = this->add("seam_slope_entire_loop", coBool); def->label = L("Scarf around entire wall"); def->tooltip = L("The scarf extends to the entire length of the wall."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("seam_slope_min_length", coFloat); - def->label = L("Scarf length"); - def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(10)); - def = this->add("seam_slope_steps", coInt); def->label = L("Scarf steps"); def->tooltip = L("Minimum number of segments of each scarf."); @@ -3020,6 +3120,12 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionPercent(80)); + def = this->add("role_base_wipe_speed", coBool); + def->label = L("Role-based wipe speed"); + def->tooltip = L("The wipe speed is determined by speed of current extrusion role. " "e.g if a wip action is executed immediately following an outer wall extrusion, the speed of the outer wall extrusion will be utilized for the wipe action."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("skirt_distance", coFloat); def->label = L("Skirt distance"); def->tooltip = L("Distance from skirt to brim or object"); @@ -3559,7 +3665,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(30)); def = this->add("tree_support_branch_angle", coFloat); - def->label = L("Tree support branch angle"); + def->label = L("Branch angle"); def->category = L("Support"); def->tooltip = L("This setting determines the maximum overhang angle that t he branches of tree support allowed to make." "If the angle is increased, the branches can be printed more horizontally, allowing them to reach farther."); @@ -3570,7 +3676,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(40.)); def = this->add("tree_support_branch_distance", coFloat); - def->label = L("Tree support branch distance"); + def->label = L("Branch distance"); def->category = L("Support"); def->tooltip = L("This setting determines the distance between neighboring tree support nodes."); def->sidetext = L("mm"); @@ -3580,7 +3686,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(5.)); def = this->add("tree_support_branch_diameter", coFloat); - def->label = L("Tree support branch diameter"); + def->label = L("Branch diameter"); def->category = L("Support"); def->tooltip = L("This setting determines the initial diameter of support nodes."); def->sidetext = L("mm"); @@ -3589,11 +3695,24 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5.)); + def = this->add("tree_support_branch_diameter_angle", coFloat); + def->label = L("Branch diameter angle"); + def->category = L("Support"); + def->tooltip = L("The angle of the branches' diameter as they gradually become thicker towards the bottom. " + "An angle of 0 will cause the branches to have uniform thickness over their length. " + "A bit of an angle can increase stability of the tree support."); + def->sidetext = L("°"); + def->min = 0.0; + def->max = 15; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.)); + def = this->add("tree_support_wall_count", coInt); def->label = L("Support wall loops"); def->category = L("Support"); - def->tooltip = L("This setting specifies the count of walls around support"); + def->tooltip = L("This setting specifies the count of support walls in the range of [0,2]. 0 means auto."); def->min = 0; + def->max = 2; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(0)); @@ -4831,7 +4950,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "support_transition_line_width", "support_transition_speed", "bed_temperature", "bed_temperature_initial_layer", "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height", "z_hop_type","nozzle_hrc","chamber_temperature","only_one_wall_top","bed_temperature_difference","long_retraction_when_cut", - "retraction_distance_when_cut" + "retraction_distance_when_cut", + "seam_slope_type","seam_slope_start_height","seam_slope_gap", "seam_slope_min_length" }; if (ignore.find(opt_key) != ignore.end()) { @@ -5219,7 +5339,12 @@ std::string DynamicPrintConfig::get_filament_type(std::string &displayed_filamen } else if (filament_type->get_at(id) == "PA") { displayed_filament_type = "Sup.PA"; return "PA-S"; - } else { + } + else if (filament_type->get_at(id) == "ABS") { + displayed_filament_type = "Sup.ABS"; + return "ABS-S"; + } + else { displayed_filament_type = filament_type->get_at(id); return filament_type->get_at(id); } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index aef0477..62e39b7 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -147,7 +147,7 @@ enum SeamPosition { // Orca enum class SeamScarfType { - None, + None = 0, External, All, }; @@ -173,6 +173,7 @@ enum SLAPillarConnectionMode { enum BrimType { btAutoBrim, // QDS + btBrimEars, // QDS btOuterOnly, btInnerOnly, btOuterAndInner, @@ -214,6 +215,15 @@ enum OverhangFanThreshold { Overhang_threshold_bridge }; +enum OverhangThresholdParticipatingCooling { + Overhang_threshold_participating_cooling_none = 0, + Overhang_threshold_participating_cooling_1_4, + Overhang_threshold_participating_cooling_2_4, + Overhang_threshold_participating_cooling_3_4, + Overhang_threshold_participating_cooling_4_4, + Overhang_threshold_participating_cooling_bridge +}; + // QDS enum BedType { btDefault = 0, @@ -221,6 +231,7 @@ enum BedType { btEP, btPEI, btPTE, + btSuperTack, btCount }; @@ -280,6 +291,9 @@ static std::string bed_type_to_gcode_string(const BedType type) std::string type_str; switch (type) { + case btSuperTack: + type_str = "supertack_plate"; + break; case btPC: type_str = "cool_plate"; break; @@ -302,6 +316,9 @@ static std::string bed_type_to_gcode_string(const BedType type) static std::string get_bed_temp_key(const BedType type) { + if (type == btSuperTack) + return "supertack_plate_temp"; + if (type == btPC) return "cool_plate_temp"; @@ -319,6 +336,9 @@ static std::string get_bed_temp_key(const BedType type) static std::string get_bed_temp_1st_layer_key(const BedType type) { + if (type == btSuperTack) + return "supertack_plate_temp_initial_layer"; + if (type == btPC) return "cool_plate_temp_initial_layer"; @@ -761,6 +781,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, tree_support_branch_distance)) ((ConfigOptionFloat, tree_support_branch_diameter)) ((ConfigOptionFloat, tree_support_branch_angle)) + ((ConfigOptionFloat, tree_support_branch_diameter_angle)) ((ConfigOptionInt, tree_support_wall_count)) ((ConfigOptionBool, detect_narrow_internal_solid_infill)) // ((ConfigOptionBool, adaptive_layer_height)) @@ -779,6 +800,7 @@ PRINT_CONFIG_CLASS_DEFINE( // OrcaSlicer ((ConfigOptionPercent, seam_gap)) ((ConfigOptionPercent, wipe_speed)) + ((ConfigOptionBool, role_base_wipe_speed)) ((ConfigOptionBool, precise_z_height)) // QDS ) @@ -816,6 +838,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionEnum, ironing_pattern)) ((ConfigOptionPercent, ironing_flow)) ((ConfigOptionFloat, ironing_spacing)) + ((ConfigOptionFloat, ironing_inset)) ((ConfigOptionFloat, ironing_direction)) ((ConfigOptionFloat, ironing_speed)) // Detect bridging perimeters @@ -854,12 +877,13 @@ PRINT_CONFIG_CLASS_DEFINE( //calib ((ConfigOptionFloat, print_flow_ratio)) // Orca: seam slopes - ((ConfigOptionEnum, seam_slope_type)) + // ((ConfigOptionEnum, seam_slope_type)) ((ConfigOptionBool, seam_slope_conditional)) ((ConfigOptionInt, scarf_angle_threshold)) - ((ConfigOptionFloatOrPercent, seam_slope_start_height)) + // ((ConfigOptionFloatOrPercent, seam_slope_start_height)) + //((ConfigOptionFloatOrPercent, seam_slope_gap)) ((ConfigOptionBool, seam_slope_entire_loop)) - ((ConfigOptionFloat, seam_slope_min_length)) + // ((ConfigOptionFloat, seam_slope_min_length)) ((ConfigOptionInt, seam_slope_steps)) ((ConfigOptionBool, seam_slope_inner_walls)) //w16 @@ -917,6 +941,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionStrings, filament_type)) ((ConfigOptionBools, filament_soluble)) ((ConfigOptionBools, filament_is_support)) + ((ConfigOptionEnumsGeneric, filament_scarf_seam_type)) + ((ConfigOptionFloatsOrPercents, filament_scarf_height)) + ((ConfigOptionFloatsOrPercents, filament_scarf_gap)) + ((ConfigOptionFloats, filament_scarf_length)) ((ConfigOptionFloats, filament_cost)) ((ConfigOptionString, filament_notes)) ((ConfigOptionStrings, default_filament_colour)) @@ -1001,9 +1029,11 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionString, bed_custom_model)) ((ConfigOptionEnum, curr_bed_type)) ((ConfigOptionInts, cool_plate_temp)) + ((ConfigOptionInts, supertack_plate_temp)) ((ConfigOptionInts, eng_plate_temp)) ((ConfigOptionInts, hot_plate_temp)) // hot is short for high temperature ((ConfigOptionInts, textured_plate_temp)) + ((ConfigOptionInts, supertack_plate_temp_initial_layer)) ((ConfigOptionInts, cool_plate_temp_initial_layer)) ((ConfigOptionInts, eng_plate_temp_initial_layer)) ((ConfigOptionInts, hot_plate_temp_initial_layer)) // hot is short for high temperature @@ -1011,6 +1041,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBools, enable_overhang_bridge_fan)) ((ConfigOptionInts, overhang_fan_speed)) ((ConfigOptionEnumsGeneric, overhang_fan_threshold)) + ((ConfigOptionEnumsGeneric, overhang_threshold_participating_cooling)) ((ConfigOptionEnum,print_sequence)) ((ConfigOptionInts, first_layer_print_sequence)) ((ConfigOptionInts, other_layers_print_sequence)) @@ -1026,7 +1057,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionEnum, draft_shield)) ((ConfigOptionFloat, extruder_clearance_height_to_rod))//QDS ((ConfigOptionFloat, extruder_clearance_height_to_lid))//QDS - ((ConfigOptionFloat, extruder_clearance_radius)) + ((ConfigOptionFloat, extruder_clearance_dist_to_rod)) ((ConfigOptionFloat, nozzle_height)) ((ConfigOptionFloat, extruder_clearance_max_radius)) ((ConfigOptionStrings, extruder_colour)) @@ -1110,6 +1141,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( // QDS: move from PrintObjectConfig ((ConfigOptionBool, independent_support_layer_height)) ((ConfigOptionBool, exclude_object)) + ((ConfigOptionPercents, filament_shrink)) //w13 ((ConfigOptionBool, seal)) //w14 diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 373f72e..6ce0403 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -538,7 +538,6 @@ void PrintObject::simplify_extrusion_path() } if (this->set_started(posSimplifySupportPath)) { - //QDS: share same progress m_print->set_status(75, L("Optimizing toolpath")); BOOST_LOG_TRIVIAL(debug) << "Simplify extrusion path of support in parallel - start"; tbb::parallel_for( @@ -649,14 +648,8 @@ void PrintObject::clear_support_layers() std::shared_ptr PrintObject::alloc_tree_support_preview_cache() { if (!m_tree_support_preview_cache) { - const coordf_t layer_height = m_config.layer_height.value; const coordf_t xy_distance = m_config.support_object_xy_distance.value; - const double angle = m_config.tree_support_branch_angle.value * M_PI / 180.; - const coordf_t max_move_distance - = (angle < M_PI / 2) ? (coordf_t)(tan(angle) * layer_height) : std::numeric_limits::max(); - const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution; - - m_tree_support_preview_cache = std::make_shared(*this, xy_distance, max_move_distance, radius_sample_resolution); + m_tree_support_preview_cache = std::make_shared(*this, xy_distance, g_config_tree_support_collision_resolution); } return m_tree_support_preview_cache; @@ -665,7 +658,6 @@ std::shared_ptr PrintObject::alloc_tree_support_preview_cache() SupportLayer* PrintObject::add_tree_support_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z) { m_support_layers.emplace_back(new SupportLayer(id, 0, this, height, print_z, slice_z)); - m_support_layers.back()->support_type = stInnerTree; return m_support_layers.back(); } @@ -798,6 +790,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "tree_support_branch_distance" || opt_key == "tree_support_branch_diameter" || opt_key == "tree_support_branch_angle" + || opt_key == "tree_support_branch_diameter_angle" || opt_key == "tree_support_wall_count") { steps.emplace_back(posSupportMaterial); } else if ( @@ -895,16 +888,14 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" - || opt_key == "seam_slope_type" || opt_key == "seam_slope_conditional" || opt_key == "scarf_angle_threshold" - || opt_key == "seam_slope_start_height" || opt_key == "seam_slope_entire_loop" - || opt_key == "seam_slope_min_length" || opt_key == "seam_slope_steps" || opt_key == "seam_slope_inner_walls" || opt_key == "seam_gap" || opt_key == "wipe_speed" + || opt_key == "role_base_wipe_speed" || opt_key == "support_speed" || opt_key == "support_interface_speed" //1.9.5 @@ -1028,19 +1019,8 @@ void PrintObject::detect_surfaces_type() // In non-spiral vase mode, go over all layers. m_layers.size()), [this, spiral_mode, region_id, interface_shells, &surfaces_new](const tbb::blocked_range& range) { - // If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating - // the support from the print. - // QDS: the above logic only applys for normal(auto) support. Complete logic: - // 1. has support, top z distance=0 (soluble material), auto support - // 2. for normal(auto), bridge_no_support is off - // 3. for tree(auto), interface top layers=0, max bridge length=0, support_critical_regions_only=false (only in this way the bridge is fully supported) - bool bottom_is_fully_supported = this->has_support() && m_config.support_top_z_distance.value == 0 && is_auto(m_config.support_type.value); - if (m_config.support_type.value == stNormalAuto) - bottom_is_fully_supported &= !m_config.bridge_no_support.value; - else if (m_config.support_type.value == stTreeAuto) { - bottom_is_fully_supported &= (m_config.support_interface_top_layers.value > 0 && m_config.max_bridge_length.value == 0 && m_config.support_critical_regions_only.value==false); - } - SurfaceType surface_type_bottom_other = bottom_is_fully_supported ? stBottom : stBottomBridge; + // QDS coconut: can't set to stBottom when soluable support is used, as the support may not be actaully generated, e.g. when "on build plate only" option is enabled. See github #3507. + SurfaceType surface_type_bottom_other = stBottomBridge; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); // BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << region_id << " and layer " << layer->print_z; @@ -2576,12 +2556,14 @@ void PrintObject::bridge_over_infill() const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); - - area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), - [internal_unsupported_area](const Polygon &p) { + ExPolygons area_to_be_bridge_ex = union_ex(area_to_be_bridge); + area_to_be_bridge_ex.erase(std::remove_if(area_to_be_bridge_ex.begin(), area_to_be_bridge_ex.end(), + [internal_unsupported_area](const ExPolygon &p) { return intersection({p}, internal_unsupported_area).empty(); }), - area_to_be_bridge.end()); + area_to_be_bridge_ex.end()); + + area_to_be_bridge = to_polygons(area_to_be_bridge_ex); Polygons limiting_area = union_(area_to_be_bridge, expansion_area); @@ -3523,90 +3505,7 @@ template void PrintObject::remove_bridges_from_contacts( SupportNecessaryType PrintObject::is_support_necessary() { - static const double super_overhang_area_threshold = SQ(scale_(5.0)); const double cantilevel_dist_thresh = scale_(6); -#if 0 - double threshold_rad = (m_config.support_threshold_angle.value < EPSILON ? 30 : m_config.support_threshold_angle.value + 1) * M_PI / 180.; - int enforce_support_layers = m_config.enforce_support_layers; - const coordf_t extrusion_width = m_config.line_width.value; - const coordf_t extrusion_width_scaled = scale_(extrusion_width); - float max_bridge_length = scale_(m_config.max_bridge_length.value); - const bool bridge_no_support = max_bridge_length > 0;// config.bridge_no_support.value; - - for (size_t layer_nr = enforce_support_layers + 1; layer_nr < this->layer_count(); layer_nr++) { - Layer* layer = m_layers[layer_nr]; - Layer* lower_layer = layer->lower_layer; - - coordf_t support_offset_scaled = extrusion_width_scaled * 0.9; - ExPolygons lower_layer_offseted = offset_ex(lower_layer->lslices, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - - // 1. check sharp tail - for (const LayerRegion* layerm : layer->regions()) { - for (const ExPolygon& expoly : layerm->raw_slices) { - // detect sharp tail - if (intersection_ex({ expoly }, lower_layer_offseted).empty()) - return SharpTail; - } - } - - // 2. check overhang area - ExPolygons super_overhang_expolys = std::move(diff_ex(layer->lslices, lower_layer_offseted)); - super_overhang_expolys.erase(std::remove_if( - super_overhang_expolys.begin(), - super_overhang_expolys.end(), - [extrusion_width_scaled](ExPolygon& area) { - return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); - }), - super_overhang_expolys.end()); - - // remove bridge - if (bridge_no_support) - remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &super_overhang_expolys, max_bridge_length); - - Polygons super_overhang_polys = to_polygons(super_overhang_expolys); - - - super_overhang_polys.erase(std::remove_if( - super_overhang_polys.begin(), - super_overhang_polys.end(), - [extrusion_width_scaled](Polygon& area) { - return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); - }), - super_overhang_polys.end()); - - double super_overhang_area = 0.0; - for (Polygon& poly : super_overhang_polys) { - bool is_ccw = poly.is_counter_clockwise(); - double area_ = poly.area(); - if (is_ccw) { - if (area_ > super_overhang_area_threshold) - return LargeOverhang; - super_overhang_area += area_; - } - else { - super_overhang_area -= area_; - } - } - - //if (super_overhang_area > super_overhang_area_threshold) - // return LargeOverhang; - - // 3. check overhang distance - const double distance_threshold_scaled = extrusion_width_scaled * 2; - ExPolygons lower_layer_offseted_2 = offset_ex(lower_layer->lslices, distance_threshold_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - ExPolygons exceed_overhang = std::move(diff_ex(super_overhang_polys, lower_layer_offseted_2)); - exceed_overhang.erase(std::remove_if( - exceed_overhang.begin(), - exceed_overhang.end(), - [extrusion_width_scaled](ExPolygon& area) { - // tolerance for 1 extrusion width offset - return offset_ex(area, -0.5 * extrusion_width_scaled).empty(); - }), - exceed_overhang.end()); - if (!exceed_overhang.empty()) - return LargeOverhang; - } -#else TreeSupport tree_support(*this, m_slicing_params); tree_support.support_type = SupportType::stTreeAuto; // need to set support type to fully utilize the power of feature detection tree_support.detect_overhangs(true); @@ -3615,7 +3514,7 @@ SupportNecessaryType PrintObject::is_support_necessary() return SharpTail; else if (tree_support.has_cantilever && tree_support.max_cantilever_dist > cantilevel_dist_thresh) return Cantilever; -#endif + return NoNeedSupp; } @@ -3806,7 +3705,7 @@ static void project_triangles_to_slabs(ConstLayerPtrsAdaptor layers, const index } void PrintObject::project_and_append_custom_facets( - bool seam, EnforcerBlockerType type, std::vector& out) const + bool seam, EnforcerBlockerType type, std::vector& out, std::vector>* vertical_points) const { for (const ModelVolume* mv : this->model_object()->volumes) if (mv->is_model_part()) { @@ -3821,7 +3720,7 @@ void PrintObject::project_and_append_custom_facets( else { std::vector projected; // Support blockers or enforcers. Project downward facing painted areas upwards to their respective slicing plane. - slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){}); + slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, vertical_points, [](){}); // Merge these projections with the output, layer by layer. assert(! projected.empty()); assert(out.empty() || out.size() == projected.size()); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 4f9bce4..4d4e02b 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -273,11 +273,15 @@ static std::vector> slices_to_regions( float z = zs[z_idx]; int idx_first_printable_region = -1; bool complex = false; + std::vector printable_region_ids; for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) { const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region]; if (region.bbox->min().z() <= z && region.bbox->max().z() >= z) { - if (idx_first_printable_region == -1 && region.model_volume->is_model_part()) + if (region.model_volume->is_model_part()) + printable_region_ids.push_back(idx_region); + if (idx_first_printable_region == -1 && region.model_volume->is_model_part()) { idx_first_printable_region = idx_region; + } else if (idx_first_printable_region != -1) { // Test for overlap with some other region. for (int idx_region2 = idx_first_printable_region; idx_region2 < idx_region; ++ idx_region2) { @@ -293,8 +297,10 @@ static std::vector> slices_to_regions( if (complex) zs_complex.push_back({ z_idx, z }); else if (idx_first_printable_region >= 0) { - const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_first_printable_region]; - slices_by_region[region.region->print_object_region_id()][z_idx] = std::move(volume_slices_find_by_id(volume_slices, region.model_volume->id()).slices[z_idx]); + for (int printable_region_id : printable_region_ids) { + const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[printable_region_id]; + append(slices_by_region[region.region->print_object_region_id()][z_idx], std::move(volume_slices_find_by_id(volume_slices, region.model_volume->id()).slices[z_idx])); + } } } } @@ -1010,6 +1016,20 @@ void PrintObject::slice_volumes() PrintObject::clip_multipart_objects, throw_on_cancel_callback); + // SuperSlicer: filament shrink + for (const std::unique_ptr &pr : m_shared_regions->all_regions) { + if (pr.get()) { + std::vector ®ion_polys = region_slices[pr->print_object_region_id()]; + const size_t extruder_id = pr->extruder(FlowRole::frPerimeter) - 1; + double scale = print->config().filament_shrink.values[extruder_id] * 0.01; + if (scale != 1) { + scale = 1 / scale; + for (ExPolygons &polys : region_polys) + for (ExPolygon &poly : polys) poly.scale(scale); + } + } + } + for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { std::vector &by_layer = region_slices[region_id]; for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id) diff --git a/src/libslic3r/ProjectTask.hpp b/src/libslic3r/ProjectTask.hpp index ba71615..ddb1126 100644 --- a/src/libslic3r/ProjectTask.hpp +++ b/src/libslic3r/ProjectTask.hpp @@ -26,6 +26,7 @@ enum MachineBedType { BED_TYPE_PE, BED_TYPE_PEI, BED_TYPE_PTE, + BED_TYPE_SUPERTACK, BED_TYPE_COUNT, }; diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 43c1ba7..3c83dce 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1030,6 +1030,9 @@ void reorder_extrusion_entities(std::vector &entities, const s void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near) { + // this function crashes if there are empty elements in entities + entities.erase(std::remove_if(entities.begin(), entities.end(), [](ExtrusionEntity *entity) { return static_cast(entity)->empty(); }), + entities.end()); reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near)); } diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index a772ec9..2d93b97 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -110,7 +110,7 @@ SlicingParameters SlicingParameters::create_from_config( params.min_layer_height = std::min(params.min_layer_height, params.layer_height); params.max_layer_height = std::max(params.max_layer_height, params.layer_height); - if (! soluble_interface || is_tree_slim(object_config.support_type.value, object_config.support_style.value)) { + if (! soluble_interface) { params.gap_raft_object = object_config.raft_contact_distance.value; //QDS params.gap_object_support = object_config.support_bottom_z_distance.value; diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp new file mode 100644 index 0000000..68f9106 --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -0,0 +1,1952 @@ +///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "../ClipperUtils.hpp" +// #include "../ClipperZUtils.hpp" +#include "../ExtrusionEntityCollection.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../Fill/FillBase.hpp" +#include "../MutablePolygon.hpp" +#include "../Geometry.hpp" +#include "../Point.hpp" +#include "clipper/clipper_z.hpp" + +#include +#include +#include + +#include + +#include "SupportCommon.hpp" +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" + +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #define DEBUG + #define _DEBUG + #undef NDEBUG + #include "../utils.hpp" + #include "../SVG.hpp" +#endif + +#include + +namespace Slic3r { + +// how much we extend support around the actual contact area +//FIXME this should be dependent on the nozzle diameter! +#define SUPPORT_MATERIAL_MARGIN 1.5 + +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 +#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. + + +// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. +std::pair generate_interface_layers( + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + // Input / output, will be merged with output. Only provided for Organic supports. + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayersPtr &top_base_interface_layers, + // Input, will be trimmed with the newly created interface layers. + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage) +{ + std::pair base_and_interface_layers; + + if (! intermediate_layers.empty() && support_params.has_interfaces()) { + // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; + const bool snug_supports = support_params.support_style == smsSnug; + const bool smooth_supports = support_params.support_style != smsGrid; + SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; + SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; + + interface_layers.assign(intermediate_layers.size(), nullptr); + if (support_params.has_base_interfaces()) + base_interface_layers.assign(intermediate_layers.size(), nullptr); + const auto smoothing_distance = support_params.support_material_interface_flow.scaled_spacing() * 1.5; + const auto minimum_island_radius = support_params.support_material_interface_flow.scaled_spacing() / support_params.interface_density; + const auto closing_distance = smoothing_distance; // scaled(config.support_material_closing_radius.value); + // Insert a new layer into base_interface_layers, if intersection with base exists. + auto insert_layer = [&layer_storage, smooth_supports, closing_distance, smoothing_distance, minimum_island_radius]( + SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer, + const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { + bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty(); + assert(! bottom.empty() || ! top.empty() || has_top_interface); + // Merge top into bottom, unite them with a safety offset. + append(bottom, std::move(top)); + // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). + bottom = intersection( + smooth_supports ? + smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + union_safety_offset(std::move(bottom)), + intermediate_layer.polygons); + if (has_top_interface) { + // Don't trim the precomputed Organic supports top interface with base layer + // as the precomputed top interface likely expands over multiple tree tips. + bottom = union_(std::move(top_interface_layer->polygons), bottom); + top_interface_layer->polygons.clear(); + } + if (! bottom.empty()) { + //FIXME Remove non-printable tiny islands, let them be printed using the base support. + //bottom = opening(std::move(bottom), minimum_island_radius); + if (! bottom.empty()) { + SupportGeneratorLayer &layer_new = top_interface_layer ? *top_interface_layer : layer_storage.allocate(type); + layer_new.polygons = std::move(bottom); + layer_new.print_z = intermediate_layer.print_z; + layer_new.bottom_z = intermediate_layer.bottom_z; + layer_new.height = intermediate_layer.height; + layer_new.bridging = intermediate_layer.bridging; + // Subtract the interface from the base regions. + intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); + if (subtract) + // Trim the base interface layer with the interface layer. + layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); + //FIXME filter layer_new.polygons islands by a minimum area? + // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; + return &layer_new; + } + } + return nullptr; + }; + tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), + [&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params, + snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { + // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below + // this intermediate layer. + // Index of the first top contact layer intersecting the current intermediate layer. + auto idx_top_contact_first = -1; + // Index of the first bottom contact layer intersecting the current intermediate layer. + auto idx_bottom_contact_first = -1; + // Index of the first top interface layer intersecting the current intermediate layer. + auto idx_top_interface_first = -1; + // Index of the first top contact interface layer intersecting the current intermediate layer. + auto idx_top_base_interface_first = -1; + auto num_intermediate = int(intermediate_layers.size()); + for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { + SupportGeneratorLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; + Polygons polygons_top_contact_projected_interface; + Polygons polygons_top_contact_projected_base; + Polygons polygons_bottom_contact_projected_interface; + Polygons polygons_bottom_contact_projected_base; + if (support_params.num_top_interface_layers > 0) { + // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)]->print_z; + coordf_t top_inteface_z = std::numeric_limits::max(); + if (support_params.num_top_base_interface_layers > 0) + // Some top base interface layers will be generated. + top_inteface_z = support_params.num_top_interface_layers_only() == 0 ? + // Only base interface layers to generate. + - std::numeric_limits::max() : + intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z; + // Move idx_top_contact_first up until above the current print_z. + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { + const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? + if (top_contact_layer.bottom_z - EPSILON > top_z) + break; + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, + // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. + // For grid supports, merging of support regions will be performed by the projection into grid. + snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + } + } + if (support_params.num_bottom_interface_layers > 0) { + // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers) + 1)]->bottom_z; + coordf_t bottom_interface_z = - std::numeric_limits::max(); + if (support_params.num_bottom_base_interface_layers > 0) + // Some bottom base interface layers will be generated. + bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ? + // Only base interface layers to generate. + std::numeric_limits::max() : + intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers_only()))]->bottom_z; + // Move idx_bottom_contact_first up until touching bottom_z. + idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { + const SupportGeneratorLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; + if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) + break; + polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); + } + } + auto resolve_same_layer = [](SupportGeneratorLayersPtr &layers, int &idx, coordf_t print_z) -> SupportGeneratorLayer* { + if (! layers.empty()) { + idx = idx_higher_or_equal(layers, idx, [print_z](const SupportGeneratorLayer *layer) { return layer->print_z > print_z - EPSILON; }); + if (idx < int(layers.size()) && layers[idx]->print_z < print_z + EPSILON) + return layers[idx]; + } + return nullptr; + }; + SupportGeneratorLayer *top_interface_layer = resolve_same_layer(top_interface_layers, idx_top_interface_first, intermediate_layer.print_z); + SupportGeneratorLayer *top_base_interface_layer = resolve_same_layer(top_base_interface_layers, idx_top_base_interface_first, intermediate_layer.print_z); + SupportGeneratorLayer *interface_layer = nullptr; + if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty() || + (top_interface_layer && ! top_interface_layer->polygons.empty())) { + interface_layer = insert_layer( + intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), top_interface_layer, + nullptr, polygons_top_contact_projected_interface.empty() ? sltBottomInterface : sltTopInterface); + interface_layers[idx_intermediate_layer] = interface_layer; + } + if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty() || + (top_base_interface_layer && ! top_base_interface_layer->polygons.empty())) + base_interface_layers[idx_intermediate_layer] = insert_layer( + intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), top_base_interface_layer, + interface_layer ? &interface_layer->polygons : nullptr, sltBase); + } + }); + + // Compress contact_out, remove the nullptr items. + // The parallel_for above may not have merged all the interface and base_interface layers + // generated by the Organic supports code, do it here. + auto merge_remove_empty = [](SupportGeneratorLayersPtr &in1, SupportGeneratorLayersPtr &in2) { + auto remove_empty = [](SupportGeneratorLayersPtr &vec) { + vec.erase( + std::remove_if(vec.begin(), vec.end(), [](const SupportGeneratorLayer *ptr) { return ptr == nullptr || ptr->polygons.empty(); }), + vec.end()); + }; + remove_empty(in1); + remove_empty(in2); + if (in2.empty()) + return std::move(in1); + else if (in1.empty()) + return std::move(in2); + else { + SupportGeneratorLayersPtr out(in1.size() + in2.size(), nullptr); + std::merge(in1.begin(), in1.end(), in2.begin(), in2.end(), out.begin(), [](auto* l, auto* r) { return l->print_z < r->print_z; }); + return out; + } + }; + interface_layers = merge_remove_empty(interface_layers, top_interface_layers); + base_interface_layers = merge_remove_empty(base_interface_layers, top_base_interface_layers); + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; + } + + return base_and_interface_layers; +} + +SupportGeneratorLayersPtr generate_raft_base( + const PrintObject &object, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers, + const SupportGeneratorLayersPtr &base_layers, + SupportGeneratorLayerStorage &layer_storage) +{ + // If there is brim to be generated, calculate the trimming regions. + Polygons brim; + if (object.has_brim()) { + // The object does not have a raft. + // Calculate the area covered by the brim. + const BrimType brim_type = object.config().brim_type; + const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; + const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; + // QDS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft. + const auto brim_object_gap = scaled(object.config().brim_object_gap.value); + //const auto brim_object_gap = scaled(object.config().brim_object_gap.value + object.config().brim_width.value); + for (const ExPolygon &ex : object.layers().front()->lslices) { + if (brim_outer && brim_inner) + polygons_append(brim, offset(ex, brim_object_gap)); + else { + if (brim_outer) + polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)))); + else + brim.emplace_back(ex.contour); + if (brim_inner) { + Polygons holes = ex.holes; + polygons_reverse(holes); + holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))); + polygons_reverse(holes); + polygons_append(brim, std::move(holes)); + } else + polygons_append(brim, ex.holes); + } + } + brim = union_(brim); + } + + // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. + const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); + const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); + SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); + SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); + SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); + SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); + if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON) + // This is not the raft contact layer. + contacts = nullptr; + if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + interfaces = nullptr; + if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + base_interfaces = nullptr; + if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft interface layer. + columns_base = nullptr; + + Polygons interface_polygons; + if (contacts != nullptr && ! contacts->polygons.empty()) + polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (interfaces != nullptr && ! interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + + // Output vector. + SupportGeneratorLayersPtr raft_layers; + + if (slicing_params.raft_layers() > 1) { + Polygons base; + Polygons columns; + Polygons first_layer; + if (columns_base != nullptr) { + if (columns_base->bottom_print_z() > slicing_params.raft_interface_top_z - EPSILON) { + // Classic supports with colums above the raft interface. + base = columns_base->polygons; + columns = base; + if (! interface_polygons.empty()) + // Trim the 1st layer columns with the inflated interface polygons. + columns = diff(columns, interface_polygons); + } else { + // Organic supports with raft on print bed. + assert(is_approx(columns_base->print_z, slicing_params.first_print_layer_height)); + first_layer = columns_base->polygons; + } + } + if (! interface_polygons.empty()) { + // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. + base = union_(base, interface_polygons); + } + // Do not add the raft contact layer, only add the raft layers below the contact layer. + // Insert the 1st layer. + { + SupportGeneratorLayer &new_layer = layer_storage.allocate((slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = slicing_params.first_print_layer_height; + new_layer.height = slicing_params.first_print_layer_height; + new_layer.bottom_z = 0.; + first_layer = union_(std::move(first_layer), base); + new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(first_layer, inflate_factor_1st_layer) : first_layer; + } + // Insert the base layers. + for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(SupporLayerType::sltRaftBase); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + slicing_params.base_raft_layer_height; + new_layer.height = slicing_params.base_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = base; + } + // Insert the interface layers. + for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(SupporLayerType::sltRaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; + new_layer.height = slicing_params.interface_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = interface_polygons; + //FIXME misusing contact_polygons for support columns. + new_layer.contact_polygons = std::make_unique(columns); + } + } else { + if (columns_base != nullptr) { + // Expand the bases of the support columns in the 1st layer. + Polygons &raft = columns_base->polygons; + Polygons trimming; + // QDS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode. + // brim_object_gap is changed to 0 by default, it's no longer appropriate to use it to determine the gap of first layer support. + //if (object.has_brim()) + // trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); + //else + trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (inflate_factor_1st_layer > SCALED_EPSILON) { + // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. + auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); + float step = inflate_factor_1st_layer / nsteps; + for (int i = 0; i < nsteps; ++ i) + raft = diff(expand(raft, step), trimming); + } else + raft = diff(raft, trimming); + if (! interface_polygons.empty()) + columns_base->polygons = diff(columns_base->polygons, interface_polygons); + } + if (! brim.empty()) { + if (columns_base) + columns_base->polygons = diff(columns_base->polygons, brim); + if (contacts) + contacts->polygons = diff(contacts->polygons, brim); + if (interfaces) + interfaces->polygons = diff(interfaces->polygons, brim); + if (base_interfaces) + base_interfaces->polygons = diff(base_interfaces->polygons, brim); + } + } + + return raft_layers; +} + +static inline void fill_expolygon_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygon &&expolygon, + Fill *filler, + const FillParams &fill_params, + float density, + ExtrusionRole role, + const Flow &flow) +{ + Surface surface(stInternal, std::move(expolygon)); + Polylines polylines; + try { + assert(!fill_params.use_arachne); + polylines = filler->fill_surface(&surface, fill_params); + } catch (InfillFailedException &) { + } + extrusion_entities_append_paths( + dst, + std::move(polylines), + role, + flow.mm3_per_mm(), flow.width(), flow.height()); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + const FillParams &fill_params, + float density, + ExtrusionRole role, + const Flow &flow) +{ + for (ExPolygon &expoly : expolygons) + fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, density, role, flow); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow) +{ + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); +} + +static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) +{ + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (size_t i = 0; i <= expoly.holes.size(); ++ i) { + Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); + pl.points.emplace_back(pl.points.front()); + if (i > 0) + // It is a hole, reverse it. + pl.reverse(); + // so that all contours are CCW oriented. + pl.clip_end(clip_length); + polylines.emplace_back(std::move(pl)); + } + return polylines; +} + +void tree_supports_generate_paths( + ExtrusionEntitiesPtr &dst, + const Polygons &polygons, + const Flow &flow, + const SupportParameters &support_params) +{ + // Offset expolygon inside, returns number of expolygons collected (0 or 1). + // Vertices of output paths are marked with Z = source contour index of the expoly. + // Vertices at the intersection of source contours are marked with Z = -1. + auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int + { + assert(delta > 0); + auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) { + dst.reserve(next_highest_power_of_2(dst.size() + src.size())); + for (const ClipperLib::Path &contour : src) { + ClipperLib_Z::Path tmp; + tmp.reserve(contour.size()); + for (const Point &p : contour) + tmp.emplace_back(p.x(), p.y(), contour_idx); + dst.emplace_back(std::move(tmp)); + } + }; + + // 1) Offset the outer contour. + ClipperLib_Z::Paths contours; + { + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(delta * 0.005); + co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths contours_raw; + co.Execute(contours_raw, - delta); + if (contours_raw.empty()) + // No need to try to offset the holes. + return 0; + append_paths_with_z(contours_raw, 0, contours); + } + + if (expoly.holes.empty()) { + // No need to subtract holes from the offsetted expolygon, we are done. + append(out, std::move(contours)); + } else { + // 2) Offset the holes one by one, collect the offsetted holes. + ClipperLib_Z::Paths holes; + { + for (const Polygon &hole : expoly.holes) { + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(delta * 0.005); + co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths out2; + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + co.Execute(out2, delta); + append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes); + } + } + + // 3) Subtract holes from the contours. + if (holes.empty()) { + // No hole remaining after an offset. Just copy the outer contour. + append(out, std::move(contours)); + } else { + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Subtract the offsetted holes from the offsetted contours. + ClipperLib_Z::Clipper clipper; + clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { + //pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); + // Just mark the intersection. + pt.z() = -1; + }); + clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true); + clipper.AddPaths(holes, ClipperLib_Z::ptClip, true); + ClipperLib_Z::Paths output; + clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + if (! output.empty()) { + append(out, std::move(output)); + } else { + // The offsetted holes have eaten up the offsetted outer contour. + return 0; + } + } + } + + return 1; + }; + + const double spacing = flow.scaled_spacing(); + // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. + const double clip_length = spacing * 0.15; + const double anchor_length = spacing * 6.; + ClipperLib_Z::Paths anchor_candidates; + for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { + std::unique_ptr eec; + ExPolygons regions_to_draw_inner_wall{expoly}; + if (support_params.tree_branch_diameter_double_wall_area_scaled > 0) + if (double area = expoly.area(); area > support_params.tree_branch_diameter_double_wall_area_scaled) { + BOOST_LOG_TRIVIAL(debug)<< "TreeSupports: double wall area: " << area<< " > " << support_params.tree_branch_diameter_double_wall_area_scaled; + eec = std::make_unique(); + // Don't reorder internal / external loops of the same island, always start with the internal loop. + eec->no_sort = true; + // Make the tree branch stable by adding another perimeter. + ExPolygons level2 = offset2_ex({expoly}, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); + if (level2.size() > 0) { + regions_to_draw_inner_wall = level2; + extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + expoly = level2.front(); + } + } + for (ExPolygon &expoly : regions_to_draw_inner_wall) + { + // Try to produce one more perimeter to place the seam anchor. + // First genrate a 2nd perimeter loop as a source for anchor candidates. + // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. + anchor_candidates.clear(); + shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); + // Orient all contours CW. + for (auto &path : anchor_candidates) + if (ClipperLib_Z::Area(path) > 0) std::reverse(path.begin(), path.end()); + + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (int idx_loop = 0; idx_loop < int(expoly.num_contours()); ++idx_loop) { + // Open the loop with a seam. + const Polygon &loop = expoly.contour_or_hole(idx_loop); + Polyline pl(loop.points); + // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. + if (idx_loop == 0) + // It is an outer contour. + pl.reverse(); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(clip_length); + if (pl.size() < 2) continue; + // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. + ClipperLib_Z::Path *closest_contour = nullptr; + Vec2d closest_point; + int closest_point_idx = -1; + double closest_point_t = 0.; + double d2min = std::numeric_limits::max(); + Vec2d seam_pt = pl.back().cast(); + for (ClipperLib_Z::Path &path : anchor_candidates) + for (int i = 0; i < int(path.size()); ++i) { + int j = next_idx_modulo(i, path); + if (path[i].z() == idx_loop || path[j].z() == idx_loop) { + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + Vec2d w = seam_pt - pi; + auto l2 = v.squaredNorm(); + auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); + if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { + // Closest point. + Vec2d fp = pi + v * t; + double d2 = (fp - seam_pt).squaredNorm(); + if (d2 < d2min) { + d2min = d2; + closest_contour = &path; + closest_point = fp; + closest_point_idx = i; + closest_point_t = t; + } + } + } + } + if (d2min < sqr(flow.scaled_width() * 3.)) { + // Try to cut an anchor from the closest_contour. + // Both closest_contour and pl are CW oriented. + pl.points.emplace_back(closest_point.cast()); + const ClipperLib_Z::Path &path = *closest_contour; + double remaining_length = anchor_length - (seam_pt - closest_point).norm(); + int i = closest_point_idx; + int j = next_idx_modulo(i, *closest_contour); + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + double l = v.norm(); + if (remaining_length < (1. - closest_point_t) * l) { + // Just trim the current line. + pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); + } else { + // Take the rest of the current line, continue with the other lines. + pl.points.emplace_back(path[j].x(), path[j].y()); + pi = pj; + for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { + j = next_idx_modulo(i, path); + pj = Vec2d(path[j].x(), path[j].y()); + v = pj - pi; + l = v.norm(); + if (i == closest_point_idx) { + // Back at the first segment. Most likely this should not happen and we may end the anchor. + break; + } + if (remaining_length <= l) { + pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); + break; + } + pl.points.emplace_back(path[j].x(), path[j].y()); + remaining_length -= l; + } + } + } + // Start with the anchor. + pl.reverse(); + polylines.emplace_back(std::move(pl)); + } + + ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; + extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + } + if (eec) { + std::reverse(eec->entities.begin(), eec->entities.end()); + dst.emplace_back(eec.release()); + } + } +} + +void fill_expolygons_with_sheath_generate_paths( + ExtrusionEntitiesPtr &dst, + const Polygons &polygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow, + const SupportParameters& support_params, + bool with_sheath, + bool no_sort) +{ + if (polygons.empty()) + return; + + if (with_sheath) { + if (density == 0) { + tree_supports_generate_paths(dst, polygons, flow, support_params); + return; + } + } + else { + fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); + return; + } + + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + + const double spacing = flow.scaled_spacing(); + // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. + const double clip_length = spacing * 0.15; + + for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { + // Don't reorder the skirt and its infills. + std::unique_ptr eec; + if (no_sort) { + eec = std::make_unique(); + eec->no_sort = true; + } + ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; + extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); + // Fill in the rest. + fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); + if (no_sort && ! eec->empty()) + dst.emplace_back(eec.release()); + } +} + +// Support layers, partially processed. +struct SupportGeneratorLayerExtruded +{ + SupportGeneratorLayerExtruded& operator=(SupportGeneratorLayerExtruded &&rhs) { + this->layer = rhs.layer; + this->extrusions = std::move(rhs.extrusions); + m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); + rhs.layer = nullptr; + return *this; + } + + bool empty() const { + return layer == nullptr || layer->polygons.empty(); + } + + void set_polygons_to_extrude(Polygons &&polygons) { + if (m_polygons_to_extrude == nullptr) + m_polygons_to_extrude = std::make_unique(std::move(polygons)); + else + *m_polygons_to_extrude = std::move(polygons); + } + Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + + bool could_merge(const SupportGeneratorLayerExtruded &other) const { + return ! this->empty() && ! other.empty() && + std::abs(this->layer->height - other.layer->height) < EPSILON && + this->layer->bridging == other.layer->bridging; + } + + // Merge regions, perform boolean union over the merged polygons. + void merge(SupportGeneratorLayerExtruded &&other) { + assert(this->could_merge(other)); + // 1) Merge the rest polygons to extrude, if there are any. + if (other.m_polygons_to_extrude != nullptr) { + if (m_polygons_to_extrude == nullptr) { + // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(this->extrusions.empty()); + m_polygons_to_extrude = std::make_unique(this->layer->polygons); + } + Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + other.m_polygons_to_extrude.reset(); + } else if (m_polygons_to_extrude != nullptr) { + assert(other.m_polygons_to_extrude == nullptr); + // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(other.extrusions.empty()); + Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + } + // 2) Merge the extrusions. + this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); + other.extrusions.clear(); + // 3) Merge the infill polygons. + Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); + this->layer->polygons = union_safety_offset(this->layer->polygons); + other.layer->polygons.clear(); + } + + void polygons_append(Polygons &dst) const { + if (layer != NULL && ! layer->polygons.empty()) + Slic3r::polygons_append(dst, layer->polygons); + } + + // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). + SupportGeneratorLayer *layer { nullptr }; + // Collect extrusions. They will be exported sorted by the bottom height. + ExtrusionEntitiesPtr extrusions; + +private: + // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. + // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. + std::unique_ptr m_polygons_to_extrude; +}; + +typedef std::vector SupportGeneratorLayerExtrudedPtrs; + +struct LoopInterfaceProcessor +{ + LoopInterfaceProcessor(coordf_t circle_r) : + n_contact_loops(0), + circle_radius(circle_r), + circle_distance(circle_r * 3.) + { + // Shape of the top contact area. + circle.points.reserve(6); + for (size_t i = 0; i < 6; ++ i) { + double angle = double(i) * M_PI / 3.; + circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); + } + } + + // Generate loop contacts at the top_contact_layer, + // trim the top_contact_layer->polygons with the areas covered by the loops. + void generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; + + int n_contact_loops; + coordf_t circle_radius; + coordf_t circle_distance; + Polygon circle; +}; + +void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const +{ + if (n_contact_loops == 0 || top_contact_layer.empty()) + return; + + Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); + + Polygons overhang_polygons; + if (top_contact_layer.layer->overhang_polygons != nullptr) + overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); + + // Generate the outermost loop. + // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) + ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); + + // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. + coord_t circle_grid_resolution = 1; + coord_t circle_grid_powerof2 = 0; + { + // epsilon to account for rounding errors + coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); + while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { + circle_grid_resolution <<= 1; + ++ circle_grid_powerof2; + } + } + + struct PointAccessor { + const Point* operator()(const Point &pt) const { return &pt; } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + + Polygons loops0; + { + // find centerline of the external loop of the contours + // Only consider the loops facing the overhang. + Polygons external_loops; + // Holes in the external loops. + Polygons circles; + Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); + for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { + // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. + ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); + Points circle_centers; + Point center_last; + // For each contour of the expolygon, start with the outer contour, continue with the holes. + for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { + Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; + const Point *seg_current_pt = nullptr; + coordf_t seg_current_t = 0.; + if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { + // The contour is below the overhang at least to some extent. + //FIXME ideally one would place the circles below the overhang only. + // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. + if (circle_centers.empty()) { + // Place the first circle. + seg_current_pt = &contour.points.front(); + seg_current_t = 0.; + center_last = *seg_current_pt; + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { + // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? + const Point &p1 = *(it-1); + const Point &p2 = *it; + // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. + const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); + const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); + coordf_t a = v_seg.squaredNorm(); + coordf_t b = 2. * v_seg.dot(v_cntr); + coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; + coordf_t disc = b * b - 4. * a * c; + if (disc > 0.) { + // The circle intersects a ray. Avoid the parts of the segment inside the circle. + coordf_t t1 = (-b - sqrt(disc)) / (2. * a); + coordf_t t2 = (-b + sqrt(disc)) / (2. * a); + coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; + // Take the lowest t in , excluding . + coordf_t t; + if (t0 <= t1) + t = t0; + else if (t2 <= 1.) + t = t2; + else { + // Try the following segment. + seg_current_pt = nullptr; + continue; + } + seg_current_pt = &p1; + seg_current_t = t; + center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); + // It has been verified that the new point is far enough from center_last. + // Ensure, that it is far enough from all the centers. + std::pair circle_closest = circle_centers_lookup.find(center_last); + if (circle_closest.first != nullptr) { + -- it; + continue; + } + } else { + // All of the segment is outside the circle. Take the first point. + seg_current_pt = &p1; + seg_current_t = 0.; + center_last = p1; + } + // Place the first circle. + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + external_loops.push_back(std::move(contour)); + for (const Point ¢er : circle_centers) { + circles.push_back(circle); + circles.back().translate(center); + } + } + } + } + // Apply a pattern to the external loops. + loops0 = diff(external_loops, circles); + } + + Polylines loop_lines; + { + // make more loops + Polygons loop_polygons = loops0; + for (int i = 1; i < n_contact_loops; ++ i) + polygons_append(loop_polygons, + opening( + loops0, + i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), + 0.5f * flow.scaled_spacing())); + // Clip such loops to the side oriented towards the object. + // Collect split points, so they will be recognized after the clipping. + // At the split points the clipped pieces will be stitched back together. + loop_lines.reserve(loop_polygons.size()); + std::unordered_map map_split_points; + for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { + assert(map_split_points.find(it->first_point()) == map_split_points.end()); + map_split_points[it->first_point()] = -1; + loop_lines.push_back(it->split_at_first_point()); + } + loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. + // Try to connect them. + for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { + Polyline &polyline = loop_lines[i_line]; + auto it = map_split_points.find(polyline.first_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + continue; + } + it = map_split_points.find(polyline.last_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + } + } + // Remove empty lines. + remove_degenerate(loop_lines); + } + + // add the contact infill area to the interface area + // note that growing loops by $circle_radius ensures no tiny + // extrusions are left inside the circles; however it creates + // a very large gap between loops and contact_infill_polygons, so maybe another + // solution should be found to achieve both goals + // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for + // "modulate by layer thickness". + top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); + + // Transform loops into ExtrusionPath objects. + extrusion_entities_append_paths( + top_contact_layer.extrusions, + std::move(loop_lines), + ExtrusionRole::erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); +} + +#ifdef SLIC3R_DEBUG +static std::string dbg_index_to_color(int idx) +{ + if (idx < 0) + return "yellow"; + idx = idx % 3; + switch (idx) { + case 0: return "red"; + case 1: return "green"; + default: return "blue"; + } +} +#endif /* SLIC3R_DEBUG */ + +// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore +// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). +// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. +// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, +// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers +// to stick too firmly to the object. +// +// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer +// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. +static void modulate_extrusion_by_overlapping_layers( + // Extrusions generated for this_layer. + ExtrusionEntitiesPtr &extrusions_in_out, + const SupportGeneratorLayer &this_layer, + // Multiple layers overlapping with this_layer, sorted bottom up. + const SupportGeneratorLayersPtr &overlapping_layers) +{ + size_t n_overlapping_layers = overlapping_layers.size(); + if (n_overlapping_layers == 0 || extrusions_in_out.empty()) + // The extrusions do not overlap with any other extrusion. + return; + + // Get the initial extrusion parameters. + ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); + assert(extrusion_path_template != nullptr); + ExtrusionRole extrusion_role = extrusion_path_template->role(); + float extrusion_width = extrusion_path_template->width; + + struct ExtrusionPathFragment + { + ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; + ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; + + Polylines polylines; + double mm3_per_mm; + float width; + float height; + }; + + // Split the extrusions by the overlapping layers, reduce their extrusion rate. + // The last path_fragment is from this_layer. + std::vector path_fragments( + n_overlapping_layers + 1, + ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); + // Don't use it, it will be released. + extrusion_path_template = nullptr; + +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; + BoundingBox bbox; + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + bbox.merge(get_extents(overlapping_layer.polygons)); + } + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + assert(path != nullptr); + bbox.merge(get_extents(path->polyline)); + } + SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); + const float transparency = 0.5f; + // Filled polygons for the overlapping regions. + svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); + } + // Contours of the overlapping regions. + svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); + } + // Fill extrusion, the source. + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + std::string color_name; + switch ((it - extrusions_in_out.begin()) % 9) { + case 0: color_name = "magenta"; break; + case 1: color_name = "deepskyblue"; break; + case 2: color_name = "coral"; break; + case 3: color_name = "goldenrod"; break; + case 4: color_name = "orange"; break; + case 5: color_name = "olivedrab"; break; + case 6: color_name = "blueviolet"; break; + case 7: color_name = "brown"; break; + default: color_name = "orchid"; break; + } + svg.draw(path->polyline, color_name, scale_(0.2)); + } +#endif /* SLIC3R_DEBUG */ + + // End points of the original paths. + std::vector> path_ends; + // Collect the paths of this_layer. + { + Polylines &polylines = path_fragments.back().polylines; + for (ExtrusionEntity *ee : extrusions_in_out) { + ExtrusionPath *path = dynamic_cast(ee); + assert(path != nullptr); + polylines.emplace_back(Polyline(std::move(path->polyline))); + path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); + delete path; + } + } + // Destroy the original extrusion paths, their polylines were moved to path_fragments already. + // This will be the destination for the new paths. + extrusions_in_out.clear(); + + // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. + // Trim by the highest overlapping layer first. + for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; + Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); + frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); + path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); + // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). + assert(this_layer.print_z > overlapping_layer.print_z); + frag.height = float(this_layer.print_z - overlapping_layer.print_z); + frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); +#ifdef SLIC3R_DEBUG + svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); +#endif /* SLIC3R_DEBUG */ + } + +#ifdef SLIC3R_DEBUG + svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); + svg.Close(); +#endif /* SLIC3R_DEBUG */ + + // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. + // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. + // Map of fragment start/end points to a pair of + // Because a non-exact matching is used for the end points, a multi-map is used. + // As the clipper library may reverse the order of some clipped paths, store both ends into the map. + struct ExtrusionPathFragmentEnd + { + ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : + layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} + size_t layer_idx; + size_t polyline_idx; + bool is_start; + }; + class ExtrusionPathFragmentEndPointAccessor { + public: + ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} + // Return an end point of a fragment, or nullptr if the fragment has been consumed already. + const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { + const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; + return polyline.points.empty() ? nullptr : + (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); + } + private: + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { + return *this; + } + + const std::vector &m_path_fragments; + }; + const coord_t search_radius = 7; + ClosestPointInRadiusLookup map_fragment_starts( + search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { + const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; + for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { + // Map a starting point of a polyline to a pair of + if (polylines[i_polyline].points.size() >= 2) { + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); + } + } + } + + // For each source path: + for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { + const Point &pt_start = path_ends[i_path].first; + const Point &pt_end = path_ends[i_path].second; + Point pt_current = pt_start; + // Find a chain of fragments with the original / reduced print height. + ExtrusionMultiPath multipath; + for (;;) { + // Find a closest end point to pt_current. + std::pair end_and_dist2 = map_fragment_starts.find(pt_current); + // There may be a bug in Clipper flipping the order of two last points in a fragment? + // assert(end_and_dist2.first != nullptr); + assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); + if (end_and_dist2.first == nullptr) { + // New fragment connecting to pt_current was not found. + // Verify that the last point found is close to the original end point of the unfragmented path. + //const double d2 = (pt_end - pt_current).cast.squaredNorm(); + //assert(d2 < coordf_t(search_radius * search_radius)); + // End of the path. + break; + } + const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; + // Fragment to consume. + ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; + Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; + // Path to append the fragment to. + ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); + if (path != nullptr) { + // Verify whether the path is compatible with the current fragment. + assert(this_layer.layer_type == sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); + if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { + path = nullptr; + } + // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. + } + if (path == nullptr) { + // Allocate a new path. + multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); + path = &multipath.paths.back(); + } + // The Clipper library may flip the order of the clipped polylines arbitrarily. + // Reverse the source polyline, if connecting to the end. + if (! fragment_end_min.is_start) + frag_polyline.reverse(); + // Enforce exact overlap of the end points of successive fragments. + assert(frag_polyline.points.front() == pt_current); + frag_polyline.points.front() = pt_current; + // Don't repeat the first point. + if (! path->polyline.points.empty()) + path->polyline.points.pop_back(); + // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. + path->polyline.append(std::move(frag_polyline)); + frag_polyline.points.clear(); + pt_current = path->polyline.points.back(); + if (pt_current == pt_end) { + // End of the path. + break; + } + } + if (!multipath.paths.empty()) { + if (multipath.paths.size() == 1) { + // This path was not fragmented. + extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); + } else { + // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed + // during the chaining of extrusions_in_out. + extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); + } + } + } + // If there are any non-consumed fragments, add them separately. + //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. + for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) + extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); +} + +// Support layer that is covered by some form of dense interface. +static constexpr const std::initializer_list support_types_interface{ + SupporLayerType::sltRaftInterface, SupporLayerType::sltBottomContact, SupporLayerType::sltBottomInterface, SupporLayerType::sltTopContact, SupporLayerType::sltTopInterface +}; + +SupportGeneratorLayersPtr generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) +{ + // Install support layers into the object. + // A support layer installed on a PrintObject has a unique print_z. + SupportGeneratorLayersPtr layers_sorted; + layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); + append(layers_sorted, raft_layers); + append(layers_sorted, bottom_contacts); + append(layers_sorted, top_contacts); + append(layers_sorted, intermediate_layers); + append(layers_sorted, interface_layers); + append(layers_sorted, base_interface_layers); + // remove dupliated layers + std::sort(layers_sorted.begin(), layers_sorted.end()); + layers_sorted.erase(std::unique(layers_sorted.begin(), layers_sorted.end()), layers_sorted.end()); + + // Sort the layers lexicographically by a raising print_z and a decreasing height. + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + int layer_id = 0; + int layer_id_interface = 0; + assert(object.support_layers().empty()); + for (size_t i = 0; i < layers_sorted.size();) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + size_t j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; + // Assign an average print_z to the set of layers with nearly equal print_z. + coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); + coordf_t height_min = layers_sorted[i]->height; + bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; + for (size_t u = i; u < j; ++u) { + SupportGeneratorLayer &layer = *layers_sorted[u]; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == SupporLayerType::sltTopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } + layer.print_z = zavg; + height_min = std::min(height_min, layer.height); + } + if (! empty) { + // Here the upper_layer and lower_layer pointers are left to null at the support layers, + // as they are never used. These pointers are candidates for removal. + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; + } + i = j; + } + return layers_sorted; +} + +void generate_support_toolpaths( + SupportLayerPtrs &support_layers, + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) +{ + // loop_interface_processor with a given circle radius. + LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); + loop_interface_processor.n_contact_loops = config.support_interface_loop_pattern.value ? 1 : 0; + + std::vector angles { support_params.base_angle }; + if (config.support_base_pattern == smpRectilinearGrid) + angles.push_back(support_params.interface_angle); + + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); + +// const coordf_t link_max_length_factor = 3.; + const coordf_t link_max_length_factor = 0.; + + // Insert the raft base layers. + auto n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); + + tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), + [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, + &bbox_object, link_max_length_factor] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + assert(support_layer_id < raft_layers.size()); + SupportLayer &support_layer = *support_layers[support_layer_id]; + assert(support_layer.support_fills.entities.empty()); + SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; + + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(support_params.raft_interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + + // Print the tree supports cutting through the raft with the exception of the 1st layer, where a full support layer will be printed below + // both the raft and the trees. + // Trim the raft layers with the tree polygons. + const Polygons &tree_polygons = + support_layer_id > 0 && support_layer_id < intermediate_layers.size() && is_approx(intermediate_layers[support_layer_id]->print_z, support_layer.print_z) ? + intermediate_layers[support_layer_id]->polygons : Polygons(); + + // Print the support base below the support columns, or the support base for the support columns plus the contacts. + if (support_layer_id > 0) { + const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? + raft_layer.polygons : + //FIXME misusing contact_polygons for support columns. + ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); + // Trees may cut through the raft layers down to a print bed. + Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); + assert(!raft_layer.bridging); + if (! to_infill_polygons.empty()) { + Fill *filler = filler_support.get(); + filler->angle = support_params.raft_angle_base; + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + tree_polygons.empty() ? to_infill_polygons : diff(to_infill_polygons, tree_polygons), + // Filler and its parameters + filler, float(support_params.support_density), + // Extrusion parameters + ExtrusionRole::erSupportMaterial, flow, + support_params, support_params.with_sheath, false); + } + if (! tree_polygons.empty()) + tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow, support_params); + } + + Fill *filler = filler_interface.get(); + Flow flow = support_params.first_layer_flow; + float density = 0.f; + if (support_layer_id == 0) { + // Base flange. + filler->angle = support_params.raft_angle_1st_layer; + filler->spacing = support_params.first_layer_flow.spacing(); + density = float(config.raft_first_layer_density.value * 0.01); + } else if (support_layer_id >= slicing_params.base_raft_layers) { + filler->angle = support_params.raft_interface_angle(support_layer.interface_id()); + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + filler->spacing = support_params.support_material_flow.spacing(); + assert(! raft_layer.bridging); + flow = Flow(float(support_params.raft_interface_flow.width()), float(raft_layer.height), support_params.raft_interface_flow.nozzle_diameter()); + density = float(support_params.raft_interface_density); + } else + continue; + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons), + // Filler and its parameters + filler, density, + // Extrusion parameters + (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, flow, + // sheath at first layer + support_params, support_layer_id == 0, support_layer_id == 0); + } + }); + + struct LayerCacheItem { + LayerCacheItem(SupportGeneratorLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} + SupportGeneratorLayerExtruded *layer_extruded; + std::vector overlapping; + }; + struct LayerCache { + SupportGeneratorLayerExtruded bottom_contact_layer; + SupportGeneratorLayerExtruded top_contact_layer; + SupportGeneratorLayerExtruded base_layer; + SupportGeneratorLayerExtruded interface_layer; + SupportGeneratorLayerExtruded base_interface_layer; + boost::container::static_vector nonempty; + + void add_nonempty_and_sort() { + for (SupportGeneratorLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) + if (! item->empty()) + this->nonempty.emplace_back(item); + // Sort the layers with the same print_z coordinate by their heights, thickest first. + std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + } + }; + std::vector layer_caches(support_layers.size()); + + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, + &bbox_object, &angles, n_raft_layers, link_max_length_factor] + (const tbb::blocked_range& range) { + // Indices of the 1st layer in their respective container at the support layer height. + size_t idx_layer_bottom_contact = size_t(-1); + size_t idx_layer_top_contact = size_t(-1); + size_t idx_layer_intermediate = size_t(-1); + size_t idx_layer_interface = size_t(-1); + size_t idx_layer_base_interface = size_t(-1); + const auto fill_type_first_layer = ipRectilinear; + auto filler_interface = std::unique_ptr(Fill::new_from_type(support_params.contact_fill_pattern)); + // Filler for the 1st layer interface, if different from filler_interface. + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + // Pointer to the 1st layer interface filler. + auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); + // Filler for the 1st layer interface, if different from filler_interface. + auto filler_raft_contact_ptr = std::unique_ptr(range.begin() == n_raft_layers && config.support_interface_top_layers.value == 0 ? + Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr); + // Pointer to the 1st layer interface filler. + auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get(); + // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : + Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + if (filler_first_layer_ptr) + filler_first_layer_ptr->set_bounding_box(bbox_object); + if (filler_raft_contact_ptr) + filler_raft_contact_ptr->set_bounding_box(bbox_object); + if (filler_base_interface) + filler_base_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + const float support_interface_angle = (support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ? + support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); + + // Find polygons with the same print_z. + SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; + SupportGeneratorLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; + SupportGeneratorLayerExtruded &base_layer = layer_cache.base_layer; + SupportGeneratorLayerExtruded &interface_layer = layer_cache.interface_layer; + SupportGeneratorLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; + // Increment the layer indices to find a layer at support_layer.print_z. + { + auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; + idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); + idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); + idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); + idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); + idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); + } + // Copy polygons from the layers. + if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) + bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; + if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) + top_contact_layer.layer = top_contacts[idx_layer_top_contact]; + if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) + interface_layer.layer = interface_layers[idx_layer_interface]; + if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) + base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; + if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) + base_layer.layer = intermediate_layers[idx_layer_intermediate]; + + // This layer is a raft contact layer. Any contact polygons at this layer are raft contacts. + bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z); + if (config.support_interface_top_layers == 0) { + // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. + // Don't merge the raft contact layer though. + if (support_params.can_merge_support_regions && ! raft_layer) { + if (base_layer.could_merge(top_contact_layer)) + base_layer.merge(std::move(top_contact_layer)); + else if (base_layer.empty()) + base_layer = std::move(top_contact_layer); + } + } else { + loop_interface_processor.generate(top_contact_layer, support_params.support_material_interface_flow); + // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. + // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used + // to trim other layers. + if (top_contact_layer.could_merge(interface_layer) && ! raft_layer) + top_contact_layer.merge(std::move(interface_layer)); + } + if ((config.support_interface_top_layers == 0 || config.support_interface_bottom_layers == 0) && support_params.can_merge_support_regions) { + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) + base_layer = std::move(bottom_contact_layer); + } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) + top_contact_layer.merge(std::move(bottom_contact_layer)); + else if (bottom_contact_layer.could_merge(interface_layer)) + bottom_contact_layer.merge(std::move(interface_layer)); + +#if 0 + if ( ! interface_layer.empty() && ! base_layer.empty()) { + // turn base support into interface when it's contained in our holes + // (this way we get wider interface anchoring) + //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes + // inside interface layers, but the code below fills just too much, see GH #4570 + Polygons islands = top_level_islands(interface_layer.layer->polygons); + polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); + base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); + } +#endif + + // Top and bottom contacts, interface layers. + enum class InterfaceLayerType { TopContact, BottomContact, RaftContact, Interface, InterfaceAsBase }; + auto extrude_interface = [&](SupportGeneratorLayerExtruded &layer_ex, InterfaceLayerType interface_layer_type) { + if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) { + bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase; + bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact; + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + auto *filler = raft_contact ? filler_raft_contact : filler_interface.get(); + auto interface_flow = layer_ex.layer->bridging ? + Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : + (raft_contact ? &support_params.raft_interface_flow : + interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow) + ->with_height(float(layer_ex.layer->height)); + filler->angle = interface_as_base ? + // If zero interface layers are configured, use the same angle as for the base layers. + angles[support_layer_id % angles.size()] : + // Use interface angle for the interface layers. + raft_contact ? + support_params.raft_interface_angle(support_layer.interface_id()) : + support_interface_angle; + double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; + filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : + interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + fill_expolygons_generate_paths( + // Destination + layer_ex.extrusions, + // Regions to fill + union_safety_offset_ex(layer_ex.polygons_to_extrude()), + // Filler and its parameters + filler, float(density), + // Extrusion parameters + ExtrusionRole::erSupportMaterialInterface, interface_flow); + } + }; + const bool top_interfaces = config.support_interface_top_layers.value != 0; + const bool bottom_interfaces = top_interfaces && config.support_interface_bottom_layers != 0; + extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase); + extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase); + extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase); + + // Base interface layers under soluble interfaces + if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_base_interface.get(); + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + assert(! base_interface_layer.layer->bridging); + Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); + filler->angle = support_interface_angle; + filler->spacing = support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); + fill_expolygons_generate_paths( + // Destination + base_interface_layer.extrusions, + //base_layer_interface.extrusions, + // Regions to fill + union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), + // Filler and its parameters + filler, float(support_params.interface_density), + // Extrusion parameters + ExtrusionRole::erSupportMaterial, interface_flow); + } + + // Base support or flange. + if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_support.get(); + filler->angle = angles[support_layer_id % angles.size()]; + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + assert(! base_layer.layer->bridging); + auto flow = support_params.support_material_flow.with_height(float(base_layer.layer->height)); + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); + float density = float(support_params.support_density); + bool sheath = support_params.with_sheath; + bool no_sort = false; + bool done = false; + if (base_layer.layer->bottom_z < EPSILON) { + // Base flange (the 1st layer). + filler = filler_first_layer; + filler->angle = Geometry::deg2rad(float(config.support_angle.value + 90.)); + density = float(config.raft_first_layer_density.value * 0.01); + flow = support_params.first_layer_flow; + // use the proper spacing for first layer as we don't need to align + // its pattern to the other layers + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + filler->spacing = flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + sheath = true; + no_sort = true; + } else if (support_params.support_style == SupportMaterialStyle::smsTreeOrganic) { + // if the tree supports are too tall, use double wall to make it stronger + SupportParameters support_params2 = support_params; + if (support_layer.print_z > 100.0) + support_params2.tree_branch_diameter_double_wall_area_scaled = 0.1; + tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params2); + done = true; + } + if (! done) + fill_expolygons_with_sheath_generate_paths( + // Destination + base_layer.extrusions, + // Regions to fill + base_layer.polygons_to_extrude(), + // Filler and its parameters + filler, density, + // Extrusion parameters + ExtrusionRole::erSupportMaterial, flow, + support_params, sheath, no_sort); + } + + // Merge base_interface_layers to base_layers to avoid unneccessary retractions + if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && + base_layer.could_merge(base_interface_layer)) + base_layer.merge(std::move(base_interface_layer)); + + layer_cache.add_nonempty_and_sort(); + + // Collect the support areas with this print_z into islands, as there is no need + // for retraction over these islands. + Polygons polys; + // Collect the extrusions, sorted by the bottom extrusion height. + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Collect islands to polys. + layer_cache_item.layer_extruded->polygons_append(polys); + // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" + // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces + // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially + // overlap in Z with another support layers, leading to over-extrusion. + // Mitigate the over-extrusion by modulating the extrusion rate over these regions. + // The print head will follow the same print_z, but the layer thickness will be reduced + // where it overlaps with another support layer. + //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? + // Collect overlapping top/bottom surfaces. + layer_cache_item.overlapping.reserve(20); + coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; + auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { + for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) + layer_cache_item.overlapping.push_back(layers[i]); + }; + add_overlapping(top_contacts, idx_layer_top_contact); + if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::sltBottomContact) { + // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. + add_overlapping(intermediate_layers, idx_layer_intermediate); + add_overlapping(interface_layers, idx_layer_interface); + add_overlapping(base_interface_layers, idx_layer_base_interface); + } + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + } + assert(support_layer.support_islands.empty()); + if (! polys.empty()) { + support_layer.support_islands = union_ex(polys); + // support_layer.support_islands_bboxes.reserve(support_layer.support_islands.size()); + // for (const ExPolygon &expoly : support_layer.support_islands) + // support_layer.support_islands_bboxes.emplace_back(get_extents(expoly).inflated(SCALED_EPSILON)); + } + } // for each support_layer_id + }); + + // Now modulate the support layer height in parallel. + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [&support_layers, &layer_caches] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + // For all extrusion types at this print_z, ordered by decreasing layer height: + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Trim the extrusion height from the bottom by the overlapping layers. + modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); + support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); + } + } + }); + +#ifndef NDEBUG + struct Test { + static bool verify_nonempty(const ExtrusionEntityCollection *collection) { + for (const ExtrusionEntity *ee : collection->entities) { + if (const ExtrusionPath *path = dynamic_cast(ee)) + assert(! path->empty()); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) + assert(! multipath->empty()); + else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { + assert(! eecol->empty()); + return verify_nonempty(eecol); + } else + assert(false); + } + return true; + } + }; + for (const SupportLayer *support_layer : support_layers) + assert(Test::verify_nonempty(&support_layer->support_fills)); +#endif // NDEBUG +} + +/* +void PrintObjectSupportMaterial::clip_by_pillars( + const PrintObject &object, + LayersPtr &bottom_contacts, + LayersPtr &top_contacts, + LayersPtr &intermediate_contacts); + +{ + // this prevents supplying an empty point set to BoundingBox constructor + if (top_contacts.empty()) + return; + + coord_t pillar_size = scale_(PILLAR_SIZE); + coord_t pillar_spacing = scale_(PILLAR_SPACING); + + // A regular grid of pillars, filling the 2D bounding box. + Polygons grid; + { + // Rectangle with a side of 2.5x2.5mm. + Polygon pillar; + pillar.points.push_back(Point(0, 0)); + pillar.points.push_back(Point(pillar_size, 0)); + pillar.points.push_back(Point(pillar_size, pillar_size)); + pillar.points.push_back(Point(0, pillar_size)); + + // 2D bounding box of the projection of all contact polygons. + BoundingBox bbox; + for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) + bbox.merge(get_extents((*it)->polygons)); + grid.reserve(size_t(ceil(bb.size()(0) / pillar_spacing)) * size_t(ceil(bb.size()(1) / pillar_spacing))); + for (coord_t x = bb.min(0); x <= bb.max(0) - pillar_size; x += pillar_spacing) { + for (coord_t y = bb.min(1); y <= bb.max(1) - pillar_size; y += pillar_spacing) { + grid.push_back(pillar); + for (size_t i = 0; i < pillar.points.size(); ++ i) + grid.back().points[i].translate(Point(x, y)); + } + } + } + + // add pillars to every layer + for my $i (0..n_support_z) { + $shape->[$i] = [ @$grid ]; + } + + // build capitals + for my $i (0..n_support_z) { + my $z = $support_z->[$i]; + + my $capitals = intersection( + $grid, + $contact->{$z} // [], + ); + + // work on one pillar at time (if any) to prevent the capitals from being merged + // but store the contact area supported by the capital because we need to make + // sure nothing is left + my $contact_supported_by_capitals = []; + foreach my $capital (@$capitals) { + // enlarge capital tops + $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2); + push @$contact_supported_by_capitals, @$capital; + + for (my $j = $i-1; $j >= 0; $j--) { + my $jz = $support_z->[$j]; + $capital = offset($capital, -$self->interface_flow->scaled_width/2); + last if !@$capitals; + push @{ $shape->[$j] }, @$capital; + } + } + + // Capitals will not generally cover the whole contact area because there will be + // remainders. For now we handle this situation by projecting such unsupported + // areas to the ground, just like we would do with a normal support. + my $contact_not_supported_by_capitals = diff( + $contact->{$z} // [], + $contact_supported_by_capitals, + ); + if (@$contact_not_supported_by_capitals) { + for (my $j = $i-1; $j >= 0; $j--) { + push @{ $shape->[$j] }, @$contact_not_supported_by_capitals; + } + } + } +} + +sub clip_with_shape { + my ($self, $support, $shape) = @_; + + foreach my $i (keys %$support) { + // don't clip bottom layer with shape so that we + // can generate a continuous base flange + // also don't clip raft layers + next if $i == 0; + next if $i < $self->object_config->raft_layers; + $support->{$i} = intersection( + $support->{$i}, + $shape->[$i], + ); + } +} +*/ + +} // namespace Slic3r diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp new file mode 100644 index 0000000..f4a6b68 --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -0,0 +1,153 @@ +///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_SupportCommon_hpp_ +#define slic3r_SupportCommon_hpp_ + +#include "../Layer.hpp" +#include "../Polygon.hpp" +#include "../Print.hpp" +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" + +namespace Slic3r { + +class PrintObject; +class SupportLayer; + +// Turn some of the base layers into base interface layers. +// For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base +// extruder to improve adhesion of the soluble filament to the base. +// For Organic supports, merge top_interface_layers & top_base_interface_layers with the interfaces +// produced by this function. +std::pair generate_interface_layers( + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + // Input / output, will be merged with output + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayersPtr &top_base_interface_layers, + // Input, will be trimmed with the newly created interface layers. + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage); + +// Generate raft layers, also expand the 1st support layer +// in case there is no raft layer to improve support adhesion. +SupportGeneratorLayersPtr generate_raft_base( + const PrintObject &object, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers, + const SupportGeneratorLayersPtr &base_layers, + SupportGeneratorLayerStorage &layer_storage); + +void tree_supports_generate_paths(ExtrusionEntitiesPtr &dst, const Polygons &polygons, const Flow &flow, const SupportParameters &support_params); + +void fill_expolygons_with_sheath_generate_paths( + ExtrusionEntitiesPtr &dst, const Polygons &polygons, Fill *filler, float density, ExtrusionRole role, const Flow &flow, const SupportParameters& support_params, bool with_sheath, bool no_sort); + +// returns sorted layers +SupportGeneratorLayersPtr generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers); + +// Produce the support G-code. +// Used by both classic and tree supports. +void generate_support_toolpaths( + SupportLayerPtrs &support_layers, + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers); + +// FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold. +// Find the first item with Z value >= of an internal threshold of fn_higher_equal. +// If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size() +// If the initial idx is size_t(-1), then use binary search. +// Otherwise search linearly upwards. +template +IndexType idx_higher_or_equal(IteratorType begin, IteratorType end, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) +{ + auto size = int(end - begin); + if (size == 0) { + idx = 0; + } else if (idx == IndexType(-1)) { + // First of the batch of layers per thread pool invocation. Use binary search. + int idx_low = 0; + int idx_high = std::max(0, size - 1); + while (idx_low + 1 < idx_high) { + int idx_mid = (idx_low + idx_high) / 2; + if (fn_higher_equal(begin[idx_mid])) + idx_high = idx_mid; + else + idx_low = idx_mid; + } + idx = fn_higher_equal(begin[idx_low]) ? idx_low : + (fn_higher_equal(begin[idx_high]) ? idx_high : size); + } else { + // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. + while (int(idx) < size && ! fn_higher_equal(begin[idx])) + ++ idx; + } + return idx; +} +template +IndexType idx_higher_or_equal(const std::vector& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) +{ + return idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal); +} + +// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold. +// Find the first item with Z value <= of an internal threshold of fn_lower_equal. +// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1. +// If the initial idx is < -1, then use binary search. +// Otherwise search linearly downwards. +template +int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal) +{ + auto size = int(end - begin); + if (size == 0) { + idx = -1; + } else if (idx < -1) { + // First of the batch of layers per thread pool invocation. Use binary search. + int idx_low = 0; + int idx_high = std::max(0, size - 1); + while (idx_low + 1 < idx_high) { + int idx_mid = (idx_low + idx_high) / 2; + if (fn_lower_equal(begin[idx_mid])) + idx_low = idx_mid; + else + idx_high = idx_mid; + } + idx = fn_lower_equal(begin[idx_high]) ? idx_high : + (fn_lower_equal(begin[idx_low ]) ? idx_low : -1); + } else { + // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. + while (idx >= 0 && ! fn_lower_equal(begin[idx])) + -- idx; + } + return idx; +} +template +int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) +{ + return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); +} + +} // namespace Slic3r + +#endif /* slic3r_SupportCommon_hpp_ */ diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 31a905f..22b8b37 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -4,6 +4,7 @@ #include "Layer.hpp" #include "Print.hpp" #include "SupportMaterial.hpp" +#include "SupportCommon.hpp" #include "Geometry.hpp" #include "Point.hpp" #include "MutablePolygon.hpp" @@ -328,22 +329,6 @@ static Polygons contours_simplified(const Vec2i &grid_size, const double pixel_s } #endif // SUPPORT_USE_AGG_RASTERIZER -static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "qdt_ts") -{ - static bool rand_init = false; - - if (!rand_init) { - srand(time(NULL)); - rand_init = true; - } - - int rand_num = rand() % 1000000; - //makedir("./SVG"); - std::string prefix = "./SVG/"; - std::string suffix = ".svg"; - return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; -} - PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : m_print_config (&object->print()->config()), m_object_config (&object->config()), @@ -566,20 +551,8 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) } #endif /* SLIC3R_DEBUG */ -#if 0 // #ifdef SLIC3R_DEBUG - // check bounds - std::ofstream out; - out.open("./SVG/ns_support_layers.txt"); - if (out.is_open()) { - out << "### Support Layers ###" << std::endl; - for (auto& i : object.support_layers()) { - out << i->print_z << std::endl; - } - } -#endif /* SLIC3R_DEBUG */ - // Generate the actual toolpaths and save them into each layer. - generate_support_toolpaths(object, object.support_layers(), *m_object_config, m_support_params, m_slicing_params, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + generate_support_toolpaths(object.support_layers(), *m_object_config, m_support_params, m_slicing_params, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); #ifdef SLIC3R_DEBUG { @@ -2244,10 +2217,6 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( layer->sharp_tails.push_back(expoly); layer->sharp_tails_height.push_back( accum_height ); append(overhangs_per_layers[layer_nr], overhang); -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), object.bounding_box()); - if (svg.is_opened()) svg.draw(overhang, "yellow"); -#endif } } @@ -2748,49 +2717,6 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ return bottom_contacts; } -template -IndexType idx_higher_or_equal(const std::vector& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) -{ - return Layer::idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal); -} - -// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold. -// Find the first item with Z value <= of an internal threshold of fn_lower_equal. -// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1. -// If the initial idx is < -1, then use binary search. -// Otherwise search linearly downwards. -template -int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal) -{ - auto size = int(end - begin); - if (size == 0) { - idx = -1; - } else if (idx < -1) { - // First of the batch of layers per thread pool invocation. Use binary search. - int idx_low = 0; - int idx_high = std::max(0, size - 1); - while (idx_low + 1 < idx_high) { - int idx_mid = (idx_low + idx_high) / 2; - if (fn_lower_equal(begin[idx_mid])) - idx_low = idx_mid; - else - idx_high = idx_mid; - } - idx = fn_lower_equal(begin[idx_high]) ? idx_high : - (fn_lower_equal(begin[idx_low ]) ? idx_low : -1); - } else { - // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. - while (idx >= 0 && ! fn_lower_equal(begin[idx])) - -- idx; - } - return idx; -} -template -int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) -{ - return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); -} - // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const @@ -3002,36 +2928,6 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp assert(top_contacts[i]->height > 0.); #endif /* _DEBUG */ -#if 0 // #ifdef SLIC3R_DEBUG - // check bounds - std::ofstream out; - out.open("./SVG/ns_bounds.txt"); - if (out.is_open()) { - if (!top_contacts.empty()) { - out << "### Top Contacts ###" << std::endl; - for (auto& t : top_contacts) { - out << t->print_z << std::endl; - } - } - if (!bottom_contacts.empty()) { - out << "### Bottome Contacts ###" << std::endl; - for (auto& b : bottom_contacts) { - out << b->print_z << std::endl; - } - } - if (!intermediate_layers.empty()) { - out << "### Intermediate Layers ###" << std::endl; - for (auto& i : intermediate_layers) { - out << i->print_z << std::endl; - } - } - out << "### Slice Layers ###" << std::endl; - for (size_t j = 0; j < object.layers().size(); ++j) { - out << object.layers()[j]->print_z << std::endl; - } - } -#endif /* SLIC3R_DEBUG */ - return intermediate_layers; } @@ -3291,1854 +3187,6 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } -SupportGeneratorLayersPtr generate_raft_base( - const PrintObject &object, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers, - const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage) -{ - // If there is brim to be generated, calculate the trimming regions. - Polygons brim; - if (object.has_brim()) { - // Calculate the area covered by the brim. - const BrimType brim_type = object.config().brim_type; - const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; - const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - // QDS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft. - const auto brim_object_gap = scaled(object.config().brim_object_gap.value); - //const auto brim_object_gap = scaled(object.config().brim_object_gap.value + object.config().brim_width.value); - for (const ExPolygon &ex : object.layers().front()->lslices) { - if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_object_gap)); - else { - if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)))); - else - brim.emplace_back(ex.contour); - if (brim_inner) { - Polygons holes = ex.holes; - polygons_reverse(holes); - holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))); - polygons_reverse(holes); - polygons_append(brim, std::move(holes)); - } else - polygons_append(brim, ex.holes); - } - } - brim = union_(brim); - } - - // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. - const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); - const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); - SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); - SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); - SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); - SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); - if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON) - // This is not the raft contact layer. - contacts = nullptr; - if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - interfaces = nullptr; - if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - base_interfaces = nullptr; - if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft interface layer. - columns_base = nullptr; - - Polygons interface_polygons; - if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - - // Output vector. - SupportGeneratorLayersPtr raft_layers; - - if (slicing_params.raft_layers() > 1) { - Polygons base; - Polygons columns; - if (columns_base != nullptr) { - base = columns_base->polygons; - columns = base; - if (! interface_polygons.empty()) - // Trim the 1st layer columns with the inflated interface polygons. - columns = diff(columns, interface_polygons); - } - if (! interface_polygons.empty()) { - // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. - base = union_(base, interface_polygons); - } - // Do not add the raft contact layer, only add the raft layers below the contact layer. - // Insert the 1st layer. - { - SupportGeneratorLayer &new_layer = layer_storage.allocate((slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = slicing_params.first_print_layer_height; - new_layer.height = slicing_params.first_print_layer_height; - new_layer.bottom_z = 0.; - new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; - } - // Insert the base layers. - for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::sltRaftBase); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + slicing_params.base_raft_layer_height; - new_layer.height = slicing_params.base_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = base; - } - // Insert the interface layers. - for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::sltRaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; - new_layer.height = slicing_params.interface_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = interface_polygons; - //FIXME misusing contact_polygons for support columns. - new_layer.contact_polygons = std::make_unique(columns); - } - } else { - if (columns_base != nullptr) { - // Expand the bases of the support columns in the 1st layer. - Polygons &raft = columns_base->polygons; - Polygons trimming; - // QDS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode. - // brim_object_gap is changed to 0 by default, it's no longer appropriate to use it to determine the gap of first layer support. - //if (object.has_brim()) - // trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); - //else - trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (inflate_factor_1st_layer > SCALED_EPSILON) { - // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); - float step = inflate_factor_1st_layer / nsteps; - for (int i = 0; i < nsteps; ++ i) - raft = diff(expand(raft, step), trimming); - } else - raft = diff(raft, trimming); - if (! interface_polygons.empty()) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); - } - if (! brim.empty()) { - if (columns_base) - columns_base->polygons = diff(columns_base->polygons, brim); - if (contacts) - contacts->polygons = diff(contacts->polygons, brim); - if (interfaces) - interfaces->polygons = diff(interfaces->polygons, brim); - if (base_interfaces) - base_interfaces->polygons = diff(base_interfaces->polygons, brim); - } - } - - return raft_layers; -} - -// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. -std::pair generate_interface_layers( - const PrintObjectConfig& config, - const SupportParameters& m_support_params, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - // Input / output, will be merged with output. Only provided for Organic supports. - SupportGeneratorLayersPtr &top_interface_layers, - SupportGeneratorLayersPtr &top_base_interface_layers, - // Input, will be trimmed with the newly created interface layers. - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage) -{ -// my $area_threshold = $self->interface_flow->scaled_spacing ** 2; - const PrintObjectConfig* m_object_config = &config; - std::pair base_and_interface_layers; - SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; - SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; - - bool snug_supports = m_object_config->support_style.value == smsSnug; - // QDS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion - // Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if support_filament==0 - bool differnt_support_interface_filament = m_object_config->support_interface_filament != 0 && m_object_config->support_interface_filament.value != m_object_config->support_filament.value; - int num_base_interface_layers_top = differnt_support_interface_filament ? 1 : 0; - int num_base_interface_layers_bottom = differnt_support_interface_filament ? 1 : 0; - int num_interface_layers_top = m_object_config->support_interface_top_layers + num_base_interface_layers_top; - int num_interface_layers_bottom = m_object_config->support_interface_bottom_layers + num_base_interface_layers_bottom; - if (num_interface_layers_bottom < 0) - num_interface_layers_bottom = num_interface_layers_top; - - if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { - // For all intermediate layers, collect top contact surfaces, which are not further than support_interface_top_layers. - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; - // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1. - -- num_interface_layers_top; - -- num_interface_layers_bottom; - int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top; - int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; - interface_layers.assign(intermediate_layers.size(), nullptr); - if (num_base_interface_layers_top || num_base_interface_layers_bottom) - base_interface_layers.assign(intermediate_layers.size(), nullptr); - auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; - auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; - auto closing_distance = smoothing_distance; // scaled(m_object_config->support_closing_radius.value); - // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( - SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { - bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty(); - assert(! bottom.empty() || ! top.empty() || has_top_interface); - // Merge top into bottom, unite them with a safety offset. - append(bottom, std::move(top)); - // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). - bottom = intersection( - snug_supports ? - smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - union_safety_offset(std::move(bottom)), - intermediate_layer.polygons); - if (has_top_interface) { - // Don't trim the precomputed Organic supports top interface with base layer - // as the precomputed top interface likely expands over multiple tree tips. - bottom = union_(std::move(top_interface_layer->polygons), bottom); - top_interface_layer->polygons.clear(); - } - if (! bottom.empty()) { - //FIXME Remove non-printable tiny islands, let them be printed using the base support. - //bottom = opening(std::move(bottom), minimum_island_radius); - if (! bottom.empty()) { - SupportGeneratorLayer &layer_new = top_interface_layer ? *top_interface_layer : layer_storage.allocate(type); - layer_new.polygons = std::move(bottom); - layer_new.print_z = intermediate_layer.print_z; - layer_new.bottom_z = intermediate_layer.bottom_z; - layer_new.height = intermediate_layer.height; - layer_new.bridging = intermediate_layer.bridging; - // Subtract the interface from the base regions. - intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); - if (subtract) - // Trim the base interface layer with the interface layer. - layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); - //FIXME filter layer_new.polygons islands by a minimum area? - // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; - return &layer_new; - } - } - return nullptr; - }; - tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, - num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, - snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { - // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below - // this intermediate layer. - // Index of the first top contact layer intersecting the current intermediate layer. - auto idx_top_contact_first = -1; - // Index of the first bottom contact layer intersecting the current intermediate layer. - auto idx_bottom_contact_first = -1; - // Index of the first top interface layer intersecting the current intermediate layer. - auto idx_top_interface_first = -1; - // Index of the first top contact interface layer intersecting the current intermediate layer. - auto idx_top_base_interface_first = -1; - auto num_intermediate = int(intermediate_layers.size()); - for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { - SupportGeneratorLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; - Polygons polygons_top_contact_projected_interface; - Polygons polygons_top_contact_projected_base; - Polygons polygons_bottom_contact_projected_interface; - Polygons polygons_bottom_contact_projected_base; - if (num_interface_layers_top > 0) { - // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; - coordf_t top_inteface_z = std::numeric_limits::max(); - if (num_base_interface_layers_top > 0) - // Some top base interface layers will be generated. - top_inteface_z = num_interface_layers_only_top == 0 ? - // Only base interface layers to generate. - - std::numeric_limits::max() : - intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; - // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; - //FIXME maybe this adds one interface layer in excess? - if (top_contact_layer.bottom_z - EPSILON > top_z) - break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, - // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. - // For grid supports, merging of support regions will be performed by the projection into grid. - snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); - } - } - if (num_interface_layers_bottom > 0) { - // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; - coordf_t bottom_interface_z = - std::numeric_limits::max(); - if (num_base_interface_layers_bottom > 0) - // Some bottom base interface layers will be generated. - bottom_interface_z = num_interface_layers_only_bottom == 0 ? - // Only base interface layers to generate. - std::numeric_limits::max() : - intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; - // Move idx_bottom_contact_first up until touching bottom_z. - idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { - const SupportGeneratorLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; - if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) - break; - polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); - } - } - auto resolve_same_layer = [](SupportGeneratorLayersPtr &layers, int &idx, coordf_t print_z) -> SupportGeneratorLayer* { - if (! layers.empty()) { - idx = idx_higher_or_equal(layers, idx, [print_z](const SupportGeneratorLayer *layer) { return layer->print_z > print_z - EPSILON; }); - if (idx < int(layers.size()) && layers[idx]->print_z < print_z + EPSILON) - return layers[idx]; - } - return nullptr; - }; - SupportGeneratorLayer *top_interface_layer = resolve_same_layer(top_interface_layers, idx_top_interface_first, intermediate_layer.print_z); - SupportGeneratorLayer *top_base_interface_layer = resolve_same_layer(top_base_interface_layers, idx_top_base_interface_first, intermediate_layer.print_z); - SupportGeneratorLayer *interface_layer = nullptr; - if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty() || - (top_interface_layer && ! top_interface_layer->polygons.empty())) { - interface_layer = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), top_interface_layer, nullptr, - polygons_top_contact_projected_interface.empty() ? sltBottomInterface : sltTopInterface); - interface_layers[idx_intermediate_layer] = interface_layer; - } - if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty() || - (top_base_interface_layer && ! top_base_interface_layer->polygons.empty())) - base_interface_layers[idx_intermediate_layer] = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), top_base_interface_layer, - interface_layer ? &interface_layer->polygons : nullptr, sltBase); - } - }); - - // Compress contact_out, remove the nullptr items. - // The parallel_for above may not have merged all the interface and base_interface layers - // generated by the Organic supports code, do it here. - auto merge_remove_empty = [](SupportGeneratorLayersPtr& in1, SupportGeneratorLayersPtr& in2) { - auto remove_empty = [](SupportGeneratorLayersPtr& vec) { - vec.erase( - std::remove_if(vec.begin(), vec.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr || ptr->polygons.empty(); }), - vec.end()); - }; - remove_empty(in1); - remove_empty(in2); - if (in2.empty()) - return std::move(in1); - else if (in1.empty()) - return std::move(in2); - else { - SupportGeneratorLayersPtr out(in1.size() + in2.size(), nullptr); - std::merge(in1.begin(), in1.end(), in2.begin(), in2.end(), out.begin(), [](auto* l, auto* r) { return l->print_z < r->print_z; }); - return out; - } - }; - interface_layers = merge_remove_empty(interface_layers, top_interface_layers); - base_interface_layers = merge_remove_empty(base_interface_layers, top_base_interface_layers); - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; - } - - return base_and_interface_layers; -} - -static inline void fill_expolygon_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygon &&expolygon, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - Surface surface(stInternal, std::move(expolygon)); - Polylines polylines; - try { - polylines = filler->fill_surface(&surface, fill_params); - } catch (InfillFailedException &) { - } - extrusion_entities_append_paths( - dst, - std::move(polylines), - role, - flow.mm3_per_mm(), flow.width(), flow.height()); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - for (ExPolygon &expoly : expolygons) - fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow) -{ - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, role, flow); -} - -static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) -{ - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t i = 0; i <= expoly.holes.size(); ++ i) { - Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); - pl.points.emplace_back(pl.points.front()); - if (i > 0) - // It is a hole, reverse it. - pl.reverse(); - // so that all contours are CCW oriented. - pl.clip_end(clip_length); - polylines.emplace_back(std::move(pl)); - } - return polylines; -} - -static inline void tree_supports_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - const Flow &flow) -{ - // Offset expolygon inside, returns number of expolygons collected (0 or 1). - // Vertices of output paths are marked with Z = source contour index of the expoly. - // Vertices at the intersection of source contours are marked with Z = -1. - auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int - { - assert(delta > 0); - auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) { - dst.reserve(next_highest_power_of_2(dst.size() + src.size())); - for (const ClipperLib::Path &contour : src) { - ClipperLib_Z::Path tmp; - tmp.reserve(contour.size()); - for (const Point &p : contour) - tmp.emplace_back(p.x(), p.y(), contour_idx); - dst.emplace_back(std::move(tmp)); - } - }; - - // 1) Offset the outer contour. - ClipperLib_Z::Paths contours; - { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(delta * 0.005); - co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths contours_raw; - co.Execute(contours_raw, - delta); - if (contours_raw.empty()) - // No need to try to offset the holes. - return 0; - append_paths_with_z(contours_raw, 0, contours); - } - - if (expoly.holes.empty()) { - // No need to subtract holes from the offsetted expolygon, we are done. - append(out, std::move(contours)); - } else { - // 2) Offset the holes one by one, collect the offsetted holes. - ClipperLib_Z::Paths holes; - { - for (const Polygon &hole : expoly.holes) { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(delta * 0.005); - co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out2; - // Execute reorients the contours so that the outer most contour has a positive area. Thus the output - // contours will be CCW oriented even though the input paths are CW oriented. - // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. - co.Execute(out2, delta); - append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes); - } - } - - // 3) Subtract holes from the contours. - if (holes.empty()) { - // No hole remaining after an offset. Just copy the outer contour. - append(out, std::move(contours)); - } else { - // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. - // Subtract the offsetted holes from the offsetted contours. - ClipperLib_Z::Clipper clipper; - clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { - //pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); - // Just mark the intersection. - pt.z() = -1; - }); - clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true); - clipper.AddPaths(holes, ClipperLib_Z::ptClip, true); - ClipperLib_Z::Paths output; - clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - if (! output.empty()) { - append(out, std::move(output)); - } else { - // The offsetted holes have eaten up the offsetted outer contour. - return 0; - } - } - } - - return 1; - }; - - const double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - const double clip_length = spacing * 0.15; - const double anchor_length = spacing * 6.; - ClipperLib_Z::Paths anchor_candidates; - for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { - std::unique_ptr eec; - double area = expoly.area(); - if (area > sqr(scaled(5.))) { - eec = std::make_unique(); - // Don't reoder internal / external loops of the same island, always start with the internal loop. - eec->no_sort = true; - // Make the tree branch stable by adding another perimeter. - ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); - if (level2.size() == 1) { - Polylines polylines; - extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); - expoly = level2.front(); - } - } - - // Try to produce one more perimeter to place the seam anchor. - // First genrate a 2nd perimeter loop as a source for anchor candidates. - // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. - anchor_candidates.clear(); - shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); - // Orient all contours CW. - for (auto &path : anchor_candidates) - if (ClipperLib_Z::Area(path) > 0) - std::reverse(path.begin(), path.end()); - - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t idx_loop = 0; idx_loop < expoly.num_contours(); ++ idx_loop) { - // Open the loop with a seam. - const Polygon &loop = expoly.contour_or_hole(idx_loop); - Polyline pl(loop.points); - // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. - if (idx_loop == 0) - // It is an outer contour. - pl.reverse(); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - if (pl.size() < 2) - continue; - // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. - ClipperLib_Z::Path *closest_contour = nullptr; - Vec2d closest_point; - int closest_point_idx = -1; - double closest_point_t; - double d2min = std::numeric_limits::max(); - Vec2d seam_pt = pl.back().cast(); - for (ClipperLib_Z::Path &path : anchor_candidates) - for (int i = 0; i < path.size(); ++ i) { - int j = next_idx_modulo(i, path); - if (path[i].z() == idx_loop || path[j].z() == idx_loop) { - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - Vec2d w = seam_pt - pi; - auto l2 = v.squaredNorm(); - auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); - if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { - // Closest point. - Vec2d fp = pi + v * t; - double d2 = (fp - seam_pt).squaredNorm(); - if (d2 < d2min) { - d2min = d2; - closest_contour = &path; - closest_point = fp; - closest_point_idx = i; - closest_point_t = t; - } - } - } - } - if (d2min < sqr(flow.scaled_width() * 3.)) { - // Try to cut an anchor from the closest_contour. - // Both closest_contour and pl are CW oriented. - pl.points.emplace_back(closest_point.cast()); - const ClipperLib_Z::Path &path = *closest_contour; - double remaining_length = anchor_length - (seam_pt - closest_point).norm(); - int i = closest_point_idx; - int j = next_idx_modulo(i, *closest_contour); - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - double l = v.norm(); - if (remaining_length < (1. - closest_point_t) * l) { - // Just trim the current line. - pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); - } else { - // Take the rest of the current line, continue with the other lines. - pl.points.emplace_back(path[j].x(), path[j].y()); - pi = pj; - for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { - j = next_idx_modulo(i, path); - pj = Vec2d(path[j].x(), path[j].y()); - v = pj - pi; - l = v.norm(); - if (i == closest_point_idx) { - // Back at the first segment. Most likely this should not happen and we may end the anchor. - break; - } - if (remaining_length <= l) { - pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); - break; - } - pl.points.emplace_back(path[j].x(), path[j].y()); - remaining_length -= l; - } - } - } - // Start with the anchor. - pl.reverse(); - polylines.emplace_back(std::move(pl)); - } - - ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; - extrusion_entities_append_paths(out, std::move(polylines), erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); - if (eec) { - std::reverse(eec->entities.begin(), eec->entities.end()); - dst.emplace_back(eec.release()); - } - } -} - -void fill_expolygons_with_sheath_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow, - bool with_sheath, - bool no_sort) -{ - if (polygons.empty()) - return; - - if (with_sheath) { - if (density == 0) { - tree_supports_generate_paths(dst, polygons, flow); - return; - } - } else { - fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); - return; - } - - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - - double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - double clip_length = spacing * 0.15; - - for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { - // Don't reorder the skirt and its infills. - std::unique_ptr eec; - if (no_sort) { - eec = std::make_unique(); - eec->no_sort = true; - } - ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t i = 0; i <= expoly.holes.size(); ++ i) { - Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - polylines.emplace_back(std::move(pl)); - } - extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); - // Fill in the rest. - fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, role, flow); - if (no_sort && ! eec->empty()) - dst.emplace_back(eec.release()); - } -} - -// Support layers, partially processed. -struct SupportGeneratorLayerExtruded -{ - SupportGeneratorLayerExtruded& operator=(SupportGeneratorLayerExtruded &&rhs) { - this->layer = rhs.layer; - this->extrusions = std::move(rhs.extrusions); - m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); - rhs.layer = nullptr; - return *this; - } - - bool empty() const { - return layer == nullptr || layer->polygons.empty(); - } - - void set_polygons_to_extrude(Polygons &&polygons) { - if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = std::make_unique(std::move(polygons)); - else - *m_polygons_to_extrude = std::move(polygons); - } - Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - - bool could_merge(const SupportGeneratorLayerExtruded &other) const { - return ! this->empty() && ! other.empty() && - std::abs(this->layer->height - other.layer->height) < EPSILON && - this->layer->bridging == other.layer->bridging; - } - - // Merge regions, perform boolean union over the merged polygons. - void merge(SupportGeneratorLayerExtruded &&other) { - assert(this->could_merge(other)); - // 1) Merge the rest polygons to extrude, if there are any. - if (other.m_polygons_to_extrude != nullptr) { - if (m_polygons_to_extrude == nullptr) { - // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(this->extrusions.empty()); - m_polygons_to_extrude = std::make_unique(this->layer->polygons); - } - Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - other.m_polygons_to_extrude.reset(); - } else if (m_polygons_to_extrude != nullptr) { - assert(other.m_polygons_to_extrude == nullptr); - // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(other.extrusions.empty()); - Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - } - // 2) Merge the extrusions. - this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); - other.extrusions.clear(); - // 3) Merge the infill polygons. - Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); - this->layer->polygons = union_safety_offset(this->layer->polygons); - other.layer->polygons.clear(); - } - - void polygons_append(Polygons &dst) const { - if (layer != NULL && ! layer->polygons.empty()) - Slic3r::polygons_append(dst, layer->polygons); - } - - // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - SupportGeneratorLayer *layer { nullptr }; - // Collect extrusions. They will be exported sorted by the bottom height. - ExtrusionEntitiesPtr extrusions; - -private: - // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. - // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - std::unique_ptr m_polygons_to_extrude; -}; - -typedef std::vector SupportGeneratorLayerExtrudedPtrs; - -struct LoopInterfaceProcessor -{ - LoopInterfaceProcessor(coordf_t circle_r) : - n_contact_loops(0), - circle_radius(circle_r), - circle_distance(circle_r * 3.) - { - // Shape of the top contact area. - circle.points.reserve(6); - for (size_t i = 0; i < 6; ++ i) { - double angle = double(i) * M_PI / 3.; - circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); - } - } - - // Generate loop contacts at the top_contact_layer, - // trim the top_contact_layer->polygons with the areas covered by the loops. - void generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; - - int n_contact_loops; - coordf_t circle_radius; - coordf_t circle_distance; - Polygon circle; -}; - -void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const -{ - if (n_contact_loops == 0 || top_contact_layer.empty()) - return; - - Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); - - Polygons overhang_polygons; - if (top_contact_layer.layer->overhang_polygons != nullptr) - overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); - - // Generate the outermost loop. - // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); - - // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. - coord_t circle_grid_resolution = 1; - coord_t circle_grid_powerof2 = 0; - { - // epsilon to account for rounding errors - coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); - while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { - circle_grid_resolution <<= 1; - ++ circle_grid_powerof2; - } - } - - struct PointAccessor { - const Point* operator()(const Point &pt) const { return &pt; } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - - Polygons loops0; - { - // find centerline of the external loop of the contours - // Only consider the loops facing the overhang. - Polygons external_loops; - // Holes in the external loops. - Polygons circles; - Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); - for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { - // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. - ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); - Points circle_centers; - Point center_last; - // For each contour of the expolygon, start with the outer contour, continue with the holes. - for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { - Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; - const Point *seg_current_pt = nullptr; - coordf_t seg_current_t = 0.; - if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { - // The contour is below the overhang at least to some extent. - //FIXME ideally one would place the circles below the overhang only. - // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. - if (circle_centers.empty()) { - // Place the first circle. - seg_current_pt = &contour.points.front(); - seg_current_t = 0.; - center_last = *seg_current_pt; - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { - // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? - const Point &p1 = *(it-1); - const Point &p2 = *it; - // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. - const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); - const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); - coordf_t a = v_seg.squaredNorm(); - coordf_t b = 2. * v_seg.dot(v_cntr); - coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; - coordf_t disc = b * b - 4. * a * c; - if (disc > 0.) { - // The circle intersects a ray. Avoid the parts of the segment inside the circle. - coordf_t t1 = (-b - sqrt(disc)) / (2. * a); - coordf_t t2 = (-b + sqrt(disc)) / (2. * a); - coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; - // Take the lowest t in , excluding . - coordf_t t; - if (t0 <= t1) - t = t0; - else if (t2 <= 1.) - t = t2; - else { - // Try the following segment. - seg_current_pt = nullptr; - continue; - } - seg_current_pt = &p1; - seg_current_t = t; - center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); - // It has been verified that the new point is far enough from center_last. - // Ensure, that it is far enough from all the centers. - std::pair circle_closest = circle_centers_lookup.find(center_last); - if (circle_closest.first != nullptr) { - -- it; - continue; - } - } else { - // All of the segment is outside the circle. Take the first point. - seg_current_pt = &p1; - seg_current_t = 0.; - center_last = p1; - } - // Place the first circle. - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - external_loops.push_back(std::move(contour)); - for (const Point ¢er : circle_centers) { - circles.push_back(circle); - circles.back().translate(center); - } - } - } - } - // Apply a pattern to the external loops. - loops0 = diff(external_loops, circles); - } - - Polylines loop_lines; - { - // make more loops - Polygons loop_polygons = loops0; - for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - opening( - loops0, - i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), - 0.5f * flow.scaled_spacing())); - // Clip such loops to the side oriented towards the object. - // Collect split points, so they will be recognized after the clipping. - // At the split points the clipped pieces will be stitched back together. - loop_lines.reserve(loop_polygons.size()); - std::unordered_map map_split_points; - for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { - assert(map_split_points.find(it->first_point()) == map_split_points.end()); - map_split_points[it->first_point()] = -1; - loop_lines.push_back(it->split_at_first_point()); - } - loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); - // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. - // Try to connect them. - for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { - Polyline &polyline = loop_lines[i_line]; - auto it = map_split_points.find(polyline.first_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - continue; - } - it = map_split_points.find(polyline.last_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - } - } - // Remove empty lines. - remove_degenerate(loop_lines); - } - - // add the contact infill area to the interface area - // note that growing loops by $circle_radius ensures no tiny - // extrusions are left inside the circles; however it creates - // a very large gap between loops and contact_infill_polygons, so maybe another - // solution should be found to achieve both goals - // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for - // "modulate by layer thickness". - top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); - - // Transform loops into ExtrusionPath objects. - extrusion_entities_append_paths( - top_contact_layer.extrusions, - std::move(loop_lines), - erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); -} - -#ifdef SLIC3R_DEBUG -static std::string dbg_index_to_color(int idx) -{ - if (idx < 0) - return "yellow"; - idx = idx % 3; - switch (idx) { - case 0: return "red"; - case 1: return "green"; - default: return "blue"; - } -} -#endif /* SLIC3R_DEBUG */ - -// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore -// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). -// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. -// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, -// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers -// to stick too firmly to the object. -// -// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer -// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. -void modulate_extrusion_by_overlapping_layers( - // Extrusions generated for this_layer. - ExtrusionEntitiesPtr &extrusions_in_out, - const SupportGeneratorLayer &this_layer, - // Multiple layers overlapping with this_layer, sorted bottom up. - const SupportGeneratorLayersPtr &overlapping_layers) -{ - size_t n_overlapping_layers = overlapping_layers.size(); - if (n_overlapping_layers == 0 || extrusions_in_out.empty()) - // The extrusions do not overlap with any other extrusion. - return; - - // Get the initial extrusion parameters. - ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); - assert(extrusion_path_template != nullptr); - ExtrusionRole extrusion_role = extrusion_path_template->role(); - float extrusion_width = extrusion_path_template->width; - - struct ExtrusionPathFragment - { - ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; - ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; - - Polylines polylines; - double mm3_per_mm; - float width; - float height; - }; - - // Split the extrusions by the overlapping layers, reduce their extrusion rate. - // The last path_fragment is from this_layer. - std::vector path_fragments( - n_overlapping_layers + 1, - ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); - // Don't use it, it will be released. - extrusion_path_template = nullptr; - -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; - BoundingBox bbox; - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - bbox.merge(get_extents(overlapping_layer.polygons)); - } - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - assert(path != nullptr); - bbox.merge(get_extents(path->polyline)); - } - SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); - const float transparency = 0.5f; - // Filled polygons for the overlapping regions. - svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); - } - // Contours of the overlapping regions. - svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); - } - // Fill extrusion, the source. - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - std::string color_name; - switch ((it - extrusions_in_out.begin()) % 9) { - case 0: color_name = "magenta"; break; - case 1: color_name = "deepskyblue"; break; - case 2: color_name = "coral"; break; - case 3: color_name = "goldenrod"; break; - case 4: color_name = "orange"; break; - case 5: color_name = "olivedrab"; break; - case 6: color_name = "blueviolet"; break; - case 7: color_name = "brown"; break; - default: color_name = "orchid"; break; - } - svg.draw(path->polyline, color_name, scale_(0.2)); - } -#endif /* SLIC3R_DEBUG */ - - // End points of the original paths. - std::vector> path_ends; - // Collect the paths of this_layer. - { - Polylines &polylines = path_fragments.back().polylines; - for (ExtrusionEntity *ee : extrusions_in_out) { - ExtrusionPath *path = dynamic_cast(ee); - assert(path != nullptr); - polylines.emplace_back(Polyline(std::move(path->polyline))); - path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); - } - } - // Destroy the original extrusion paths, their polylines were moved to path_fragments already. - // This will be the destination for the new paths. - extrusions_in_out.clear(); - - // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. - // Trim by the highest overlapping layer first. - for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; - Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); - frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); - path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); - // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). - assert(this_layer.print_z > overlapping_layer.print_z); - frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); -#ifdef SLIC3R_DEBUG - svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); -#endif /* SLIC3R_DEBUG */ - } - -#ifdef SLIC3R_DEBUG - svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); - svg.Close(); -#endif /* SLIC3R_DEBUG */ - - // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. - // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. - // Map of fragment start/end points to a pair of - // Because a non-exact matching is used for the end points, a multi-map is used. - // As the clipper library may reverse the order of some clipped paths, store both ends into the map. - struct ExtrusionPathFragmentEnd - { - ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : - layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} - size_t layer_idx; - size_t polyline_idx; - bool is_start; - }; - class ExtrusionPathFragmentEndPointAccessor { - public: - ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} - // Return an end point of a fragment, or nullptr if the fragment has been consumed already. - const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { - const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; - return polyline.points.empty() ? nullptr : - (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); - } - private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { - return *this; - } - - const std::vector &m_path_fragments; - }; - const coord_t search_radius = 7; - ClosestPointInRadiusLookup map_fragment_starts( - search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { - const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; - for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { - // Map a starting point of a polyline to a pair of - if (polylines[i_polyline].points.size() >= 2) { - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); - } - } - } - - // For each source path: - for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { - const Point &pt_start = path_ends[i_path].first; - const Point &pt_end = path_ends[i_path].second; - Point pt_current = pt_start; - // Find a chain of fragments with the original / reduced print height. - ExtrusionMultiPath multipath; - for (;;) { - // Find a closest end point to pt_current. - std::pair end_and_dist2 = map_fragment_starts.find(pt_current); - // There may be a bug in Clipper flipping the order of two last points in a fragment? - // assert(end_and_dist2.first != nullptr); - assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); - if (end_and_dist2.first == nullptr) { - // New fragment connecting to pt_current was not found. - // Verify that the last point found is close to the original end point of the unfragmented path. - //const double d2 = (pt_end - pt_current).cast.squaredNorm(); - //assert(d2 < coordf_t(search_radius * search_radius)); - // End of the path. - break; - } - const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; - // Fragment to consume. - ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; - Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; - // Path to append the fragment to. - ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); - if (path != nullptr) { - // Verify whether the path is compatible with the current fragment. - assert(this_layer.layer_type == sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); - if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { - path = nullptr; - } - // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. - } - if (path == nullptr) { - // Allocate a new path. - multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); - path = &multipath.paths.back(); - } - // The Clipper library may flip the order of the clipped polylines arbitrarily. - // Reverse the source polyline, if connecting to the end. - if (! fragment_end_min.is_start) - frag_polyline.reverse(); - // Enforce exact overlap of the end points of successive fragments. - assert(frag_polyline.points.front() == pt_current); - frag_polyline.points.front() = pt_current; - // Don't repeat the first point. - if (! path->polyline.points.empty()) - path->polyline.points.pop_back(); - // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. - path->polyline.append(std::move(frag_polyline)); - frag_polyline.points.clear(); - pt_current = path->polyline.points.back(); - if (pt_current == pt_end) { - // End of the path. - break; - } - } - if (!multipath.paths.empty()) { - if (multipath.paths.size() == 1) { - // This path was not fragmented. - extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); - } else { - // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed - // during the chaining of extrusions_in_out. - extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); - } - } - } - // If there are any non-consumed fragments, add them separately. - //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. - for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) - extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); -} - -SupportGeneratorLayersPtr generate_support_layers( - PrintObject &object, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) -{ - // Install support layers into the object. - // A support layer installed on a PrintObject has a unique print_z. - SupportGeneratorLayersPtr layers_sorted; - layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); - layers_append(layers_sorted, raft_layers); - layers_append(layers_sorted, bottom_contacts); - layers_append(layers_sorted, top_contacts); - layers_append(layers_sorted, intermediate_layers); - layers_append(layers_sorted, interface_layers); - layers_append(layers_sorted, base_interface_layers); - // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - int layer_id = 0; - int layer_id_interface = 0; - assert(object.support_layers().empty()); - for (size_t i = 0; i < layers_sorted.size();) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - size_t j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; - // Assign an average print_z to the set of layers with nearly equal print_z. - coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); - coordf_t height_min = layers_sorted[i]->height; - bool empty = true; - // For snug supports, layers where the direction of the support interface shall change are accounted for. - size_t num_interfaces = 0; - size_t num_top_contacts = 0; - double top_contact_bottom_z = 0; - for (size_t u = i; u < j; ++u) { - SupportGeneratorLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) { - empty = false; - num_interfaces += one_of(layer.layer_type, support_types_interface); - if (layer.layer_type == SupporLayerType::sltTopContact) { - ++ num_top_contacts; - assert(num_top_contacts <= 1); - // All top contact layers sharing this print_z shall also share bottom_z. - //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); - top_contact_bottom_z = layer.bottom_z; - } - } - layer.print_z = zavg; - height_min = std::min(height_min, layer.height); - } - if (! empty) { - // Here the upper_layer and lower_layer pointers are left to null at the support layers, - // as they are never used. These pointers are candidates for removal. - bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; - size_t this_layer_id_interface = layer_id_interface; - if (this_layer_contacts_only) { - // Find a supporting layer for its interface ID. - for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) - if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { - // other_layer supports this top contact layer. Assign a different support interface direction to this layer - // from the layer that supports it. - this_layer_id_interface = other_layer.interface_id() + 1; - } - } - object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); - if (num_interfaces && ! this_layer_contacts_only) - ++ layer_id_interface; - } - i = j; - } - return layers_sorted; -} - -void generate_support_toolpaths( - PrintObject &object, - SupportLayerPtrs &support_layers, - const PrintObjectConfig &config, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) -{ - // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); - loop_interface_processor.n_contact_loops = config.support_interface_loop_pattern ? 1 : 0; - - std::vector angles { support_params.base_angle }; - if (config.support_base_pattern == smpRectilinearGrid) - angles.push_back(support_params.interface_angle); - - BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); - -// const coordf_t link_max_length_factor = 3.; - const coordf_t link_max_length_factor = 0.; - - float raft_angle_1st_layer = 0.f; - float raft_angle_base = 0.f; - float raft_angle_interface = 0.f; - if (slicing_params.base_raft_layers > 1) { - // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = support_params.interface_angle; - raft_angle_base = support_params.base_angle; - raft_angle_interface = support_params.interface_angle; - } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { - // 1st layer, interface & contact layers available. - raft_angle_1st_layer = support_params.base_angle; - if (config.enable_support.value || config.enforce_support_layers) // has_support() - // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. - raft_angle_1st_layer += 0.7854f; - raft_angle_interface = support_params.interface_angle; - } else if (slicing_params.interface_raft_layers == 1) { - // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 1); - assert(slicing_params.raft_layers() == 1 && raft_layers.size() == 0); - } else { - // No raft. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 0); - assert(slicing_params.raft_layers() == 0 && raft_layers.size() == 0); - } - - // Insert the raft base layers. - size_t n_raft_layers = size_t(std::max(0, int(slicing_params.raft_layers()) - 1)); - tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), - [&support_layers, &raft_layers, &config, &support_params, &slicing_params, - &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - assert(support_layer_id < raft_layers.size()); - SupportLayer &support_layer = *support_layers[support_layer_id]; - assert(support_layer.support_fills.entities.empty()); - SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; - - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(support_params.interface_fill_pattern)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - - // Print the support base below the support columns, or the support base for the support columns plus the contacts. - if (support_layer_id > 0) { - const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? - raft_layer.polygons : - //FIXME misusing contact_polygons for support columns. - ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); - if (! to_infill_polygons.empty()) { - assert(! raft_layer.bridging); - Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); - Fill * filler = filler_support.get(); - filler->angle = raft_angle_base; - filler->spacing = support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - to_infill_polygons, - // Filler and its parameters - filler, float(support_params.support_density), - // Extrusion parameters - erSupportMaterial, flow, - support_params.with_sheath, false); - } - } - - Fill *filler = filler_interface.get(); - Flow flow = support_params.first_layer_flow; - float density = 0.f; - if (support_layer_id == 0) { - // Base flange. - filler->angle = raft_angle_1st_layer; - filler->spacing = support_params.first_layer_flow.spacing(); - density = float(config.raft_first_layer_density.value * 0.01); - } else if (support_layer_id >= slicing_params.base_raft_layers) { - filler->angle = raft_angle_interface; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - filler->spacing = support_params.support_material_flow.spacing(); - assert(! raft_layer.bridging); - flow = Flow(float(support_params.support_material_interface_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); - density = float(support_params.interface_density); - } else - continue; - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - raft_layer.polygons, - // Filler and its parameters - filler, density, - // Extrusion parameters - (support_layer_id < slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, - // sheath at first layer - support_layer_id == 0, support_layer_id == 0); - } - }); - - struct LayerCacheItem { - LayerCacheItem(SupportGeneratorLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} - SupportGeneratorLayerExtruded *layer_extruded; - std::vector overlapping; - }; - struct LayerCache { - SupportGeneratorLayerExtruded bottom_contact_layer; - SupportGeneratorLayerExtruded top_contact_layer; - SupportGeneratorLayerExtruded base_layer; - SupportGeneratorLayerExtruded interface_layer; - SupportGeneratorLayerExtruded base_interface_layer; - boost::container::static_vector nonempty; - - void add_nonempty_and_sort() { - for (SupportGeneratorLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) - if (! item->empty()) - this->nonempty.emplace_back(item); - // Sort the layers with the same print_z coordinate by their heights, thickest first. - std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); - } - }; - std::vector layer_caches(support_layers.size()); - - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&object, &config, &support_params, &slicing_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - &bbox_object, &angles, link_max_length_factor] - (const tbb::blocked_range& range) { - // Indices of the 1st layer in their respective container at the support layer height. - size_t idx_layer_bottom_contact = size_t(-1); - size_t idx_layer_top_contact = size_t(-1); - size_t idx_layer_intermediate = size_t(-1); - size_t idx_layer_interface = size_t(-1); - size_t idx_layer_base_interface = size_t(-1); - // QDS - const auto fill_type_first_layer = ipConcentric; - auto filler_interface = std::unique_ptr(Fill::new_from_type(support_params.contact_fill_pattern)); - // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); - // Pointer to the 1st layer interface filler. - auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); - // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : - Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); - auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - if (filler_first_layer_ptr) - filler_first_layer_ptr->set_bounding_box(bbox_object); - if (filler_base_interface) - filler_base_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = is_tree(config.support_type.value) ? - (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : - 0; - - // Find polygons with the same print_z. - SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; - SupportGeneratorLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; - SupportGeneratorLayerExtruded &base_layer = layer_cache.base_layer; - SupportGeneratorLayerExtruded &interface_layer = layer_cache.interface_layer; - SupportGeneratorLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; - // Increment the layer indices to find a layer at support_layer.print_z. - { - auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; - idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); - idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); - idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); - idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); - idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); - } - // Copy polygons from the layers. - if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) - bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; - if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) - top_contact_layer.layer = top_contacts[idx_layer_top_contact]; - if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) - interface_layer.layer = interface_layers[idx_layer_interface]; - if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) - base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; - if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) - base_layer.layer = intermediate_layers[idx_layer_intermediate]; - - if (config.support_interface_top_layers == 0) { - // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. - if (support_params.can_merge_support_regions) { - if (base_layer.could_merge(top_contact_layer)) - base_layer.merge(std::move(top_contact_layer)); - else if (base_layer.empty()) - base_layer = std::move(top_contact_layer); - } - } else { - loop_interface_processor.generate(top_contact_layer, support_params.support_material_interface_flow); - // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. - // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used - // to trim other layers. - if (top_contact_layer.could_merge(interface_layer)) - top_contact_layer.merge(std::move(interface_layer)); - } - if ((config.support_interface_top_layers == 0 || config.support_interface_bottom_layers == 0) && support_params.can_merge_support_regions) { - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) - base_layer = std::move(bottom_contact_layer); - } else if (bottom_contact_layer.could_merge(top_contact_layer)) - top_contact_layer.merge(std::move(bottom_contact_layer)); - else if (bottom_contact_layer.could_merge(interface_layer)) - bottom_contact_layer.merge(std::move(interface_layer)); - -#if 0 - if ( ! interface_layer.empty() && ! base_layer.empty()) { - // turn base support into interface when it's contained in our holes - // (this way we get wider interface anchoring) - //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes - // inside interface layers, but the code below fills just too much, see GH #4570 - Polygons islands = top_level_islands(interface_layer.layer->polygons); - polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); - base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); - } -#endif - - // Calculate top interface angle - float angle_of_biggest_bridge = -1.f; - do - { - // Currently only works when thick_bridges is off - if (config.thick_bridges) - break; - - coordf_t object_layer_bottom_z = support_layer.print_z + slicing_params.gap_support_object; - const Layer* object_layer = object.get_layer_at_bottomz(object_layer_bottom_z, 10.0 * EPSILON); - if (object_layer == nullptr) - break; - - if (object_layer != nullptr) { - float biggest_bridge_area = 0.f; - const Polygons& top_contact_polys = top_contact_layer.polygons_to_extrude(); - for (auto layerm : object_layer->regions()) { - for (auto bridge_surface : layerm->fill_surfaces.filter_by_type(stBottomBridge)) { - float bs_area = bridge_surface->area(); - if (bs_area <= biggest_bridge_area || bridge_surface->bridge_angle < 0.f) - continue; - - angle_of_biggest_bridge = bridge_surface->bridge_angle; - biggest_bridge_area = bs_area; - } - } - } - } while (0); - - auto calc_included_angle_degree = [](int degree_a, int degree_b) { - int iad = std::abs(degree_b - degree_a); - return std::min(iad, 180 - iad); - }; - - // Top and bottom contacts, interface layers. - for (size_t i = 0; i < 3; ++ i) { - SupportGeneratorLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); - if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) - continue; - bool interface_as_base = config.support_interface_top_layers.value == 0 || - (config.support_interface_bottom_layers == 0 && &layer_ex == &bottom_contact_layer); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - Flow interface_flow; - if (layer_ex.layer->bridging) - interface_flow = Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()); - else if (layer_ex.layer->bottom_z < EPSILON) { - interface_flow = support_params.first_layer_flow; - }else - interface_flow = (interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); - filler_interface->angle = interface_as_base ? - // If zero interface layers are configured, use the same angle as for the base layers. - angles[support_layer_id % angles.size()] : - // Use interface angle for the interface layers. - support_params.interface_angle + interface_angle_delta; - - // QDS - bool can_adjust_top_interface_angle = (config.support_interface_top_layers.value > 1 && &layer_ex == &top_contact_layer); - if (can_adjust_top_interface_angle && angle_of_biggest_bridge >= 0.f) { - int bridge_degree = (int)Geometry::rad2deg(angle_of_biggest_bridge); - int support_intf_degree = (int)Geometry::rad2deg(filler_interface->angle); - int max_included_degree = 0; - int step = 90; - for (int add_on_degree = 0; add_on_degree < 180; add_on_degree += step) { - int degree_to_try = support_intf_degree + add_on_degree; - int included_degree = calc_included_angle_degree(bridge_degree, degree_to_try); - if (included_degree > max_included_degree) { - max_included_degree = included_degree; - filler_interface->angle = Geometry::deg2rad((float)degree_to_try); - } - } - } - double density = interface_as_base ? support_params.support_density : support_params.interface_density; - filler_interface->spacing = interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); - filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); - // QDS support more interface patterns - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - if (config.support_interface_pattern == smipGrid) { - filler_interface->angle = Geometry::deg2rad(support_params.base_angle); - fill_params.dont_sort = true; - } - if (config.support_interface_pattern == smipRectilinearInterlaced) - filler_interface->layer_id = support_layer.interface_id(); - fill_expolygons_generate_paths( - // Destination - layer_ex.extrusions, - // Regions to fill - union_safety_offset_ex(layer_ex.polygons_to_extrude()), - // Filler and its parameters - filler_interface.get(), fill_params, - // Extrusion parameters - erSupportMaterialInterface, interface_flow); - } - - // Base interface layers under soluble interfaces - if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_base_interface.get(); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - assert(! base_interface_layer.layer->bridging); - Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = support_params.interface_angle + interface_angle_delta; - filler->spacing = support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); - fill_expolygons_generate_paths( - // Destination - base_interface_layer.extrusions, - //base_layer_interface.extrusions, - // Regions to fill - union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), - // Filler and its parameters - filler, float(support_params.interface_density), - // Extrusion parameters - erSupportMaterial, interface_flow); - } - - // Base support or flange. - if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_support.get(); - filler->angle = angles[support_layer_id % angles.size()]; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - assert(! base_layer.layer->bridging); - auto flow = support_params.support_material_flow.with_height(float(base_layer.layer->height)); - filler->spacing = support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); - float density = float(support_params.support_density); - bool sheath = support_params.with_sheath; - bool no_sort = false; - if (base_layer.layer->bottom_z < EPSILON) { - // Base flange (the 1st layer). - filler = filler_first_layer; - // QDS: the 1st layer use the same fill direction as other layers(in rectilinear) to avoid - // that 2nd layer detaches from the 1st layer. - //filler->angle = Geometry::deg2rad(float(m_object_config->support_angle.value + 90.)); - density = float(config.raft_first_layer_density.value * 0.01); - flow = support_params.first_layer_flow; - // use the proper spacing for first layer as we don't need to align - // its pattern to the other layers - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler->spacing = flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - sheath = true; - no_sort = true; - } - fill_expolygons_with_sheath_generate_paths( - // Destination - base_layer.extrusions, - // Regions to fill - base_layer.polygons_to_extrude(), - // Filler and its parameters - filler, density, - // Extrusion parameters - erSupportMaterial, flow, - sheath, no_sort); - - } - - // Merge base_interface_layers to base_layers to avoid unneccessary retractions - if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && - base_layer.could_merge(base_interface_layer)) - base_layer.merge(std::move(base_interface_layer)); - - layer_cache.add_nonempty_and_sort(); - - // Collect the support areas with this print_z into islands, as there is no need - // for retraction over these islands. - Polygons polys; - // Collect the extrusions, sorted by the bottom extrusion height. - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Collect islands to polys. - layer_cache_item.layer_extruded->polygons_append(polys); - // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" - // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces - // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially - // overlap in Z with another support layers, leading to over-extrusion. - // Mitigate the over-extrusion by modulating the extrusion rate over these regions. - // The print head will follow the same print_z, but the layer thickness will be reduced - // where it overlaps with another support layer. - //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? - // Collect overlapping top/bottom surfaces. - layer_cache_item.overlapping.reserve(20); - coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { - for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(layers[i]); - }; - add_overlapping(top_contacts, idx_layer_top_contact); - if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::sltBottomContact) { - // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. - add_overlapping(intermediate_layers, idx_layer_intermediate); - add_overlapping(interface_layers, idx_layer_interface); - add_overlapping(base_interface_layers, idx_layer_base_interface); - } - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - } - if (! polys.empty()) - expolygons_append(support_layer.support_islands, union_ex(polys)); - } // for each support_layer_id - }); - - // Now modulate the support layer height in parallel. - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&support_layers, &layer_caches] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - // For all extrusion types at this print_z, ordered by decreasing layer height: - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Trim the extrusion height from the bottom by the overlapping layers. - modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); - support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); - } - } - }); - -#ifndef NDEBUG - struct Test { - static bool verify_nonempty(const ExtrusionEntityCollection *collection) { - for (const ExtrusionEntity *ee : collection->entities) { - if (const ExtrusionPath *path = dynamic_cast(ee)) - assert(! path->empty()); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) - assert(! multipath->empty()); - else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { - assert(! eecol->empty()); - return verify_nonempty(eecol); - } else - assert(false); - } - return true; - } - }; - for (const SupportLayer *support_layer : support_layers) - assert(Test::verify_nonempty(&support_layer->support_fills)); -#endif // NDEBUG -} - /* void PrintObjectSupportMaterial::clip_by_pillars( const PrintObject &object, diff --git a/src/libslic3r/Support/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp index 583d089..3737b4f 100644 --- a/src/libslic3r/Support/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -19,14 +19,6 @@ inline double layer_z(const SlicingParameters& slicing_params, const size_t laye { return slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height; } -inline LayerIndex layer_idx_ceil(const SlicingParameters& slicing_params, const double z) -{ - return LayerIndex(ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); -} -inline LayerIndex layer_idx_floor(const SlicingParameters& slicing_params, const double z) -{ - return LayerIndex(floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); -} inline SupportGeneratorLayer& layer_initialize( SupportGeneratorLayer& layer_new, @@ -53,67 +45,6 @@ inline SupportGeneratorLayer& layer_allocate( return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); } -// Generate raft layers, also expand the 1st support layer -// in case there is no raft layer to improve support adhesion. -SupportGeneratorLayersPtr generate_raft_base( - const PrintObject &object, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers, - const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage); - -// returns sorted layers -SupportGeneratorLayersPtr generate_support_layers( - PrintObject &object, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers); - -// Turn some of the base layers into base interface layers. -// For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base -// extruder to improve adhesion of the soluble filament to the base. -std::pair generate_interface_layers( - const PrintObjectConfig& config, - const SupportParameters& support_params, - const SupportGeneratorLayersPtr& bottom_contacts, - const SupportGeneratorLayersPtr& top_contacts, - // Input / output, will be merged with output. Only provided for Organic supports. - SupportGeneratorLayersPtr& top_interface_layers, - SupportGeneratorLayersPtr& top_base_interface_layers, - SupportGeneratorLayersPtr& intermediate_layers, - SupportGeneratorLayerStorage& layer_storage); - -// Produce the support G-code. -// Used by both classic and tree supports. -void generate_support_toolpaths( - PrintObject &object, - SupportLayerPtrs &support_layers, - const PrintObjectConfig &config, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers); - -void fill_expolygons_with_sheath_generate_paths( - ExtrusionEntitiesPtr& dst, - const Polygons& polygons, - Fill* filler, - float density, - ExtrusionRole role, - const Flow& flow, - bool with_sheath, - bool no_sort); - void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers); void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer); diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 3e14b44..dd95f1a 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "../libslic3r.h" #include "../Flow.hpp" #include "../PrintConfig.hpp" @@ -10,6 +11,7 @@ namespace Slic3r { struct SupportParameters { + SupportParameters() = delete; SupportParameters(const PrintObject& object) { const PrintConfig& print_config = object.print()->config(); @@ -36,13 +38,19 @@ struct SupportParameters { this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2)); this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2)); } else { - this->num_top_base_interface_layers = 0; - this->num_bottom_base_interface_layers = 0; + // QDS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion + // Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if + // support_filament==0 + bool differnt_support_interface_filament = object_config.support_interface_filament != 0 && + object_config.support_interface_filament != object_config.support_filament; + this->num_top_base_interface_layers = differnt_support_interface_filament ? 1 : 0; + this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0; } } this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); + this->raft_interface_flow = support_material_interface_flow; // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. this->support_layer_height_min = scaled(0.01); @@ -89,6 +97,8 @@ struct SupportParameters { this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.)); this->interface_spacing = object_config.support_interface_spacing.value + this->support_material_interface_flow.spacing(); this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->interface_spacing); + double raft_interface_spacing = object_config.support_interface_spacing.value + this->raft_interface_flow.spacing(); + this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing); this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing(); this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing); if (object_config.support_interface_top_layers.value == 0) { @@ -98,11 +108,12 @@ struct SupportParameters { } SupportMaterialPattern support_pattern = object_config.support_base_pattern; - this->with_sheath = /*is_tree(object_config.support_type) &&*/ object_config.tree_support_wall_count > 0; + this->with_sheath = object_config.tree_support_wall_count > 0; this->base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; if (object_config.support_interface_pattern == smipGrid) this->contact_fill_pattern = ipGrid; else if (object_config.support_interface_pattern == smipRectilinearInterlaced) @@ -113,6 +124,67 @@ struct SupportParameters { object_config.support_interface_pattern == smipConcentric ? ipConcentric : (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + + this->raft_angle_1st_layer = 0.f; + this->raft_angle_base = 0.f; + this->raft_angle_interface = 0.f; + if (slicing_params.base_raft_layers > 1) { + assert(slicing_params.raft_layers() >= 4); + // There are all raft layer types (1st layer, base, interface & contact layers) available. + this->raft_angle_1st_layer = this->interface_angle; + this->raft_angle_base = this->base_angle; + this->raft_angle_interface = this->interface_angle; + if ((slicing_params.interface_raft_layers & 1) == 0) + // Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface. + this->raft_angle_interface += float(0.5 * M_PI); + } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { + assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3); + // 1st layer, interface & contact layers available. + this->raft_angle_1st_layer = this->base_angle; + this->raft_angle_interface = this->interface_angle + 0.5 * M_PI; + } else if (slicing_params.interface_raft_layers == 1) { + // Only the contact raft layer is non-empty, which will be printed as the 1st layer. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 1); + assert(slicing_params.raft_layers() == 1); + this->raft_angle_1st_layer = float(0.5 * M_PI); + this->raft_angle_interface = this->raft_angle_1st_layer; + } else { + // No raft. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 0); + assert(slicing_params.raft_layers() == 0); + } + + support_extrusion_width = object_config.support_line_width.value > 0 ? object_config.support_line_width : object_config.line_width; + // Check if set to zero, use default if so. + if (support_extrusion_width <= 0.0) { + const auto nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_interface_filament - 1); + support_extrusion_width = Flow::auto_extrusion_width(FlowRole::frSupportMaterial, (float) nozzle_diameter); + } + + independent_layer_height = print_config.independent_support_layer_height; + + // force double walls everywhere if wall count is larger than 1 + tree_branch_diameter_double_wall_area_scaled = object_config.tree_support_wall_count.value > 1 ? 0.1 : + object_config.tree_support_wall_count.value == 0 ? 0.25 * sqr(scaled(5.0)) * M_PI : + std::numeric_limits::max(); + + support_style = object_config.support_style; + if (support_style == smsDefault) { + if (is_tree(object_config.support_type)) { + // organic support doesn't work with variable layer heights (including adaptive layer height and height range modifier, see #4313) + if (!object.has_variable_layer_heights) { + BOOST_LOG_TRIVIAL(warning) << "tree support default to organic support"; + support_style = smsTreeOrganic; + } else { + BOOST_LOG_TRIVIAL(warning) << "tree support default to hybrid tree due to adaptive layer height"; + support_style = smsTreeHybrid; + } + } else { + support_style = smsGrid; + } + } } // Both top / bottom contacts and interfaces are soluble. bool soluble_interface; @@ -142,6 +214,8 @@ struct SupportParameters { Flow support_material_flow; Flow support_material_interface_flow; Flow support_material_bottom_interface_flow; + // Flow at raft inteface & contact layers. + Flow raft_interface_flow; coordf_t support_extrusion_width; // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? bool can_merge_support_regions; @@ -157,13 +231,29 @@ struct SupportParameters { coordf_t interface_spacing; coordf_t support_expansion=0; coordf_t interface_density; + // Density of the raft interface and contact layers. + coordf_t raft_interface_density; coordf_t support_spacing; coordf_t support_density; + SupportMaterialStyle support_style = smsDefault; InfillPattern base_fill_pattern; InfillPattern interface_fill_pattern; + // Pattern of the raft interface and contact layers. + InfillPattern raft_interface_fill_pattern; InfillPattern contact_fill_pattern; bool with_sheath; + // Branches of organic supports with area larger than this threshold will be extruded with double lines. + double tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(5.0)) * M_PI;; + + float raft_angle_1st_layer; + float raft_angle_base; + float raft_angle_interface; + + // Produce a raft interface angle for a given SupportLayer::interface_id() + float raft_interface_angle(size_t interface_id) const + { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } + bool independent_layer_height = false; const double thresh_big_overhang = Slic3r::sqr(scale_(10)); }; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index a499f47..fd01208 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1,28 +1,26 @@ #include #include +#include -#include "MinimumSpanningTree.hpp" -#include "TreeSupport.hpp" -#include "Print.hpp" -#include "Layer.hpp" +#include "format.hpp" +#include "ClipperUtils.hpp" #include "Fill/FillBase.hpp" -#include "Fill/FillConcentric.hpp" -#include "CurveAnalyzer.hpp" -#include "SVG.hpp" -#include "ShortestPath.hpp" #include "I18N.hpp" +#include "Layer.hpp" +#include "MinimumSpanningTree.hpp" +#include "Print.hpp" +#include "ShortestPath.hpp" +#include "SupportCommon.hpp" +#include "SVG.hpp" +#include "TreeSupportCommon.hpp" +#include "TreeSupport.hpp" +#include "TreeSupport3D.hpp" #include #include -#include "TreeModelVolumes.hpp" -#include "TreeSupport3D.hpp" -#include "SupportMaterial.hpp" -#include "Fill/FillBase.hpp" -#include "BuildVolume.hpp" -#include "ClipperUtils.hpp" -#include -#include #include +#include +#include #include #include @@ -46,7 +44,6 @@ namespace Slic3r { #define unscale_(val) ((val) * SCALING_FACTOR) -#define FIRST_LAYER_EXPANSION 1.2 extern void generate_tree_support_3D(PrintObject& print_object, TreeSupport* tree_support, std::function throw_on_cancel); @@ -603,54 +600,25 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p m_print_config = &m_object->print()->config(); m_raft_layers = slicing_params.base_raft_layers + slicing_params.interface_raft_layers; support_type = m_object_config->support_type; - support_style = m_object_config->support_style; - if (support_style == smsDefault) { - //1.9.5 - // organic support doesn't work with variable layer heights (including adaptive layer height and height range modifier, see #4313) - if (!m_object->has_variable_layer_heights) { - BOOST_LOG_TRIVIAL(warning) << "tree support default to hybrid tree due to adaptive layer height"; - support_style = smsTreeOrganic; - } - else { - BOOST_LOG_TRIVIAL(warning) << "Adaptive layer height is not supported for organic support, using hybrid tree support instead."; - support_style = smsTreeHybrid; - } - } - SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; - if (support_style == smsTreeHybrid && support_pattern == smpDefault) - support_pattern = smpRectilinear; - m_support_params.base_fill_pattern = - support_pattern == smpLightning ? ipLightning : - support_pattern == smpHoneycomb ? ipHoneycomb : - m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase; - m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - if (m_object_config->support_interface_pattern == smipGrid) - m_support_params.contact_fill_pattern = ipGrid; - else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - m_support_params.contact_fill_pattern = ipRectilinear; - else - m_support_params.contact_fill_pattern = (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_interface_pattern == smipConcentric ? - ipConcentric : - (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - m_support_params.support_extrusion_width = m_object_config->support_line_width.value > 0 ? m_object_config->support_line_width : m_object_config->line_width; - // Check if set to zero, use default if so. - if (m_support_params.support_extrusion_width <= 0.0) { - const auto nozzle_diameter = object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament - 1); - m_support_params.support_extrusion_width = Flow::auto_extrusion_width(FlowRole::frSupportMaterial, (float)nozzle_diameter); - } - is_slim = is_tree_slim(support_type, support_style); - is_strong = is_tree(support_type) && support_style == smsTreeStrong; - MAX_BRANCH_RADIUS = 10.0; - tree_support_branch_diameter_angle = 5.0;//is_slim ? 10.0 : 5.0; + SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; + if (m_support_params.support_style == smsTreeHybrid && support_pattern == smpDefault) + support_pattern = smpRectilinear; + + if(support_pattern == smpLightning) + m_support_params.base_fill_pattern = ipLightning; + diameter_angle_scale_factor = std::clamp(m_object_config->tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + is_slim = is_tree_slim(support_type, m_support_params.support_style); + is_strong = is_tree(support_type) && m_support_params.support_style == smsTreeStrong; + base_radius = std::max(MIN_BRANCH_RADIUS, m_object_config->tree_support_branch_diameter.value / 2); // by default tree support needs no infill, unless it's tree hybrid which contains normal nodes. with_infill = support_pattern != smpNone && support_pattern != smpDefault; m_machine_border.contour = get_bed_shape_with_excluded_area(*m_print_config); Vec3d plate_offset = m_object->print()->get_plate_origin(); // align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset) m_machine_border.translate(Point(scale_(plate_offset(0)), scale_(plate_offset(1))) - m_object->instances().front().shift); - m_support_params.independent_layer_height = m_print_config->independent_support_layer_height; + top_z_distance = m_object_config->support_top_z_distance.value; + if (top_z_distance > EPSILON) top_z_distance = std::max(top_z_distance, float(m_slicing_params.min_layer_height)); #if USE_TREESUPPRT3D m_support_params.independent_layer_height = false; // do not use independent layer height for 3D tree support #endif @@ -690,11 +658,13 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) static const double sharp_tail_max_support_height = 16.f; // a region is considered well supported if the number of layers below it exceeds this threshold const int thresh_layers_below = 10 / config.layer_height; - double obj_height = m_object->size().z(); // +1 makes the threshold inclusive double thresh_angle = config.support_threshold_angle.value > EPSILON ? config.support_threshold_angle.value + 1 : 30; thresh_angle = std::min(thresh_angle, 89.); // should be smaller than 90 const double threshold_rad = Geometry::deg2rad(thresh_angle); + // FIXME this is a fudge constant! + double support_tree_tip_diameter = 0.8; + auto enforcer_overhang_offset = scaled(support_tree_tip_diameter); // for small overhang removal struct OverhangCluster { @@ -781,7 +751,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) return cluster; }; - if (!is_tree(stype)) return; + if (!is_tree(stype)) return; max_cantilever_dist = 0; m_highest_overhang_layer = 0; @@ -852,7 +822,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) bool is_sharp_tail = false; // 1. nothing below // this is a sharp tail region if it's floating and non-ignorable - if (!overlaps(offset_ex(expoly, 0.5 * extrusion_width_scaled), lower_polys)) { + if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) { is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty(); } @@ -885,7 +855,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } dist_max = std::max(dist_max, dist_pt); } - if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base + if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base max_cantilever_dist = std::max(max_cantilever_dist, dist_max); layer->cantilevers.emplace_back(poly); BOOST_LOG_TRIVIAL(debug) << "found a cantilever cluster. layer_nr=" << layer_nr << dist_max; @@ -972,7 +942,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) layer->sharp_tails_height.push_back( accum_height); } - } + } } } @@ -990,7 +960,8 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) auto enforcers = m_object->slice_support_enforcers(); auto blockers = m_object->slice_support_blockers(); - m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); + m_vertical_enforcer_points.clear(); + m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers, &m_vertical_enforcer_points); m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); if (is_auto(stype) && config_remove_small_overhangs) { @@ -1024,7 +995,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) { layer1->lslices, {"min_layer_lslices","red",0.5} }, { m_object->get_layer(cluster.max_layer)->lslices, {"max_layer_lslices","yellow",0.5} }, { cluster.merged_poly,{"overhang", "blue", 0.5} }, - { cluster.is_cantilever? layer1->cantilevers: offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled), {cluster.is_cantilever ? "cantilever":"erode1","green",0.5}} }); + { cluster.is_cantilever? layer1->cantilevers: offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled), {cluster.is_cantilever ? "cantilever":"erode1","green",0.5}} }); #endif } } @@ -1040,6 +1011,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } int layers_with_overhangs = 0; + int layers_with_enforcers = 0; for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { if (m_object->print()->canceled()) break; @@ -1067,7 +1039,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } if (layer_nr < blockers.size()) { - // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, + // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. ExPolygons blocker = offset_ex(union_(blockers[layer_nr]), scale_(radius_sample_resolution)); layer->loverhangs = diff_ex(layer->loverhangs, blocker); @@ -1081,8 +1053,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } if (max_bridge_length > 0 && layer->loverhangs.size() > 0 && lower_layer) { - //1.9.5 - // do not break bridge as the interface will be poor + // do not break bridge as the interface will be poor, see #4318 bool break_bridge = false; m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &layer->loverhangs, max_bridge_length, break_bridge); } @@ -1090,22 +1061,18 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) int nDetected = layer->loverhangs.size(); // enforcers now follow same logic as normal support. See STUDIO-3692 if (layer_nr < enforcers.size() && lower_layer) { - float no_interface_offset = std::accumulate(layer->regions().begin(), layer->regions().end(), FLT_MAX, - [](float acc, const LayerRegion* layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); - Polygons lower_layer_polygons = (layer_nr == 0) ? Polygons() : to_polygons(lower_layer->lslices_extrudable); - Polygons& enforcer = enforcers[layer_nr]; - if (!enforcer.empty()) { - ExPolygons enforcer_polygons = diff_ex(intersection_ex(layer->lslices_extrudable, enforcer), - // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - append(layer->loverhangs, enforcer_polygons); + ExPolygons enforced_overhangs = intersection_ex(diff_ex(layer->lslices_extrudable, lower_layer->lslices_extrudable), enforcers[layer_nr]); + if (!enforced_overhangs.empty()) { + // FIXME this is a hack to make enforcers work on steep overhangs. See STUDIO-7538. + enforced_overhangs = diff_ex(offset_ex(enforced_overhangs, enforcer_overhang_offset), lower_layer->lslices_extrudable); + append(layer->loverhangs, enforced_overhangs); } } int nEnforced = layer->loverhangs.size(); // add sharp tail overhangs append(layer->loverhangs, sharp_tail_overhangs); - + // fill overhang_types for (size_t i = 0; i < layer->loverhangs.size(); i++) overhang_types.emplace(&layer->loverhangs[i], i < nDetected ? OverhangType::Detected : @@ -1115,10 +1082,11 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) layers_with_overhangs++; m_highest_overhang_layer = std::max(m_highest_overhang_layer, size_t(layer_nr)); } + if (nEnforced > 0) layers_with_enforcers++; if (!layer->cantilevers.empty()) has_cantilever = true; } - BOOST_LOG_TRIVIAL(info) << "Tree support overhang detection done. " << layers_with_overhangs << " layers with overhangs."; + BOOST_LOG_TRIVIAL(info) << "Tree support overhang detection done. " << layers_with_overhangs << " layers with overhangs. nEnforced=" << layers_with_enforcers; #ifdef SUPPORT_TREE_DEBUG_TO_SVG for (const Layer* layer : m_object->layers()) { @@ -1177,7 +1145,7 @@ void TreeSupport::create_tree_support_layers() raft_slice_z = raft_print_z - height / 2; m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z); } - //1.9.5 + // Layers between the raft contacts and bottom of the object. double dist_to_go = m_slicing_params.object_print_z_min - raft_print_z; auto nsteps = int(ceil(dist_to_go / m_slicing_params.max_suport_layer_height)); @@ -1189,17 +1157,6 @@ void TreeSupport::create_tree_support_layers() } m_raft_layers = layer_id; } - - for (Layer *layer : m_object->layers()) { - SupportLayer* ts_layer = m_object->add_tree_support_layer(layer_id++, layer->height, layer->print_z, layer->slice_z); - if (ts_layer->id() > m_raft_layers) { - SupportLayer* lower_layer = m_object->get_support_layer(ts_layer->id() - 1); - if (lower_layer) { - lower_layer->upper_layer = ts_layer; - ts_layer->lower_layer = lower_layer; - } - } - } } static inline BoundingBox fill_expolygon_generate_paths( @@ -1275,9 +1232,9 @@ static void _make_loops(ExtrusionEntitiesPtr& loops_entities, ExPolygons &suppor depth_per_expoly.erase(depth_iter); expoly_list.erase(first_iter); } - + extrusion_entities_append_loops(loops_entities, std::move(loops), role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - + } static void make_perimeter_and_inner_brim(ExtrusionEntitiesPtr &dst, const ExPolygon &support_area, size_t wall_count, const Flow &flow, ExtrusionRole role) @@ -1287,7 +1244,6 @@ static void make_perimeter_and_inner_brim(ExtrusionEntitiesPtr &dst, const ExPol _make_loops(dst, support_area_new, role, wall_count, flow); } -//1.9.5 static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const ExPolygon& support_area, size_t wall_count, const Flow& flow, ExtrusionRole role, Fill* filler_support, double support_density, bool infill_first=true) { Polygons loops; @@ -1330,11 +1286,12 @@ static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const ExPolygon if (infill_first) dst.insert(dst.end(), loops_entities.begin(), loops_entities.end()); - else { // loops first + else { // loops first loops_entities.insert(loops_entities.end(), dst.begin(), dst.end()); dst = std::move(loops_entities); } } + dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) { return static_cast(entity)->empty(); }), dst.end()); if (infill_first) { // sort regions to reduce travel Points ordering_points; @@ -1390,8 +1347,8 @@ void TreeSupport::generate_toolpaths() } raft_areas = std::move(offset_ex(raft_areas, scale_(3.))); - //1.9.5 - size_t layer_nr = 0; + + size_t layer_nr = 0; for (; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) { SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); coordf_t expand_offset = (layer_nr == 0 ? m_object_config->raft_first_layer_expansion.value : 0.); @@ -1427,12 +1384,13 @@ void TreeSupport::generate_toolpaths() first_non_raft_base.emplace_back(*area_group.area); } } - first_non_raft_base = offset_ex(first_non_raft_base, support_extrusion_width); //1.9.5 + first_non_raft_base = offset_ex(first_non_raft_base, support_extrusion_width); ExPolygons raft_base_areas = intersection_ex(raft_areas, first_non_raft_base); ExPolygons raft_interface_areas = diff_ex(raft_areas, raft_base_areas); + // raft interfaces - for (size_t layer_nr = m_slicing_params.base_raft_layers; + for (layer_nr = m_slicing_params.base_raft_layers; layer_nr < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; layer_nr++) { @@ -1455,7 +1413,6 @@ void TreeSupport::generate_toolpaths() filler_interface, fill_params, erSupportMaterial, support_flow); } - //1.9.5 // layers between raft and object for (; layer_nr < m_raft_layers; layer_nr++) { SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); @@ -1492,7 +1449,6 @@ void TreeSupport::generate_toolpaths() SupportLayer* ts_layer = m_object->get_support_layer(layer_id); Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); - //1.9.5 Flow interface_flow = support_material_interface_flow(m_object, ts_layer->height); // update flow using real support layer height coordf_t support_spacing = object_config.support_base_pattern_spacing.value + support_flow.spacing(); coordf_t support_density = std::min(1., support_flow.spacing() / support_spacing); @@ -1522,11 +1478,9 @@ void TreeSupport::generate_toolpaths() // roof_1st_layer fill_params.density = interface_density; // Note: spacing means the separation between two lines as if they are tightly extruded - //1.9.5 filler_Roof1stLayer->spacing = interface_flow.spacing(); // generate a perimeter first to support interface better ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection(); - //1.9.5 make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_flow, erSupportMaterial, filler_Roof1stLayer.get(), interface_density, false); temp_support_fills->no_sort = true; // make sure loops are first @@ -1537,14 +1491,12 @@ void TreeSupport::generate_toolpaths() } else if (area_group.type == SupportLayer::FloorType) { // floor_areas fill_params.density = bottom_interface_density; - //1.9.5 filler_interface->spacing = interface_flow.spacing(); fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface, interface_flow); } else if (area_group.type == SupportLayer::RoofType) { // roof_areas fill_params.density = interface_density; - //1.9.5 filler_interface->spacing = interface_flow.spacing(); if (m_object_config->support_interface_pattern == smipGrid) { filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value); @@ -1571,7 +1523,8 @@ void TreeSupport::generate_toolpaths() Polygons loops = to_polygons(poly); if (layer_id == 0) { float density = float(m_object_config->raft_first_layer_density.value * 0.01); - fill_expolygons_with_sheath_generate_paths(ts_layer->support_fills.entities, loops, filler_support.get(), density, erSupportMaterial, flow, true, false); + fill_expolygons_with_sheath_generate_paths(ts_layer->support_fills.entities, loops, filler_support.get(), density, erSupportMaterial, flow, + m_support_params, true, false); } else { if (need_infill && m_support_params.base_fill_pattern != ipLightning) { @@ -1579,18 +1532,14 @@ void TreeSupport::generate_toolpaths() // otherwise must draw 1 wall // Don't need extra walls if we have infill. Extra walls may overlap with the infills. size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0; - //1.9.5 make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(min_wall_count, wall_count), flow, erSupportMaterial, filler_support.get(), support_density); } else { - size_t walls = wall_count; - if (area_group.need_extra_wall && walls < 2) walls += 1; - for (size_t i = 1; i < walls; i++) { - Polygons contour_new = offset(poly.contour, -(i - 0.5f) * flow.scaled_spacing(), jtSquare); - loops.insert(loops.end(), contour_new.begin(), contour_new.end()); - } - fill_expolygons_with_sheath_generate_paths(ts_layer->support_fills.entities, loops, nullptr, 0, erSupportMaterial, flow, true, false); + SupportParameters support_params = m_support_params; + if (area_group.need_extra_wall && object_config.tree_support_wall_count.value == 0) + support_params.tree_branch_diameter_double_wall_area_scaled = 0.1; + tree_supports_generate_paths(ts_layer->support_fills.entities, loops, flow, support_params); } } } @@ -1650,8 +1599,9 @@ void TreeSupport::generate_toolpaths() } // sort extrusions to reduce travel, also make sure walls go before infills - if(ts_layer->support_fills.no_sort==false) + if (ts_layer->support_fills.no_sort == false) { chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities); + } } } ); @@ -1663,11 +1613,39 @@ void deleteDirectoryContents(const std::filesystem::path& dir) std::filesystem::remove_all(entry.path()); } + +void TreeSupport::move_bounds_to_contact_nodes(std::vector &move_bounds, + PrintObject &print_object, + const TreeSupport3D::TreeSupportSettings &config) +{ + m_ts_data = print_object.alloc_tree_support_preview_cache(); + // convert move_bounds back to Support Nodes for tree skeleton preview + this->contact_nodes.resize(move_bounds.size()); + for (int layer_nr = move_bounds.size() - 1; layer_nr >= 0; layer_nr--) { + TreeSupport3D::SupportElements &elements = move_bounds[layer_nr]; + auto &contact_nodes_layer = this->contact_nodes[layer_nr]; + for (size_t i = 0; i < elements.size(); i++) { + auto &elem = elements[i]; + auto &state = elem.state; + state.print_z = layer_z(print_object.slicing_parameters(), config, layer_nr); + auto node = this->create_node(state.result_on_layer, state.distance_to_top, layer_nr, 0, state.to_buildplate, nullptr, state.print_z, config.layer_height); + contact_nodes_layer.push_back(node); + if (layer_nr < move_bounds.size() - 1 && !elem.parents.empty()) { + node->parent = contact_nodes[layer_nr + 1][elem.parents.front()]; + for (int j : elem.parents) { + contact_nodes[layer_nr + 1][j]->child = node; + node->parents.push_back(contact_nodes[layer_nr + 1][j]); + } + } + + if (i == 0) { m_ts_data->layer_heights.emplace_back(state.print_z, config.layer_height, std::max(0, layer_nr - 1)); } + } + } +} + void TreeSupport::generate() { - auto t_start = std::chrono::high_resolution_clock::now(); - - if (support_style == smsTreeOrganic) { + if (m_support_params.support_style == smsTreeOrganic) { generate_tree_support_3D(*m_object, this, this->throw_on_cancel); return; } @@ -1683,9 +1661,9 @@ void TreeSupport::generate() create_tree_support_layers(); m_ts_data = m_object->alloc_tree_support_preview_cache(); m_ts_data->is_slim = is_slim; - - // Generate contact points of tree support - std::vector> contact_nodes(m_object->layers().size()); + // // get the ring of outside plate + // auto tmp= diff_ex(offset_ex(m_machine_border, scale_(100)), m_machine_border); + // if (!tmp.empty()) m_ts_data->m_machine_border = tmp[0]; #if USE_SUPPORT_3D std::vector move_bounds(m_highest_overhang_layer + 1); @@ -1694,53 +1672,70 @@ void TreeSupport::generate() Points bedpts = m_machine_border.contour.points; BuildVolume build_volume{ Pointfs{ unscaled(bedpts[0]), unscaled(bedpts[1]),unscaled(bedpts[2]),unscaled(bedpts[3])}, m_print_config->printable_height }; - TreeSupport3D::TreeSupportSettings tree_support_3d_config{ TreeSupport3D::TreeSupportMeshGroupSettings{ *m_object } }; + TreeSupport3D::TreeSupportSettings tree_support_3d_config{ TreeSupport3D::TreeSupportMeshGroupSettings{ *m_object }, m_slicing_params }; m_model_volumes = std::make_unique( *m_object, build_volume, tree_support_3d_config.maximum_move_distance, tree_support_3d_config.maximum_move_distance_slow, 1); // ### Precalculate avoidances, collision etc. if (m_highest_overhang_layer <= tree_support_3d_config.z_distance_top_layers) return; size_t highest_node_layer = m_highest_overhang_layer - tree_support_3d_config.z_distance_top_layers + 1; - m_model_volumes->precalculate(highest_node_layer, throw_on_cancel); + m_model_volumes->precalculate(*m_object, highest_node_layer, throw_on_cancel); auto t_precalc = std::chrono::high_resolution_clock::now(); // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas // ### Place tips of the support tree - SupportGeneratorLayersPtr bottom_contacts(m_highest_overhang_layer + 1, nullptr); - SupportGeneratorLayersPtr top_contacts(m_highest_overhang_layer + 1, nullptr); - SupportGeneratorLayersPtr intermediate_layers(m_highest_overhang_layer + 1, nullptr); - SupportGeneratorLayerStorage layer_storage; + size_t num_support_layers = m_highest_overhang_layer + 1; std::vector overhangs; - - //m_object->print()->set_status(57, _u8L("Support: generate contact points")); - if (support_style != smsTreeHybrid) { - overhangs.resize(m_object->support_layer_count()); - for (size_t i = 0; i < overhangs.size(); i++) { - overhangs[i] = to_polygons(m_object->get_layer(i)->loverhangs); + const int num_raft_layers = int(tree_support_3d_config.raft_layers.size()); + const int num_layers = int(m_object->layer_count()) + num_raft_layers; + bool has_raft = num_raft_layers > 0; + overhangs.resize(num_layers); + for (size_t i = 0; i < m_object->layer_count(); i++) { + for (ExPolygon &expoly : m_object->get_layer(i)->loverhangs) { + Polygons polys = to_polygons(expoly); + if (overhang_types[&expoly] == TreeSupport::SharpTail) { polys = offset(polys, scale_(0.2)); } + append(overhangs[i + num_raft_layers], polys); } - //m_object->clear_support_layers(); - TreeSupport3D::generate_initial_areas(*m_object, *m_model_volumes.get(), tree_support_3d_config, overhangs, move_bounds, top_contacts, layer_storage, throw_on_cancel); } -#endif - generate_contact_points(contact_nodes); + + SupportGeneratorLayerStorage layer_storage; + SupportGeneratorLayersPtr top_contacts; + SupportGeneratorLayersPtr bottom_contacts; + SupportGeneratorLayersPtr interface_layers; + SupportGeneratorLayersPtr base_interface_layers; + SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); + if (m_support_params.has_top_contacts || has_raft) top_contacts.assign(num_support_layers, nullptr); + if (m_support_params.has_bottom_contacts) bottom_contacts.assign(num_support_layers, nullptr); + if (m_support_params.has_interfaces() || has_raft) interface_layers.assign(num_support_layers, nullptr); + if (m_support_params.has_base_interfaces() || has_raft) base_interface_layers.assign(num_support_layers, nullptr); + TreeSupport3D::InterfacePlacer interface_placer{m_object->slicing_parameters(), m_support_params, tree_support_3d_config, + // Outputs + layer_storage, top_contacts, interface_layers, base_interface_layers}; + + TreeSupport3D::generate_initial_areas(*m_object, *m_model_volumes.get(), tree_support_3d_config, overhangs, move_bounds, interface_placer, throw_on_cancel); + move_bounds_to_contact_nodes(move_bounds, *m_object, tree_support_3d_config); +#else + generate_contact_points(); profiler.stage_finish(STAGE_GENERATE_CONTACT_NODES); + m_ts_data->layer_heights = plan_layer_heights(); +#endif //Drop nodes to lower layers. profiler.stage_start(STAGE_DROP_DOWN_NODES); m_object->print()->set_status(60, _u8L("Generating support")); - drop_nodes(contact_nodes); + drop_nodes(); profiler.stage_finish(STAGE_DROP_DOWN_NODES); - smooth_nodes(contact_nodes);// , tree_support_3d_config); + smooth_nodes();// , tree_support_3d_config); //Generate support areas. profiler.stage_start(STAGE_DRAW_CIRCLES); m_object->print()->set_status(65, _u8L("Generating support")); - draw_circles(contact_nodes); + draw_circles(); profiler.stage_finish(STAGE_DRAW_CIRCLES); - + profiler.stage_start(STAGE_GENERATE_TOOLPATHS); m_object->print()->set_status(70, _u8L("Generating support")); generate_toolpaths(); @@ -1765,9 +1760,8 @@ coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, size_t layers_to_ } else { radius = base_radius * (layers_to_top + 1) / (tip_layers * 2); } - radius = std::max(radius, MIN_BRANCH_RADIUS); } - radius = std::min(radius, MAX_BRANCH_RADIUS); + radius = std::clamp(radius, MIN_BRANCH_RADIUS, MAX_BRANCH_RADIUS); return radius; } @@ -1783,9 +1777,7 @@ coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_to { radius = mm_to_top;// this is a 45 degree tip } - - radius = std::max(radius, MIN_BRANCH_RADIUS); - radius = std::min(radius, MAX_BRANCH_RADIUS); + radius = std::clamp(radius, MIN_BRANCH_RADIUS, MAX_BRANCH_RADIUS); // if have interface layers, radius should be larger if (m_object_config->support_interface_top_layers.value > 0) radius = std::max(radius, base_radius); @@ -1796,10 +1788,15 @@ coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_to return radius; } -coordf_t TreeSupport::get_radius(const SupportNode* node, coordf_t base_radius) +coordf_t TreeSupport::calc_radius(coordf_t mm_to_top) +{ + return calc_branch_radius(base_radius, mm_to_top, diameter_angle_scale_factor); +} + +coordf_t TreeSupport::get_radius(const SupportNode* node) { if (node->radius == 0) - node->radius = calc_branch_radius(base_radius, node->dist_mm_to_top, node->diameter_angle_scale_factor); + node->radius = calc_radius(node->dist_mm_to_top); return node->radius; } @@ -1871,7 +1868,7 @@ ExPolygons avoid_object_remove_extra_small_parts(const ExPolygon &expoly, const } } if (idx_max_area >= 0) expolys_out.emplace_back(std::move(expolys_avoid[idx_max_area])); - + return expolys_out; } @@ -1951,7 +1948,7 @@ Polygons TreeSupport::get_trim_support_regions( return polygons_trimming; } -void TreeSupport::draw_circles(const std::vector>& contact_nodes) +void TreeSupport::draw_circles() { const PrintObjectConfig &config = m_object->config(); const Print* print = m_object->print(); @@ -1964,7 +1961,7 @@ void TreeSupport::draw_circles(const std::vector>& con // Use square support if there are too many nodes per layer because circle support needs much longer time to compute // Hower circle support can be printed faster, so we prefer circle for fewer nodes case. - const bool SQUARE_SUPPORT = avg_node_per_layer > 200; + const bool SQUARE_SUPPORT = avg_node_per_layer > 200; const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 25; // The number of vertices in each circle. @@ -1991,19 +1988,16 @@ void TreeSupport::draw_circles(const std::vector>& con const coordf_t layer_height = config.layer_height.value; const size_t top_interface_layers = config.support_interface_top_layers.value; const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; - const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);// * layer_height / branch_radius; //Scale factor per layer to produce the desired angle. - const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning; coordf_t support_extrusion_width = m_support_params.support_extrusion_width; const coordf_t line_width_scaled = scale_(support_extrusion_width); - //1.9.5 const float tree_brim_width = config.raft_first_layer_expansion.value; if (m_object->support_layer_count() <= m_raft_layers) return; BOOST_LOG_TRIVIAL(info) << "draw_circles for object: " << m_object->model_object()->name; - tbb::parallel_for(tbb::blocked_range(0, m_object->layer_count()), + tbb::parallel_for(tbb::blocked_range(0, m_ts_data->layer_heights.size()), [&](const tbb::blocked_range& range) { for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) @@ -2024,6 +2018,7 @@ void TreeSupport::draw_circles(const std::vector>& con ts_layer->print_z = m_ts_data->layer_heights[layer_nr].print_z; ts_layer->height = m_ts_data->layer_heights[layer_nr].height; + size_t obj_layer_nr = m_ts_data->layer_heights[layer_nr].obj_layer_nr; if (ts_layer->height < EPSILON) { continue; } @@ -2041,24 +2036,30 @@ void TreeSupport::draw_circles(const std::vector>& con bool need_extra_wall = false; ExPolygons collision_sharp_tails; ExPolygons collision_base; - auto get_collision = [&](bool sharp_tail) -> ExPolygons& { - if (sharp_tail) { - if (collision_sharp_tails.empty()) - collision_sharp_tails = m_ts_data->get_collision(m_object_config->support_top_z_distance.value, layer_nr); - return collision_sharp_tails; - } - else { - if (collision_base.empty()) - collision_base = m_ts_data->get_collision((layer_nr == 0) ? config.support_object_first_layer_gap : m_ts_data->m_xy_distance, layer_nr); - return collision_base; + auto get_collision = [&](bool sharp_tail) -> ExPolygons &{ + ExPolygons &collision = sharp_tail ? collision_sharp_tails : collision_base; + if (collision.empty()) { + collision = offset_ex(m_ts_data->m_layer_outlines[obj_layer_nr], + sharp_tail ? scale_(top_z_distance) : + scale_((obj_layer_nr == 0) ? config.support_object_first_layer_gap : m_ts_data->m_xy_distance)); + // the top layers may be too close to interface with adaptive layer heights and very small overhang angle + if (top_z_distance > EPSILON) { + float accum_height = 0; + for (size_t layer_id = obj_layer_nr + 1; + layer_id < m_ts_data->m_layer_outlines.size() && (accum_height += m_object->get_layer(layer_id)->height) && accum_height <= top_z_distance; + layer_id++) { + collision = union_ex(collision, offset_ex(m_ts_data->m_layer_outlines[layer_id], scale_(top_z_distance))); + } + } } + return collision; }; - BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << contact_nodes[layer_nr].size(); + BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << curr_layer_nodes.size(); //Draw the support areas and add the roofs appropriately to the support roof instead of normal areas. - ts_layer->lslices.reserve(contact_nodes[layer_nr].size()); + ts_layer->support_islands.reserve(curr_layer_nodes.size()); ExPolygons area_poly; // the polygon node area which will be printed as normal support - for (const SupportNode* p_node : contact_nodes[layer_nr]) + for (const SupportNode* p_node : curr_layer_nodes) { if (print->canceled()) break; @@ -2101,7 +2102,7 @@ void TreeSupport::draw_circles(const std::vector>& con circle.points[i] = circle.points[i] * scale + node.position; } } - if (layer_nr == 0 && m_raft_layers == 0) { + if (obj_layer_nr == 0 && m_raft_layers == 0) { double brim_width = tree_brim_width > 0 ? tree_brim_width : std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius); auto tmp=offset(circle, scale_(brim_width)); if(!tmp.empty()) @@ -2114,7 +2115,6 @@ void TreeSupport::draw_circles(const std::vector>& con if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1) overhang_expanded.emplace_back(node.overhang); else { - // 对于有缺陷的模型,overhang膨胀以后可能是空的! overhang_expanded = offset_ex({ node.overhang }, scale_(m_ts_data->m_xy_distance)); } append(area, overhang_expanded); @@ -2129,14 +2129,14 @@ void TreeSupport::draw_circles(const std::vector>& con need_extra_wall = true; } - if (layer_nr>0 && node.distance_to_top < 0) + if (obj_layer_nr>0 && node.distance_to_top < 0) append(roof_gap_areas, area); - else if (layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false) + else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false) { append(roof_1st_layer, area); max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top); } - else if (layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false) + else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false) { append(roof_areas, area); max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top); @@ -2144,7 +2144,6 @@ void TreeSupport::draw_circles(const std::vector>& con } else { - //area = diff_clipped(area, get_collision(node.is_sharp_tail && node.distance_to_top<=0)); append(base_areas, area); max_layers_above_base = std::max(max_layers_above_base, node.dist_mm_to_top); } @@ -2154,9 +2153,9 @@ void TreeSupport::draw_circles(const std::vector>& con //m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str()); // join roof segments - double contact_dist_scaled = scale_(0.5);// scale_(m_slicing_params.gap_support_object); - roof_areas = std::move(offset2_ex(roof_areas, contact_dist_scaled, -contact_dist_scaled)); - roof_1st_layer = std::move(offset2_ex(roof_1st_layer, contact_dist_scaled, -contact_dist_scaled)); + roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(false)); + roof_areas = intersection_ex(roof_areas, m_machine_border); + roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), get_collision(false)); // roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer))); @@ -2180,13 +2179,11 @@ void TreeSupport::draw_circles(const std::vector>& con { // find the lowest interface layer // TODO the gap may not be exact when "independent support layer height" is enabled - size_t layer_nr_next = layer_nr; - for (size_t i = 0; i < bottom_interface_layers && layer_nr_next>0; i++) { - layer_nr_next = m_ts_data->layer_heights[layer_nr_next].next_layer_nr; - } - for (size_t i = 0; i <= bottom_gap_layers && i<=layer_nr_next; i++) + size_t layer_nr_next = layer_nr - bottom_interface_layers; + size_t obj_layer_nr_next = m_ts_data->layer_heights[layer_nr_next].obj_layer_nr; + for (size_t i = 0; i <= bottom_gap_layers && i <= obj_layer_nr_next; i++) { - const Layer* below_layer = m_object->get_layer(layer_nr_next - i); + const Layer *below_layer = m_object->get_layer(obj_layer_nr_next - i); ExPolygons bottom_interface = std::move(intersection_ex(base_areas, below_layer->lslices)); floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end()); } @@ -2197,25 +2194,33 @@ void TreeSupport::draw_circles(const std::vector>& con base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10))); } } - if (bottom_gap_layers > 0 && layer_nr > bottom_gap_layers) { - const Layer* below_layer = m_object->get_layer(layer_nr - bottom_gap_layers); + if (bottom_gap_layers > 0 && m_ts_data->layer_heights[layer_nr].obj_layer_nr > bottom_gap_layers) { + const Layer* below_layer = m_object->get_layer(m_ts_data->layer_heights[layer_nr].obj_layer_nr - bottom_gap_layers); ExPolygons bottom_gap_area = std::move(intersection_ex(floor_areas, below_layer->lslices)); if (!bottom_gap_area.empty()) { floor_areas = std::move(diff_ex(floor_areas, bottom_gap_area)); } } auto &area_groups = ts_layer->area_groups; - for (auto& area : ts_layer->base_areas) { - area_groups.emplace_back(&area, SupportLayer::BaseType, max_layers_above_base); - area_groups.back().need_infill = overlaps({ area }, area_poly); + for (auto& expoly : ts_layer->base_areas) { + if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base); + area_groups.back().need_infill = overlaps({ expoly }, area_poly); area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill; } - for (auto& area : ts_layer->roof_areas) { - area_groups.emplace_back(&area, SupportLayer::RoofType, max_layers_above_roof); + for (auto& expoly : ts_layer->roof_areas) { + if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof); area_groups.back().interface_id = interface_id; } - for (auto &area : ts_layer->floor_areas) area_groups.emplace_back(&area, SupportLayer::FloorType, 10000); - for (auto &area : ts_layer->roof_1st_layer) area_groups.emplace_back(&area, SupportLayer::Roof1stLayer, max_layers_above_roof1); + for (auto &expoly : ts_layer->floor_areas) { + if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::FloorType, 10000); + } + for (auto &expoly : ts_layer->roof_1st_layer) { + if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::Roof1stLayer, max_layers_above_roof1); + } for (auto &area_group : area_groups) { auto& expoly = area_group.area; @@ -2227,14 +2232,14 @@ void TreeSupport::draw_circles(const std::vector>& con expoly->holes.end()); if (layer_nr < brim_skirt_layers) - ts_layer->lslices.emplace_back(*expoly); + ts_layer->support_islands.emplace_back(*expoly); } - ts_layer->lslices = std::move(union_ex(ts_layer->lslices)); + ts_layer->support_islands = std::move(union_ex(ts_layer->support_islands)); //Must update bounding box which is used in avoid crossing perimeter ts_layer->lslices_bboxes.clear(); - ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size()); - for (const ExPolygon& expoly : ts_layer->lslices) + ts_layer->lslices_bboxes.reserve(ts_layer->support_islands.size()); + for (const ExPolygon& expoly : ts_layer->support_islands) ts_layer->lslices_bboxes.emplace_back(get_extents(expoly)); ts_layer->backup_untyped_slices(); @@ -2248,7 +2253,7 @@ void TreeSupport::draw_circles(const std::vector>& con std::vector contours; std::vector overhangs; - for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) { + for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { if (print->canceled()) break; const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); @@ -2336,7 +2341,7 @@ void TreeSupport::draw_circles(const std::vector>& con // polygon pointer: depth, direction, farPoint std::map> holePropagationInfos; - for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) { + for (int layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--) { if (print->canceled()) break; //m_object->print()->set_status(66, (boost::format(_u8L("Support: fix holes at layer %d")) % layer_nr).str()); @@ -2397,7 +2402,7 @@ void TreeSupport::draw_circles(const std::vector>& con { // if roof1 interface is inside a hole, need to expand the interface for (auto& roof1 : ts_layer->roof_1st_layer) { - //if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center())) + //if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center())) bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point& pt) { return hole.contains(pt); }); if (is_inside_hole) { Polygon hole_reoriented = hole; @@ -2411,7 +2416,7 @@ void TreeSupport::draw_circles(const std::vector>& con // make sure 1) roof1 and object 2) roof1 and roof, won't intersect // Note: We can't replace roof1 directly, as we have recorded its address. // So instead we need to replace its members one by one. - auto tmp1 = diff_ex(roof1, get_collision((layer_nr == 0) ? config.support_object_xy_distance : m_ts_data->m_xy_distance, layer_nr)); + auto tmp1 = diff_ex(roof1, get_collision(0, m_ts_data->layer_heights[layer_nr].obj_layer_nr)); tmp1 = diff_ex(tmp1, ts_layer->roof_areas); if (!tmp1.empty()) { roof1.contour = std::move(tmp1[0].contour); @@ -2428,14 +2433,14 @@ void TreeSupport::draw_circles(const std::vector>& con #ifdef SUPPORT_TREE_DEBUG_TO_SVG - for (int layer_nr = m_object->layer_count() - 1; layer_nr >= 0; layer_nr--) { + for (int layer_nr = m_object->support_layer_count() - 1; layer_nr >= 0; layer_nr--) { SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); ExPolygons& base_areas = ts_layer->base_areas; ExPolygons& roof_areas = ts_layer->roof_areas; ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer; ExPolygons roofs = roof_areas; append(roofs, roof_1st_layer); if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue; - draw_contours_and_nodes_to_svg(debug_out_path("circles_%d_%.2f.svg", layer_nr, ts_layer->print_z), base_areas, roofs, ts_layer->lslices, {}, {}, {"base", "roof", "lslices"}, {"blue","red","black"}); + draw_contours_and_nodes_to_svg(debug_out_path("circles_%d_%.2f.svg", layer_nr, ts_layer->print_z), base_areas, roofs, ts_layer->lslices_extrudable, {}, {}, {"base", "roof", "lslices"}, {"blue","red","black"}); } #endif // SUPPORT_TREE_DEBUG_TO_SVG @@ -2450,7 +2455,7 @@ void TreeSupport::draw_circles(const std::vector>& con double SupportNode::diameter_angle_scale_factor; -void TreeSupport::drop_nodes(std::vector>& contact_nodes) +void TreeSupport::drop_nodes() { const PrintObjectConfig &config = m_object->config(); // Use Minimum Spanning Tree to connect the points on each layer and move them while dropping them down. @@ -2461,32 +2466,23 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod double tan_angle = tan(angle); // when nodes are thick, they can move further. this is the max angle const coordf_t max_move_distance = (angle < M_PI / 2) ? (coordf_t)(tan_angle * layer_height)*wall_count : std::numeric_limits::max(); const double max_move_distance2 = max_move_distance * max_move_distance; - const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2; - const size_t tip_layers = branch_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. - const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);//*layer_height / branch_radius; // Scale factor per layer to produce the desired angle. + const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution; const bool support_on_buildplate_only = config.support_on_build_plate_only.value; const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; const size_t top_interface_layers = config.support_interface_top_layers.value; SupportNode::diameter_angle_scale_factor = diameter_angle_scale_factor; - auto get_max_move_dist = [this, &config, branch_radius, tip_layers, diameter_angle_scale_factor, wall_count, support_extrusion_width](const SupportNode *node, int power = 1) { - double move_dist = node->max_move_dist; + auto get_max_move_dist = [this, &config, tan_angle, wall_count, support_extrusion_width](const SupportNode *node, int power = 1) { if (node->max_move_dist == 0) { - node->radius = get_radius(node, branch_radius); - double angle = config.tree_support_branch_angle.value; - if (angle > 30.0 && node->radius > MIN_BRANCH_RADIUS) - angle = (node->radius - MIN_BRANCH_RADIUS) / (MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS) * (config.tree_support_branch_angle.value - 30.0) + 30.0; - double tan_angle = tan(angle * M_PI / 180); - node->max_move_dist = (angle < 90) ? (coordf_t) (tan_angle * node->height) : std::numeric_limits::max(); - node->max_move_dist = std::min(node->max_move_dist, support_extrusion_width); - move_dist = node->max_move_dist; + node->radius = get_radius(node); + node->max_move_dist = std::min(tan_angle * node->height, support_extrusion_width); } + double move_dist = node->max_move_dist; if (power == 2) move_dist = SQ(move_dist); return move_dist; }; - m_ts_data->layer_heights = plan_layer_heights(contact_nodes); std::vector &layer_heights = m_ts_data->layer_heights; if (layer_heights.empty()) return; @@ -2501,27 +2497,28 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod std::vector > all_layer_radius(contact_nodes.size()); std::vector> all_layer_node_dist(contact_nodes.size()); for (size_t layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--) { - if (layer_heights[layer_nr].height < EPSILON) continue; auto& layer_radius = all_layer_radius[layer_nr]; auto& layer_node_dist = all_layer_node_dist[layer_nr]; for (auto* p_node : contact_nodes[layer_nr]) { layer_node_dist.emplace(p_node->dist_mm_to_top); } - size_t layer_nr_next = layer_heights[layer_nr].next_layer_nr; + size_t layer_nr_next = layer_nr - 1; if (layer_nr_next <= contact_nodes.size() - 1 && layer_nr_next > 0) { for (auto node_dist : layer_node_dist) all_layer_node_dist[layer_nr_next].emplace(node_dist + layer_heights[layer_nr].height); } for (auto node_dist : layer_node_dist) { - layer_radius.emplace(calc_branch_radius(branch_radius, node_dist, diameter_angle_scale_factor)); + layer_radius.emplace(calc_radius(node_dist)); } } // parallel pre-compute avoidance tbb::parallel_for(tbb::blocked_range(0, contact_nodes.size() - 1), [&](const tbb::blocked_range &range) { for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { for (auto node_radius : all_layer_radius[layer_nr]) { - m_ts_data->get_avoidance(node_radius, layer_nr); - get_collision(m_ts_data->m_xy_distance, layer_nr); + size_t obj_layer_nr= layer_heights[layer_nr].obj_layer_nr; + m_ts_data->get_avoidance(node_radius, obj_layer_nr); + get_collision(0, obj_layer_nr); + get_collision(node_radius, obj_layer_nr); } } }); @@ -2542,18 +2539,19 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod auto& layer_contact_nodes = contact_nodes[layer_nr]; if (layer_contact_nodes.empty()) continue; - - int layer_nr_next = layer_heights[layer_nr].next_layer_nr; + + int layer_nr_next = layer_nr - 1; coordf_t print_z = layer_heights[layer_nr].print_z; coordf_t print_z_next = layer_heights[layer_nr_next].print_z; - coordf_t height_next = layer_heights[layer_nr_next].height; + coordf_t height_next = layer_heights[layer_nr_next].height; + size_t obj_layer_nr = layer_heights[layer_nr].obj_layer_nr; + size_t obj_layer_nr_next = layer_heights[layer_nr_next].obj_layer_nr; std::deque> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches. - const Layer* ts_layer = m_object->get_support_layer(layer_nr); m_object->print()->set_status(60 + int(10 * (1 - float(layer_nr) / contact_nodes.size())), _u8L("Generating support"));// (boost::format(_u8L("Support: propagate branches at layer %d")) % layer_nr).str()); - Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(layer_nr)); + Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(obj_layer_nr)); //std::unordered_map& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; tbb::concurrent_unordered_map mst_line_x_layer_contour_cache; auto is_line_cut_by_contour = [&mst_line_x_layer_contour_cache,&layer_contours](Point a, Point b) @@ -2577,7 +2575,7 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod }; //Group together all nodes for each part. - const ExPolygons& parts = m_ts_data->m_layer_outlines_below[layer_nr]; + const ExPolygons& parts = m_ts_data->m_layer_outlines_below[obj_layer_nr]; std::vector> nodes_per_part(1 + parts.size()); //All nodes that aren't inside a part get grouped together in the 0th part. for (SupportNode* p_node : layer_contact_nodes) { @@ -2645,19 +2643,18 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod #ifdef SUPPORT_TREE_DEBUG_TO_SVG coordf_t branch_radius_temp = 0; coordf_t max_y = std::numeric_limits::min(); - draw_layer_mst(debug_out_path("mtree_%.2f.svg", ts_layer->print_z), spanning_trees, m_object->get_layer(layer_nr)->lslices); + draw_layer_mst(debug_out_path("mtree_%.2f.svg", print_z), spanning_trees, m_object->get_layer(obj_layer_nr)->lslices_extrudable); #endif for (size_t group_index = 0; group_index < nodes_per_part.size(); group_index++) { auto& nodes_this_part = nodes_per_part[group_index]; const MinimumSpanningTree& mst = spanning_trees[group_index]; //In the first pass, merge all nodes that are close together. - tbb::concurrent_unordered_set to_delete; std::vector> nodes_vec(nodes_this_part.begin(), nodes_this_part.end()); tbb::parallel_for_each(nodes_vec.begin(), nodes_vec.end(), [&](const std::pair& entry) { SupportNode* p_node = entry.second; SupportNode& node = *p_node; - if (to_delete.find(p_node) != to_delete.end()) + if (!p_node->valid) { return; //Delete this node (don't create a new node for it on the next layer). } @@ -2666,7 +2663,8 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod // Remove all circle neighbours that are completely inside the polygon and merge them into this node. for (const Point &neighbour : neighbours) { SupportNode * neighbour_node = nodes_this_part[neighbour]; - if(neighbour_node->type==ePolygon) continue; + if (neighbour_node->valid == false) continue; + if (neighbour_node->type == ePolygon) continue; coord_t neighbour_radius = scale_(neighbour_node->radius); Point pt_north = neighbour + Point(0, neighbour_radius), pt_south = neighbour - Point(0, neighbour_radius), pt_west = neighbour - Point(neighbour_radius, 0), pt_east = neighbour + Point(neighbour_radius, 0); @@ -2677,17 +2675,17 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); node.merged_neighbours.push_front(neighbour_node); node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); - to_delete.insert(neighbour_node); + neighbour_node->valid = false; } } - } - else if (neighbours.size() == 1 && vsize2_with_unscale(neighbours[0] - node.position) < max_move_distance2 && mst.adjacent_nodes(neighbours[0]).size() == 1 && + } else if (neighbours.size() == 1 && vsize2_with_unscale(neighbours[0] - node.position) < get_max_move_dist(p_node, 2) && + mst.adjacent_nodes(neighbours[0]).size() == 1 && nodes_this_part[neighbours[0]]->type!=ePolygon) // We have just two nodes left, and they're very close, and the only neighbor is not ePolygon { //Insert a completely new node and let both original nodes fade. Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes. - coordf_t next_radius = calc_branch_radius(branch_radius, node.dist_mm_to_top+height_next, diameter_angle_scale_factor); - auto avoid_layer = get_avoidance(next_radius, layer_nr_next); + coordf_t next_radius = calc_radius(node.dist_mm_to_top+height_next); + auto avoid_layer = get_avoidance(next_radius, obj_layer_nr_next); if (group_index == 0) { //Avoid collisions. @@ -2703,14 +2701,14 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod node_parent = p_node->parent ? p_node : neighbour; // Make sure the next pass doesn't drop down either of these (since that already happened). node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node); - const bool to_buildplate = !is_inside_ex(get_collision(0, layer_nr_next), next_position); - SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, layer_nr_next, node_parent->support_roof_layers_below - 1, to_buildplate, node_parent, + const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position); + SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next, node_parent->support_roof_layers_below - 1, to_buildplate, node_parent, print_z_next, height_next); get_max_move_dist(next_node); m_ts_data->m_mutex.lock(); contact_nodes[layer_nr_next].push_back(next_node); - to_delete.insert(neighbour); - to_delete.insert(p_node); + neighbour->valid = false; + p_node->valid = false; m_ts_data->m_mutex.unlock(); } else if (neighbours.size() > 1) //Don't merge leaf nodes because we would then incur movement greater than the maximum move distance. @@ -2718,7 +2716,7 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod //Remove all neighbours that are too close and merge them into this node. for (const Point& neighbour : neighbours) { - if (vsize2_with_unscale(neighbour - node.position) < /*max_move_distance2*/get_max_move_dist(&node,2)) + if (vsize2_with_unscale(neighbour - node.position) < get_max_move_dist(&node,2)) { SupportNode* neighbour_node = nodes_this_part[neighbour]; if (neighbour_node->type == ePolygon) continue; @@ -2726,11 +2724,10 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod if(node.dist_mm_to_top < neighbour_node->dist_mm_to_top) continue; m_ts_data->m_mutex.lock(); - if (to_delete.find(p_node) == to_delete.end()) + if (p_node->valid) { // since we are processing all nodes in parallel, p_node may have been deleted by another thread. In this case, we should not delete neighbour_node. node.merged_neighbours.push_front(neighbour_node); node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); - to_delete.insert(neighbour_node); neighbour_node->valid = false; } m_ts_data->m_mutex.unlock(); @@ -2742,10 +2739,10 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod //In the second pass, move all middle nodes. tbb::parallel_for_each(nodes_vec.begin(), nodes_vec.end(), [&](const std::pair& entry) { - + SupportNode* p_node = entry.second; const SupportNode& node = *p_node; - if (to_delete.find(p_node) != to_delete.end()) + if (!p_node->valid) { return; } @@ -2753,22 +2750,13 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod // polygon node do not merge or move const bool to_buildplate = true; // keep only the part that won't be removed by the next layer - ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision_polys(0, layer_nr_next)); - // find the biggest overhang if there are many - float area_biggest = -1; - int index_biggest = -1; - for (int i = 0; i < overhangs_next.size(); i++) { - float a=area(overhangs_next[i]); - if (a > area_biggest) { - area_biggest = a; - index_biggest = i; - } - } - if (index_biggest >= 0) { - SupportNode* next_node = m_ts_data->create_node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, to_buildplate, - p_node, print_z_next, height_next); + ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision(0, obj_layer_nr_next)); + for(auto& overhang:overhangs_next) { + Point next_pt = overhang.contour.centroid(); + SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1, + to_buildplate, p_node, print_z_next, height_next); next_node->max_move_dist = 0; - next_node->overhang = std::move(overhangs_next[index_biggest]); + next_node->overhang = std::move(overhang); m_ts_data->m_mutex.lock(); contact_nodes[layer_nr_next].emplace_back(next_node); m_ts_data->m_mutex.unlock(); @@ -2778,11 +2766,11 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod } //If the branch falls completely inside a collision area (the entire branch would be removed by the X/Y offset), delete it. - if (group_index > 0 && is_inside_ex(get_collision(m_ts_data->m_xy_distance, layer_nr), node.position)) + if (group_index > 0 && is_inside_ex(get_collision(0, obj_layer_nr), node.position)) { std::scoped_lock lock(m_ts_data->m_mutex); - const coordf_t branch_radius_node = get_radius(p_node, branch_radius); - Point to_outside = projection_onto(get_collision(m_ts_data->m_xy_distance, layer_nr), node.position); + const coordf_t branch_radius_node = get_radius(p_node); + Point to_outside = projection_onto(get_collision(0, obj_layer_nr), node.position); double dist2_to_outside = vsize2_with_unscale(node.position - to_outside); if (dist2_to_outside >= branch_radius_node * branch_radius_node) //Too far inside. { @@ -2791,7 +2779,6 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod unsupported_branch_leaves.push_front({ layer_nr, p_node }); } else { - to_delete.insert(p_node); p_node->valid = false; } return; @@ -2799,7 +2786,6 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod // if the link between parent and current is cut by contours, mark current as bottom contact node if (p_node->parent && intersection_ln({p_node->position, p_node->parent->position}, layer_contours).empty()==false) { - to_delete.insert(p_node); p_node->valid = false; return; } @@ -2812,23 +2798,23 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod // 1. do not merge neighbors under 5mm // 2. Only merge node with single neighbor in distance between [max_move_distance, 10mm/layer_height] float dist2_to_first_neighbor = neighbours.empty() ? 0 : vsize2_with_unscale(neighbours[0] - node.position); - if (ts_layer->print_z > DO_NOT_MOVER_UNDER_MM && - (neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= max_move_distance2))) // Only nodes that aren't about to collapse. + if (node.print_z > DO_NOT_MOVER_UNDER_MM && + (neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= get_max_move_dist(p_node, 2)))) // Only nodes that aren't about to collapse. { // Move towards the average position of all neighbours. Point sum_direction(0, 0); for (const Point &neighbour : neighbours) { // do not move to the neighbor to be deleted SupportNode *neighbour_node = nodes_this_part[neighbour]; - if (to_delete.find(neighbour_node) != to_delete.end()) continue; + if (!neighbour_node->valid) continue; Point direction = neighbour - node.position; // do not move to neighbor that's too far away (即使以最大速度移动,在接触热床之前都无法汇聚) float dist2_to_neighbor = vsize2_with_unscale(direction); - coordf_t branch_bottom_radius = calc_branch_radius(branch_radius, node.dist_mm_to_top + node.print_z, diameter_angle_scale_factor); - coordf_t neighbour_bottom_radius = calc_branch_radius(branch_radius, neighbour_node->dist_mm_to_top + neighbour_node->print_z, diameter_angle_scale_factor); - double max_converge_distance = tan_angle * (ts_layer->print_z - DO_NOT_MOVER_UNDER_MM) + std::max(branch_bottom_radius, neighbour_bottom_radius); + coordf_t branch_bottom_radius = calc_radius(node.dist_mm_to_top + node.print_z); + coordf_t neighbour_bottom_radius = calc_radius(neighbour_node->dist_mm_to_top + neighbour_node->print_z); + double max_converge_distance = tan_angle * (p_node->print_z - DO_NOT_MOVER_UNDER_MM) + std::max(branch_bottom_radius, neighbour_bottom_radius); if (dist2_to_neighbor > max_converge_distance * max_converge_distance) continue; if (is_line_cut_by_contour(node.position, neighbour)) continue; @@ -2836,16 +2822,16 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod if (!is_strong) sum_direction += direction * (1 / dist2_to_neighbor); else - sum_direction += direction; + sum_direction += direction; } if (!is_strong) move_to_neighbor_center = sum_direction; else { - if (vsize2_with_unscale(sum_direction) <= max_move_distance2) { + if (vsize2_with_unscale(sum_direction) <= get_max_move_dist(p_node, 2)) { move_to_neighbor_center = sum_direction; } else { - move_to_neighbor_center = normal(sum_direction, scale_(get_max_move_dist(&node))); + move_to_neighbor_center = normal(sum_direction, scale_(get_max_move_dist(p_node))); } } } @@ -2853,11 +2839,11 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod #ifdef SUPPORT_TREE_DEBUG_TO_SVG if (node.position(1) > max_y) { max_y = node.position(1); - branch_radius_temp = get_radius(p_node, branch_radius); + branch_radius_temp = get_radius(p_node); } #endif - coordf_t next_radius = calc_branch_radius(branch_radius, node.dist_mm_to_top + height_next, diameter_angle_scale_factor); - auto avoidance_next = get_avoidance(next_radius, layer_nr_next); + coordf_t next_radius = calc_radius(node.dist_mm_to_top + height_next); + auto avoidance_next = get_avoidance(next_radius, obj_layer_nr_next); Point to_outside = projection_onto(avoidance_next, node.position); Point direction_to_outer = to_outside - node.position; @@ -2865,7 +2851,7 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod // don't move if // 1) line of node and to_outside is cut by contour (means supports may intersect with object) // 2) it's impossible to move to build plate - if (is_line_cut_by_contour(node.position, to_outside) || dist2_to_outer > max_move_distance2 * SQ(layer_nr) || + if (is_line_cut_by_contour(node.position, to_outside) || dist2_to_outer > max_move_distance2 * SQ(obj_layer_nr) || !is_inside_ex(avoidance_next, node.position)) { // try move to outside of lower layer instead Point candidate_vertex = node.position; @@ -2893,12 +2879,17 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod movement = move_to_neighbor_center; // otherwise move to neighbor center first } - if (vsize2_with_unscale(movement) > get_max_move_dist(&node,2)) - movement = normal(movement, scale_(get_max_move_dist(&node))); + if (node.is_sharp_tail && node.dist_mm_to_top < 3) { + movement = normal(node.skin_direction, scale_(get_max_move_dist(&node))); + } + else if (dist2_to_outer > 0) + movement = normal(direction_to_outer, scale_(get_max_move_dist(&node))); + else + movement = normal(move_to_neighbor_center, scale_(get_max_move_dist(&node))); next_layer_vertex += movement; - if (group_index == 0) { + if (group_index == 0 && 0) { // Avoid collisions. const coordf_t max_move_between_samples = get_max_move_dist(&node, 1) + radius_sample_resolution + EPSILON; // 100 micron extra for rounding errors. bool is_outside = move_out_expolys(avoidance_next, next_layer_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); @@ -2908,10 +2899,15 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod if (is_outside) { next_layer_vertex = candidate_vertex; } } } - - const bool to_buildplate = !is_inside_ex(get_collision(0, layer_nr_next), next_layer_vertex); - SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node, + auto next_collision = get_collision(0, obj_layer_nr_next); + const bool to_buildplate = !is_inside_ex(next_collision, next_layer_vertex); + SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node, print_z_next, height_next); + // don't increase radius if next node will collide partially with the object (STUDIO-7883) + to_outside = projection_onto(next_collision, next_node->position); + direction_to_outer = to_outside - node.position; + double dist_to_outer = unscale_(direction_to_outer.cast().norm()); + next_node->radius = std::max(node.radius, std::min(next_node->radius, dist_to_outer)); get_max_move_dist(next_node); m_ts_data->m_mutex.lock(); contact_nodes[layer_nr_next].push_back(next_node); @@ -2922,12 +2918,12 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod #ifdef SUPPORT_TREE_DEBUG_TO_SVG if (contact_nodes[layer_nr].empty() == false) { - draw_contours_and_nodes_to_svg(debug_out_path("contact_points_%.2f.svg", contact_nodes[layer_nr][0]->print_z), get_collision(0,layer_nr_next), - get_avoidance(branch_radius_temp, layer_nr), - m_ts_data->m_layer_outlines[layer_nr], + draw_contours_and_nodes_to_svg(debug_out_path("contact_points_%.2f.svg", contact_nodes[layer_nr][0]->print_z), get_collision(0,obj_layer_nr_next), + get_avoidance(branch_radius_temp, obj_layer_nr), + m_ts_data->m_layer_outlines[obj_layer_nr], contact_nodes[layer_nr], contact_nodes[layer_nr_next], { "overhang","avoid","outline" }, { "blue","red","yellow" }); - BOOST_LOG_TRIVIAL(debug) << "drop_nodes layer->next " << layer_nr << "->" << layer_nr_next << ", print_z=" << ts_layer->print_z + BOOST_LOG_TRIVIAL(debug) << "drop_nodes layer->next " << layer_nr << "->" << layer_nr_next << ", print_z=" << print_z << ", num points: " << contact_nodes[layer_nr].size() << "->" << contact_nodes[layer_nr_next].size(); for (size_t i = 0; i < std::min(size_t(5), contact_nodes[layer_nr].size()); i++) { auto &node = contact_nodes[layer_nr][i]; @@ -2944,8 +2940,6 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod for (; i_node != nullptr; i_node = i_node->parent) { size_t i_layer = i_node->obj_layer_nr; - std::vector::iterator to_erase = std::find(contact_nodes[i_layer].begin(), contact_nodes[i_layer].end(), i_node); - if (to_erase != contact_nodes[i_layer].end()) { // update the parent-child chain if (i_node->parent) { @@ -2960,23 +2954,27 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod i_node->child->parents.erase(std::find(i_node->child->parents.begin(), i_node->child->parents.end(), i_node)); append(i_node->child->parents, i_node->parents); } - contact_nodes[i_layer].erase(to_erase); - i_node->valid = false; + i_node->is_processed = true; // mark to be deleted later for (SupportNode* neighbour : i_node->merged_neighbours) { - unsupported_branch_leaves.push_front({ i_layer, neighbour }); + if (neighbour && !neighbour->is_processed) + unsupported_branch_leaves.push_front({ i_layer, neighbour }); } } } } + for (auto &layer_contact_nodes : contact_nodes) { + if (!layer_contact_nodes.empty()) + layer_contact_nodes.erase(std::remove_if(layer_contact_nodes.begin(), layer_contact_nodes.end(), [](SupportNode *node) { return node->is_processed; }), + layer_contact_nodes.end()); + } } BOOST_LOG_TRIVIAL(debug) << "after m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size(); - } -void TreeSupport::smooth_nodes(std::vector> &contact_nodes) +void TreeSupport::smooth_nodes() { for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { std::vector &curr_layer_nodes = contact_nodes[layer_nr]; @@ -2987,6 +2985,10 @@ void TreeSupport::smooth_nodes(std::vector> &contact_ } float max_move = scale_(m_object_config->support_line_width / 2); + // if the branch is very tall, the tip also needs extra wall + float thresh_tall_branch = 100; + float thresh_dist_to_top = 30; + for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) { std::vector &curr_layer_nodes = contact_nodes[layer_nr]; if (curr_layer_nodes.empty()) continue; @@ -2996,17 +2998,20 @@ void TreeSupport::smooth_nodes(std::vector> &contact_ std::vector radii; std::vector branch; SupportNode * p_node = node; + float total_height = 0; // add a fixed head if it's not a polygon node, see STUDIO-4403 // Polygon node can't be added because the move distance might be huge, making the nodes in between jump and dangling if (node->child && node->child->type!=ePolygon) { pts.push_back(p_node->child->position); radii.push_back(p_node->child->radius); branch.push_back(p_node->child); + total_height += p_node->child->height; } do { pts.push_back(p_node->position); radii.push_back(p_node->radius); branch.push_back(p_node); + total_height += p_node->height; p_node = p_node->parent; } while (p_node && !p_node->is_processed); if (pts.size() < 3) continue; @@ -3025,7 +3030,8 @@ void TreeSupport::smooth_nodes(std::vector> &contact_ branch[i]->radius = radii1[i]; branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2; branch[i]->is_processed = true; - if (branch[i]->parents.size()>1 || (branch[i]->movement.x() > max_move || branch[i]->movement.y() > max_move)) + if (branch[i]->parents.size() > 1 || (branch[i]->movement.x() > max_move || branch[i]->movement.y() > max_move) || + (total_height > thresh_tall_branch && branch[i]->dist_mm_to_top < thresh_dist_to_top)) branch[i]->need_extra_wall = true; BOOST_LOG_TRIVIAL(trace) << "smooth_nodes: layer_nr=" << layer_nr << ", i=" << i << ", pt=" << pt << ", movement=" << branch[i]->movement << ", radius=" << branch[i]->radius; } @@ -3048,11 +3054,10 @@ void TreeSupport::smooth_nodes(std::vector> &contact_ } #if USE_SUPPORT_3D -void TreeSupport::smooth_nodes(std::vector>& contact_nodes, const TreeSupport3D::TreeSupportSettings& config) +void TreeSupport::smooth_nodes(const TreeSupport3D::TreeSupportSettings& config) { const coordf_t branch_radius = m_object_config->tree_support_branch_diameter.value / 2; const coordf_t branch_radius_scaled = scale_(branch_radius); - const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.); // contact nodes to support elements TreeSupport3D::SupportElements move_bounds; std::map node2elemIdx; @@ -3085,7 +3090,7 @@ void TreeSupport::smooth_nodes(std::vector>& contact_n state.skip_ovalisation = false; Polygons circle; if (node->type == eCircle) { - const Polygon base_circle = make_circle(scale_(std::max(1.0,node->radius)), TreeSupport3D::SUPPORT_TREE_CIRCLE_RESOLUTION); + const Polygon base_circle = make_circle(scale_(std::max(1.0,node->radius)), SUPPORT_TREE_CIRCLE_RESOLUTION); circle.emplace_back(base_circle); circle.front().translate(node->position); } @@ -3118,29 +3123,29 @@ void TreeSupport::smooth_nodes(std::vector>& contact_n elements_with_link_down.push_back({ elem_parent, child }); } } - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(contact_nodes.size()); ++layer_idx) { + for (int layer_idx = 0; layer_idx < int(contact_nodes.size()); ++layer_idx) { linear_data_layers.emplace_back(0); } break; - std::vector> map_downwards_old; - std::vector> map_downwards_new; + std::vector> map_downwards_old; + std::vector> map_downwards_new; linear_data_layers.emplace_back(0); - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(contact_nodes.size()); ++layer_idx) { + for (int layer_idx = 0; layer_idx < int(contact_nodes.size()); ++layer_idx) { std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto& l, auto& r) { return l.first < r.first; }); auto& layer = contact_nodes[layer_idx]; for (size_t elem_idx = 0; elem_idx < layer.size(); ++elem_idx) { - Node* node = layer[elem_idx]; + SupportNode* node = layer[elem_idx]; int child = -1; if (layer_idx > 0) { - auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), node, [](auto& l, const Node* r) { return l.first < r; }); + auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), node, [](auto& l, const SupportNode* r) { return l.first < r; }); if (it != map_downwards_old.end() && it->first == node) { child = it->second; // Only one link points to a node above from below. assert(!(++it != map_downwards_old.end() && it->first == node)); } - const Node* pchild = child == -1 ? nullptr : contact_nodes[layer_idx - 1][child]; + const SupportNode* pchild = child == -1 ? nullptr : contact_nodes[layer_idx - 1][child]; } TreeSupport3D::SupportElement* elem = &move_bounds[node2elemIdx[node]]; if (node->parent) { @@ -3168,110 +3173,144 @@ void TreeSupport::smooth_nodes(std::vector>& contact_n } #endif -std::vector TreeSupport::plan_layer_heights(std::vector> &contact_nodes) +std::vector TreeSupport::plan_layer_heights() { - const coordf_t max_layer_height = m_slicing_params.max_layer_height; - const coordf_t layer_height = m_object_config->layer_height.value; - coordf_t z_distance_top = m_slicing_params.gap_support_object; - // QDS: add extra distance if thick bridge is enabled - // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height - if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { - z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(*m_print_config) - layer_height; - } - const size_t support_roof_layers = m_object_config->support_interface_top_layers.value; - const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; - std::vector layer_heights(contact_nodes.size()); - std::vector bounds; - - if (layer_height == max_layer_height || !m_support_params.independent_layer_height) { - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, layer_nr > 0 ? size_t(layer_nr - 1) : 0}; + std::vector layer_heights; + std::map z_heights; // print_z:height + if (!m_support_params.independent_layer_height) { + layer_heights.resize(m_object->layer_count()); + for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { + z_heights[m_object->get_layer(layer_nr)->print_z] = m_object->get_layer(layer_nr)->height; + layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, size_t(layer_nr)}; } - } - else { - bounds.push_back(0); + } else { + const coordf_t max_layer_height = m_slicing_params.max_suport_layer_height; + const coordf_t min_layer_height = m_slicing_params.min_layer_height; + std::map bounds; // print_z: height // Keep first layer still - layer_heights[0] = { m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0 }; + bounds[m_object->get_layer(0)->print_z] = {m_object->get_layer(0)->height}; + std::vector obj_layer_zs; + obj_layer_zs.reserve(m_object->layer_count()); + for (const Layer *l : m_object->layers()) obj_layer_zs.emplace_back((float) l->print_z); + z_heights[m_object->get_layer(0)->print_z] = m_object->get_layer(0)->height; // Collect top contact layers for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { if (!contact_nodes[layer_nr].empty()) { - bounds.push_back(layer_nr); - layer_heights[layer_nr].print_z = contact_nodes[layer_nr].front()->print_z; - layer_heights[layer_nr].height = contact_nodes[layer_nr].front()->height; - BOOST_LOG_TRIVIAL(trace) << "plan_layer_heights0 print_z, height, layer_nr: " << layer_heights[layer_nr].print_z << " " << layer_heights[layer_nr].height << " " - << layer_nr; + coordf_t print_z = contact_nodes[layer_nr].front()->print_z; + coordf_t height = contact_nodes[layer_nr].front()->height; + // insertion will fail if there is already a key of print_z, so no need to check + bounds.insert({print_z, height}); + bounds.insert({print_z - height, 0}); // the bottom_z of the layer } } - std::set s(bounds.begin(), bounds.end()); - bounds.assign(s.begin(), s.end()); - for (size_t idx_extreme = 1; idx_extreme < bounds.size(); idx_extreme++) { - int extr2_layer_nr = bounds[idx_extreme]; - coordf_t extr2z = layer_heights[extr2_layer_nr].bottom_z(); - int extr1_layer_nr = bounds[idx_extreme - 1]; - coordf_t extr1z = layer_heights[extr1_layer_nr].print_z; - coordf_t dist = extr2z - extr1z; + auto it1 = bounds.begin(); + auto it2 = std::next(it1); + for (; it2 != bounds.end(); it2++) { + coordf_t z2 = it2->first; + coordf_t h2 = it2->second; + coordf_t z1 = it1->first; + coordf_t h1 = it1->second; + coordf_t dist = z2 - z1; + if (dist < min_layer_height - EPSILON) continue; + + BOOST_LOG_TRIVIAL(trace) << format("plan_layer_heights0 (%.2f,%.2f)->(%.2f,%.2f): ", z1, h1, z2, h2); // Insert intermediate layers. - size_t n_layers_extra = size_t(ceil(dist / (m_slicing_params.max_suport_layer_height + EPSILON))); - int actual_internel_layers = extr2_layer_nr - extr1_layer_nr - 1; - int extr_layers_left = extr2_layer_nr - extr1_layer_nr - n_layers_extra - 1; - if (n_layers_extra < 1) - continue; - + size_t n_layers_extra = size_t(ceil(dist / max_layer_height)); coordf_t step = dist / coordf_t(n_layers_extra); - coordf_t print_z = extr1z + step; - //assert(step >= layer_height - EPSILON); - coordf_t extr2z_large_steps = extr2z; - for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) { - // if (curr_layer_nodes.empty()) continue; - if (std::abs(print_z - m_object->get_layer(layer_nr)->print_z) < step / 2 + EPSILON || extr_layers_left < 1) { - layer_heights[layer_nr].print_z = print_z; - layer_heights[layer_nr].height = step; - print_z += step; - } - else { - // can't clear curr_layer_nodes, or the model will have empty layers - layer_heights[layer_nr].print_z = 0.0; - layer_heights[layer_nr].height = 0.0; - extr_layers_left--; - } + coordf_t print_z = z1 + step; + for (int i = 0; i < n_layers_extra; i++, print_z += step) { + z_heights[print_z] = step; + BOOST_LOG_TRIVIAL(debug) << "plan_layer_heights add entry print_z, height: " << print_z << " " << step; } + it1=it2; } - // fill in next_layer_nr - int i = layer_heights.size() - 1, j = i; - for (; i >= 0; i--) { - if (layer_heights[i].height < EPSILON) { - continue; + // map z_heights to layer_heights + int i = 0; + size_t obj_layer_nr = 0; + layer_heights.resize(z_heights.size()); + for (auto it = z_heights.begin(); it != z_heights.end(); it++, i++) { + coordf_t print_z = it->first; + coordf_t height = it->second; + while (obj_layer_nr < obj_layer_zs.size() && obj_layer_zs[obj_layer_nr] < print_z - height / 2) obj_layer_nr++; + layer_heights[i] = {print_z, height, obj_layer_nr}; + + } + } + + // add support layers according to layer_heights + int support_layer_nr = m_raft_layers; + for (size_t i = 0; i < layer_heights.size(); i++, support_layer_nr++) { + SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z); + if (ts_layer->id() > m_raft_layers) { + SupportLayer *lower_layer = m_object->get_support_layer(ts_layer->id() - 1); + if (lower_layer) { + lower_layer->upper_layer = ts_layer; + ts_layer->lower_layer = lower_layer; } - for (j = i - 1; j >= 0; j--) { - if (layer_heights[j].height > EPSILON && layer_heights[j].print_z0.01) // there is a gap more than 0.01mm, increase the top z distance to fill the gap - layer_heights[i].height = layer_heights[i].print_z - layer_heights[j].print_z; - break; - } + } + } + + + // re-distribute contact_nodes to support layers + decltype(contact_nodes) contact_nodes2(support_layer_nr); + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + if (contact_nodes[layer_nr].empty()) continue; + SupportNode *node1 = contact_nodes[layer_nr].front(); + auto it = z_heights.lower_bound(node1->print_z + EPSILON); + if (it == z_heights.end()) it = std::prev(it); + int layer_nr2 = std::distance(z_heights.begin(), it); + contact_nodes2[layer_nr2].insert(contact_nodes2[layer_nr2].end(), contact_nodes[layer_nr].begin(), contact_nodes[layer_nr].end()); + } + contact_nodes = contact_nodes2; + + // adjust contact nodes' distance_to_top and support_roof_layers_below according to layer_heights + // In case of very large top z distance, one gap layer is not enough, we need to split it into multiple layers + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + if (contact_nodes[layer_nr].empty()) continue; + SupportNode *node1 = contact_nodes[layer_nr].front(); + BOOST_LOG_TRIVIAL(debug) << format("plan_layer_heights node1->layer_nr,printz,height,distance_to_top: %d, %.2f,%.2f, %d", layer_nr, node1->print_z, node1->height, node1->distance_to_top) + << ", object_layer_zs[" << layer_heights[layer_nr].obj_layer_nr << "]=" << m_object->get_layer(layer_heights[layer_nr].obj_layer_nr)->print_z; + coordf_t new_height = layer_heights[layer_nr].height; + if (std::abs(node1->height - new_height) < EPSILON) continue; + if (top_z_distance < EPSILON && node1->height < EPSILON) continue; // top_z_distance==0, this is soluable interface + coordf_t accum_height = 0; + int num_layers = 0; + for (int i=layer_nr;i>=0;i--){ + if (layer_heights[i].height > EPSILON) { + accum_height += layer_heights[i].height; + num_layers++; + if (accum_height > node1->height - EPSILON) break; } } + BOOST_LOG_TRIVIAL(debug) << format("plan_layer_heights adjust node's height %d %.2f: (%.3f,%d)->(%.3f,%.3f,%d)", layer_nr, node1->print_z, + node1->height, node1->distance_to_top, new_height, accum_height, -num_layers); + for (SupportNode *node : contact_nodes[layer_nr]) { + node->height = new_height; + node->distance_to_top = -num_layers; + node->support_roof_layers_below += num_layers - 1; + } } // log layer_heights for (size_t i = 0; i < layer_heights.size(); i++) { - if (layer_heights[i].height > EPSILON) - BOOST_LOG_TRIVIAL(trace) << "plan_layer_heights print_z, height, lower_layer_nr->layer_nr: " << layer_heights[i].print_z << " " << layer_heights[i].height << " " - << layer_heights[i].next_layer_nr << "->" << i; + //if (layer_heights[i].height > EPSILON) + BOOST_LOG_TRIVIAL(trace) << format("plan_layer_heights[%d] print_z, height: %.2f, %.2f",i, layer_heights[i].print_z, layer_heights[i].height); } return layer_heights; } -void TreeSupport::generate_contact_points(std::vector>& contact_nodes) +void TreeSupport::generate_contact_points() { const PrintObjectConfig &config = m_object->config(); const coordf_t point_spread = scale_(config.tree_support_branch_distance.value); const coordf_t max_bridge_length = scale_(config.max_bridge_length.value); - coord_t radius = scale_(config.tree_support_branch_diameter.value / 2); - radius = std::max(m_min_radius, radius); + coord_t radius_scaled = scale_(base_radius); + bool on_buildplate_only = m_object_config->support_on_build_plate_only.value; + const bool roof_enabled = config.support_interface_top_layers.value > 0; + const bool force_tip_to_roof = roof_enabled && m_support_params.soluble_interface; //First generate grid points to cover the entire area of the print. BoundingBox bounding_box = m_object->bounding_box(); @@ -3299,20 +3338,17 @@ void TreeSupport::generate_contact_points(std::vector> } const coordf_t layer_height = config.layer_height.value; - coordf_t z_distance_top = m_slicing_params.gap_support_object; - if (!m_support_params.independent_layer_height) { - z_distance_top = round(z_distance_top / layer_height) * layer_height; - // QDS: add extra distance if thick bridge is enabled - // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height - if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { - z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; - } - } + coordf_t z_distance_top = this->top_z_distance; + // if (!m_support_params.independent_layer_height) { + // z_distance_top = round(z_distance_top / layer_height) * layer_height; + // // QDS: add extra distance if thick bridge is enabled + // // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height + // if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { + // z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; + //} + // } const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; //Support must always be 1 layer below overhang. int gap_layers = z_distance_top == 0 ? 0 : 1; - // virtual layer with 0 height will be deleted - if (z_distance_top == 0) - z_distance_top = 0.001; size_t support_roof_layers = config.support_interface_top_layers.value; if (support_roof_layers > 0) @@ -3323,8 +3359,26 @@ void TreeSupport::generate_contact_points(std::vector> // fix bug of generating support for very thin objects if (m_object->layers().size() <= z_distance_top_layers + 1) return; - //if (m_object->support_layer_count() <= m_raft_layers) - // return; + + contact_nodes.clear(); + contact_nodes.resize(m_object->layers().size()); + + tbb::spin_mutex mtx; + + // add vertical enforcer points + std::vector zs = zs_from_layers(m_object->layers()); + std::vector>> vertical_enforcer_points_by_layers(m_object->layer_count()); + for (auto& pt_and_normal : m_vertical_enforcer_points) { + auto pt = pt_and_normal.first; + auto normal = pt_and_normal.second; // normal seems useless + auto iter = std::lower_bound(zs.begin(), zs.end(), pt.z()); + if (iter != zs.end()) { + size_t layer_nr = iter - zs.begin(); + if (layer_nr > 0 && layer_nr < contact_nodes.size()) { + vertical_enforcer_points_by_layers[layer_nr].push_back({ to_2d(pt).cast(),scaled(to_2d(normal)) }); + } + } + } int nonempty_layers = 0; tbb::concurrent_vector all_nodes; @@ -3333,37 +3387,35 @@ void TreeSupport::generate_contact_points(std::vector> if (m_object->print()->canceled()) break; Layer* layer = m_object->get_layer(layer_nr); - auto& curr_nodes = contact_nodes[layer_nr - 1]; - if (layer->loverhangs.empty()) continue; + auto& curr_nodes = contact_nodes[layer_nr-1]; std::unordered_set already_inserted; - auto print_z = m_object->get_layer(layer_nr)->print_z; auto bottom_z = m_object->get_layer(layer_nr)->bottom_z(); - auto height = m_object->get_layer(layer_nr)->height; bool added = false; // Did we add a point this way? bool is_sharp_tail = false; - auto insert_point = [&](Point pt, const ExPolygon& overhang_part, bool force_add = false) { - Point hash_pos = pt / ((radius + 1) / 1); + // take the least restrictive avoidance possible + ExPolygons relevant_forbidden = offset_ex(m_ts_data->m_layer_outlines[layer_nr - 1], scale_(MIN_BRANCH_RADIUS)); + // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + relevant_forbidden = offset_ex(union_ex(relevant_forbidden), scaled(0.005), jtMiter, 1.2); + + + auto insert_point = [&](Point pt, const ExPolygon& overhang, double radius, bool force_add = false, bool add_interface=true) { + Point hash_pos = pt / ((radius_scaled + 1) / 1); SupportNode* contact_node = nullptr; if (force_add || !already_inserted.count(hash_pos)) { already_inserted.emplace(hash_pos); bool to_buildplate = true; + size_t roof_layers = add_interface ? support_roof_layers : 0; // add a new node as a virtual node which acts as the invisible gap between support and object // distance_to_top=-1: it's virtual // print_z=object_layer->bottom_z: it directly contacts the bottom // height=z_distance_top: it's height is exactly the gap distance // dist_mm_to_top=0: it directly contacts the bottom - contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr - 1, support_roof_layers + 1, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0, - unscale_(radius)); - contact_node->overhang = overhang_part; + contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers + 1, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0, + radius); + contact_node->overhang = overhang; contact_node->is_sharp_tail = is_sharp_tail; - if (is_sharp_tail) { - int ind = overhang_part.contour.closest_point_index(pt); - auto n1 = (overhang_part.contour[ind] - overhang_part.contour[ind - 1]).cast().normalized(); - auto n2 = (overhang_part.contour[ind] - overhang_part.contour[ind + 1]).cast().normalized(); - contact_node->skin_direction = scaled((n1 + n2).normalized()); - } curr_nodes.emplace_back(contact_node); added = true; }; @@ -3373,73 +3425,92 @@ void TreeSupport::generate_contact_points(std::vector> for (const auto& overhang_part : layer->loverhangs) { const auto& overhang_type = this->overhang_types[&overhang_part]; is_sharp_tail = overhang_type == OverhangType::SharpTail; - BoundingBox overhang_bounds = get_extents(overhang_part); - if (support_style == smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang && !is_sharp_tail) { - if (!overlaps({ overhang_part }, m_ts_data->m_layer_outlines_below[layer_nr - 1])) { - Point candidate = overhang_bounds.center(); - SupportNode* contact_node = insert_point(candidate, overhang_part, true); - contact_node->type = ePolygon; - contact_node->radius = unscale_(overhang_bounds.radius()); - curr_nodes.emplace_back(contact_node); - continue; + ExPolygons overhangs_regular; + if (m_support_params.support_style == smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang && !is_sharp_tail) { + overhangs_regular = offset_ex(intersection_ex({overhang_part}, m_ts_data->m_layer_outlines_below[layer_nr - 1]), radius_scaled); + ExPolygons overhangs_normal = diff_ex({overhang_part}, overhangs_regular); + if (area(overhangs_normal) > m_support_params.thresh_big_overhang) { + // if the outside area is still big, we can need normal nodes + for (auto &overhang : overhangs_normal) { + BoundingBox overhang_bounds = get_extents(overhang); + double radius = unscale_(overhang_bounds.radius()); + Point candidate = overhang_bounds.center(); + SupportNode *contact_node = insert_point(candidate, overhang, radius, true, true); + contact_node->type = ePolygon; + curr_nodes.emplace_back(contact_node); + } + }else{ + // otherwise, all nodes should be circle nodes + overhangs_regular = ExPolygons{overhang_part}; } + } else { + overhangs_regular = ExPolygons{overhang_part}; } - // add supports at corners for both auto and manual overhangs, github #2008 - { - auto& points = overhang_part.contour.points; + for (auto &overhang : overhangs_regular) { + bool add_interface = (force_tip_to_roof || area(overhang) > minimum_roof_area) && !is_sharp_tail; + BoundingBox overhang_bounds = get_extents(overhang); + double radius = std::clamp(unscale_(overhang_bounds.radius()), MIN_BRANCH_RADIUS, base_radius); + // add supports at corners for both auto and manual overhangs, github #2008 + auto &points = overhang.contour.points; int nSize = points.size(); for (int i = 0; i < nSize; i++) { auto pt = points[i]; auto v1 = (pt - points[(i - 1 + nSize) % nSize]).cast().normalized(); auto v2 = (pt - points[(i + 1) % nSize]).cast().normalized(); if (v1.dot(v2) > -0.7) { // angle smaller than 135 degrees - SupportNode* contact_node = insert_point(pt, overhang_part); + SupportNode *contact_node = insert_point(pt, overhang, radius, false, add_interface); if (contact_node) { contact_node->is_corner = true; } } } - } // add supports along contours - libnest2d::placers::EdgeCache edge_cache(overhang_part); + libnest2d::placers::EdgeCache edge_cache(overhang); for (size_t i = 0; i < edge_cache.holeCount() + 1; i++) { - double step = point_spread / (i == 0 ? edge_cache.circumference() : edge_cache.circumference(i - 1)); + double step = point_spread / (i == 0 ? edge_cache.circumference() : edge_cache.circumference(i - 1)); double distance = 0; while (distance < 1) { - auto pt = i == 0 ? edge_cache.coords(distance) : edge_cache.coords(i - 1, distance); - SupportNode* contact_node = insert_point(pt, overhang_part); + auto pt = i == 0 ? edge_cache.coords(distance) : edge_cache.coords(i - 1, distance); + SupportNode *contact_node = insert_point(pt, overhang,radius, false, add_interface); distance += step; } } // don't add inner supports for sharp tails - if (is_sharp_tail) - continue; + if (is_sharp_tail) continue; // add inner supports - overhang_bounds.inflated(-radius); - ExPolygons overhang_inner = offset_ex(overhang_part, -radius); + overhang_bounds.inflated(-radius_scaled); + ExPolygons overhang_inner = offset_ex(overhang, -radius_scaled); for (Point candidate : grid_points) { if (overhang_bounds.contains(candidate)) { // QDS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported! - bool is_inside = is_inside_ex(overhang_inner, candidate); - if (is_inside) { - SupportNode* contact_node = insert_point(candidate, overhang_part); - } + bool is_inside = is_inside_ex(overhang_inner, candidate); + if (is_inside) { SupportNode *contact_node = insert_point(candidate, overhang,radius, false, add_interface); } } } - + } + } + for (auto& pt_and_normal : vertical_enforcer_points_by_layers[layer_nr]) { + is_sharp_tail = true;// fake it as sharp tail point so the contact distance will be 0 + auto vertical_enforcer_point= pt_and_normal.first; + auto node=insert_point(vertical_enforcer_point, ExPolygon(), false); + if (node) + node->skin_direction = pt_and_normal.second; } if (!curr_nodes.empty()) nonempty_layers++; for (auto node : curr_nodes) { all_nodes.emplace_back(node->position(0), node->position(1), scale_(node->print_z)); } #ifdef SUPPORT_TREE_DEBUG_TO_SVG - draw_contours_and_nodes_to_svg(debug_out_path("init_contact_points_%.2f.svg", print_z), layer->loverhangs,layer->lslices, m_ts_data->m_layer_outlines_below[layer_nr], + draw_contours_and_nodes_to_svg(debug_out_path("init_contact_points_%.2f.svg", bottom_z), layer->loverhangs,layer->lslices_extrudable, m_ts_data->m_layer_outlines_below[layer_nr], contact_nodes[layer_nr], contact_nodes[layer_nr - 1], { "overhang","lslices","outlines_below"}); #endif }} ); // end tbb::parallel_for + + + int nNodes = all_nodes.size(); avg_node_per_layer = nodes_angle = 0; if (nNodes > 0) { @@ -3457,7 +3528,7 @@ void TreeSupport::generate_contact_points(std::vector> mx2 += x * x; } nodes_angle = atan2(nNodes * mxy - mx * my, nNodes * mx2 - SQ(mx)); - + BOOST_LOG_TRIVIAL(info) << "avg_node_per_layer=" << avg_node_per_layer << ", nodes_angle=" << nodes_angle; } } @@ -3476,13 +3547,16 @@ void TreeSupport::insert_dropped_node(std::vector& nodes_layer, Su conflicting_node->support_roof_layers_below = std::max(conflicting_node->support_roof_layers_below, p_node->support_roof_layers_below); } -TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance, coordf_t max_move, coordf_t radius_sample_resolution) - : m_xy_distance(xy_distance), m_max_move(max_move), m_radius_sample_resolution(radius_sample_resolution) +TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance, coordf_t radius_sample_resolution) + : m_xy_distance(xy_distance), m_radius_sample_resolution(radius_sample_resolution) { + branch_scale_factor = tan(object.config().tree_support_branch_angle.value * M_PI / 180.); clear_nodes(); + m_max_move_distances.resize(object.layers().size(), 0); for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr) { const Layer* layer = object.get_layer(layer_nr); + m_max_move_distances[layer_nr] = layer->height * branch_scale_factor; m_layer_outlines.push_back(ExPolygons()); ExPolygons& outline = m_layer_outlines.back(); for (const ExPolygon& poly : layer->lslices) { @@ -3543,35 +3617,21 @@ SupportNode* TreeSupportData::create_node(const Point position, const int distan { // this function may be called from multiple threads, need to lock m_mutex.lock(); - SupportNode* node = new SupportNode(position, distance_to_top, obj_layer_nr, support_roof_layers_below, to_buildplate, parent, print_z_, height_, dist_mm_to_top_, radius_); - contact_nodes.emplace_back(node); + std::unique_ptr node = std::make_unique(position, distance_to_top, obj_layer_nr, support_roof_layers_below, to_buildplate, parent, print_z_, height_, dist_mm_to_top_, radius_); + SupportNode* raw_ptr = node.get(); + contact_nodes.emplace_back(std::move(node)); m_mutex.unlock(); if (parent) - node->movement = position - parent->position; - return node; + raw_ptr->movement = position - parent->position; + return raw_ptr; } void TreeSupportData::clear_nodes() { - for (auto node : contact_nodes) { - delete node; - } + tbb::spin_mutex::scoped_lock guard(m_mutex); contact_nodes.clear(); } -void TreeSupportData::remove_invalid_nodes() -{ - for (auto it = contact_nodes.begin(); it != contact_nodes.end();) { - if ((*it)->valid==false) { - delete (*it); - it = contact_nodes.erase(it); - } - else { - it++; - } - } -} - coordf_t TreeSupportData::ceil_radius(coordf_t radius) const { size_t factor = (size_t)(radius / m_radius_sample_resolution); @@ -3588,53 +3648,39 @@ const ExPolygons& TreeSupportData::calculate_collision(const RadiusLayerPair& ke { assert(key.layer_nr < m_layer_outlines.size()); - ExPolygons collision_areas = std::move(offset_ex(m_layer_outlines[key.layer_nr], scale_(key.radius))); + ExPolygons collision_areas = std::move(offset_ex(m_layer_outlines[key.layer_nr], scale_(key.radius+m_xy_distance))); collision_areas = expolygons_simplify(collision_areas, scale_(m_radius_sample_resolution)); + // collision_areas.emplace_back(m_machine_border); const auto ret = m_collision_cache.insert({ key, std::move(collision_areas) }); return ret.first->second; } const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& key) const { - const auto& radius = key.radius; - const auto& layer_nr = key.layer_nr; - BOOST_LOG_TRIVIAL(debug) << "calculate_avoidance on (radius,layer)= (" << radius << "," << layer_nr<<"), recursion="< 0) { // Avoidance for a given layer depends on all layers beneath it so could have very deep recursion depths if // called at high layer heights. We can limit the reqursion depth to N by checking if the layer N // below the current one exists and if not, forcing the calculation of that layer. This may cause another recursion // if the layer at 2N below the current one but we won't exceed our limit unless there are N*N uncalculated layers // below our current one. - size_t layer_nr_next = layer_nr; - int layers_below; - for (layers_below = 0; layers_below < max_recursion_depth && layer_nr_next > 0; layers_below++) { layer_nr_next = layer_heights[layer_nr_next].next_layer_nr; } + constexpr auto max_recursion_depth = 100; // Check if we would exceed the recursion limit by trying to process this layer - if (layers_below >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr_next}) == m_avoidance_cache.end()) { + if (layer_nr >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr - max_recursion_depth}) == m_avoidance_cache.end()) { // Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result. - get_avoidance(radius, layer_nr_next, key.recursions + 1); + get_avoidance(radius, layer_nr - max_recursion_depth); } - layer_nr_next = layer_heights[layer_nr].next_layer_nr; - ExPolygons avoidance_areas = std::move(offset_ex(get_avoidance(radius, layer_nr_next, key.recursions+1), scale_(-m_max_move))); - const ExPolygons &collision = get_collision(radius, layer_nr); - avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end()); - avoidance_areas = std::move(union_ex(avoidance_areas)); - auto ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)}); - //assert(ret.second); - return ret.first->second; - } else { - BOOST_LOG_TRIVIAL(debug) << "calculate_avoidance exceeds max_recursion_depth*2 on radius=" << radius << ", layer=" << layer_nr << ", recursion=" << key.recursions; - ExPolygons avoidance_areas = get_collision(radius, layer_nr);// std::move(offset_ex(m_layer_outlines_below[layer_nr], scale_(m_xy_distance + radius))); - auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) }); - assert(ret.second); - return ret.first->second; + avoidance_areas = std::move(offset_ex(get_avoidance(radius, layer_nr - 1), scale_(-m_max_move_distances[layer_nr-1]))); } + const ExPolygons &collision = get_collision(radius, layer_nr); + avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end()); + avoidance_areas = std::move(union_ex(avoidance_areas)); + auto ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)}); + //assert(ret.second); + return ret.first->second; } } //namespace Slic3r diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index c8ea31f..d2aaad0 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -28,9 +28,9 @@ struct LayerHeightData { coordf_t print_z = 0; coordf_t height = 0; - size_t next_layer_nr = 0; + size_t obj_layer_nr = 0; LayerHeightData() = default; - LayerHeightData(coordf_t z, coordf_t h, size_t next_layer) : print_z(z), height(h), next_layer_nr(next_layer) {} + LayerHeightData(coordf_t z, coordf_t h, size_t obj_layer) : print_z(z), height(h), obj_layer_nr(obj_layer) {} coordf_t bottom_z() { return print_z - height; } @@ -202,7 +202,7 @@ public: * \param radius_sample_resolution Sample size used to round requested node radii. * \param collision_resolution */ - TreeSupportData(const PrintObject& object, coordf_t max_move, coordf_t radius_sample_resolution, coordf_t collision_resolution); + TreeSupportData(const PrintObject& object, coordf_t radius_sample_resolution, coordf_t collision_resolution); ~TreeSupportData() { clear_nodes(); } @@ -247,10 +247,10 @@ public: SupportNode* create_node(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, SupportNode* parent, coordf_t print_z_, coordf_t height_, coordf_t dist_mm_to_top_ = 0, coordf_t radius_ = 0); void clear_nodes(); - void remove_invalid_nodes(); std::vector layer_heights; - std::vector contact_nodes; + std::vector> contact_nodes; + // ExPolygon m_machine_border; private: /*! @@ -260,7 +260,7 @@ private: coordf_t radius; size_t layer_nr; int recursions; - + }; struct RadiusLayerPairEquality { constexpr bool operator()(const RadiusLayerPair& _Left, const RadiusLayerPair& _Right) const { @@ -296,7 +296,7 @@ private: */ const ExPolygons& calculate_avoidance(const RadiusLayerPair& key) const; - tbb::spin_mutex m_mutex; + tbb::spin_mutex m_mutex; public: bool is_slim = false; @@ -305,11 +305,7 @@ public: */ coordf_t m_xy_distance; - /*! - * \brief The maximum distance that the centrepoint of a tree branch may - * move in consequtive layers - */ - coordf_t m_max_move; + double branch_scale_factor = 1.0; // tan(45 degrees) /*! * \brief Sample resolution for radius values. @@ -328,6 +324,8 @@ public: // union contours of all layers below std::vector m_layer_outlines_below; + std::vector m_max_move_distances; + /*! * \brief Caches for the collision, avoidance and internal model polygons * at given radius and layer indices. @@ -336,7 +334,7 @@ public: * generally considered OK as the functions are still logically const * (ie there is no difference in behaviour for the user betweeen * calculating the values each time vs caching the results). - * + * * coconut: previously stl::unordered_map is used which seems problematic with tbb::parallel_for. * So we change to tbb::concurrent_unordered_map */ @@ -366,6 +364,10 @@ public: */ TreeSupport(PrintObject& object, const SlicingParameters &slicing_params); + void move_bounds_to_contact_nodes(std::vector &move_bounds, + PrintObject &print_object, + const TreeSupport3D::TreeSupportSettings &config); + /*! * \brief Create the areas that need support. * @@ -377,13 +379,26 @@ public: void detect_overhangs(bool check_support_necessity = false); + SupportNode* create_node(const Point position, + const int distance_to_top, + const int obj_layer_nr, + const int support_roof_layers_below, + const bool to_buildplate, + SupportNode* parent, + coordf_t print_z_, + coordf_t height_, + coordf_t dist_mm_to_top_ = 0, + coordf_t radius_ = 0) + { + return m_ts_data->create_node(position, distance_to_top, obj_layer_nr, support_roof_layers_below, to_buildplate, parent, print_z_, height_, dist_mm_to_top_, radius_); + } + int avg_node_per_layer = 0; - float nodes_angle = 0; + float nodes_angle = 0; bool has_sharp_tails = false; bool has_cantilever = false; double max_cantilever_dist = 0; SupportType support_type; - SupportMaterialStyle support_style; std::unique_ptr generator; std::unordered_map printZ_to_lightninglayer; @@ -398,6 +413,8 @@ public: enum OverhangType { Detected = 0, Enforced, SharpTail }; std::map overhang_types; + std::vector> m_vertical_enforcer_points; + private: /*! * \brief Generator for model collision, avoidance and internal guide volumes @@ -405,6 +422,7 @@ private: * Lazily computes volumes as needed. * \warning This class is NOT currently thread-safe and should not be accessed in OpenMP blocks */ + std::vector> contact_nodes; std::shared_ptr m_ts_data; std::unique_ptr m_model_volumes; PrintObject *m_object; @@ -417,14 +435,17 @@ private: size_t m_highest_overhang_layer = 0; std::vector> m_spanning_trees; std::vector< std::unordered_map> m_mst_line_x_layer_contour_caches; - float DO_NOT_MOVER_UNDER_MM = 0.0; - coordf_t MAX_BRANCH_RADIUS = 10.0; - coordf_t MIN_BRANCH_RADIUS = 0.5; - coordf_t MAX_BRANCH_RADIUS_FIRST_LAYER = 12.0; - coordf_t MIN_BRANCH_RADIUS_FIRST_LAYER = 2.0; - float tree_support_branch_diameter_angle = 5.0; - coord_t m_min_radius = scale_(1); // in mm + coordf_t base_radius = 0.0; + const coordf_t MAX_BRANCH_RADIUS = 10.0; + const coordf_t MIN_BRANCH_RADIUS = 0.4; + const coordf_t MAX_BRANCH_RADIUS_FIRST_LAYER = 12.0; + const coordf_t MIN_BRANCH_RADIUS_FIRST_LAYER = 2.0; + double diameter_angle_scale_factor = tan(5.0*M_PI/180.0); + // minimum roof area (1 mm^2), area smaller than this value will not have interface + const double minimum_roof_area{SQ(scaled(1.))}; + float top_z_distance = 0.0; + bool is_strong = false; bool is_slim = false; bool with_infill = false; @@ -441,7 +462,7 @@ private: * save the resulting support polygons to. * \param contact_nodes The nodes to draw as support. */ - void draw_circles(const std::vector>& contact_nodes); + void draw_circles(); /*! * \brief Drops down the nodes of the tree support towards the build plate. @@ -455,18 +476,18 @@ private: * dropped down. The nodes are dropped to lower layers inside the same * vector of layers. */ - void drop_nodes(std::vector> &contact_nodes); + void drop_nodes(); - void smooth_nodes(std::vector> &contact_nodes); + void smooth_nodes(); - void smooth_nodes(std::vector>& contact_nodes, const TreeSupport3D::TreeSupportSettings& config); + void smooth_nodes(const TreeSupport3D::TreeSupportSettings& config); /*! QDS: MusangKing: maximum layer height * \brief Optimize the generation of tree support by pre-planning the layer_heights - * + * */ - std::vector plan_layer_heights(std::vector> &contact_nodes); + std::vector plan_layer_heights(); /*! * \brief Creates points where support contacts the model. * @@ -480,7 +501,7 @@ private: * \return For each layer, a list of points where the tree should connect * with the model. */ - void generate_contact_points(std::vector>& contact_nodes); + void generate_contact_points(); /*! * \brief Add a node to the next layer. @@ -494,8 +515,10 @@ private: coordf_t calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor); // get unscaled radius(mm) of node based on the distance mm to top coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor, bool use_min_distance=true); - coordf_t get_radius(const SupportNode* node, coordf_t base_radius); + coordf_t calc_radius(coordf_t mm_to_top); + coordf_t get_radius(const SupportNode* node); ExPolygons get_avoidance(coordf_t radius, size_t obj_layer_nr); + // layer's expolygon expanded by radius+m_xy_distance ExPolygons get_collision(coordf_t radius, size_t layer_nr); // get Polygons instead of ExPolygons Polygons get_collision_polys(coordf_t radius, size_t layer_nr); diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index 6e1714c..df9c869 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -19,7 +19,7 @@ #include "Polygon.hpp" #include "Polyline.hpp" #include "MutablePolygon.hpp" -#include "SupportMaterial.hpp" +#include "SupportCommon.hpp" #include "TriangleMeshSlicer.hpp" #include "TreeSupport.hpp" #include "I18N.hpp" @@ -37,6 +37,7 @@ #define TBB_PREVIEW_GLOBAL_CONTROL 1 #include #include +#include #include #if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32) @@ -63,16 +64,6 @@ namespace Slic3r namespace TreeSupport3D { -enum class LineStatus -{ - INVALID, - TO_MODEL, - TO_MODEL_GRACIOUS, - TO_MODEL_GRACIOUS_SAFE, - TO_BP, - TO_BP_SAFE -}; - using LineInformation = std::vector>; using LineInformations = std::vector; using namespace std::literals; @@ -80,28 +71,28 @@ using namespace std::literals; static inline void validate_range(const Point &pt) { static constexpr const int32_t hi = 65536 * 16384; - if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi) - throw ClipperLib::clipperException("Coordinate outside allowed range"); + if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi) + throw ClipperLib::clipperException("Coordinate outside allowed range"); } -static inline void validate_range(const Points &points) +static inline void validate_range(const Points &points) { for (const Point &p : points) validate_range(p); } -static inline void validate_range(const MultiPoint &mp) +static inline void validate_range(const MultiPoint &mp) { validate_range(mp.points); } -static inline void validate_range(const Polygons &polygons) +static inline void validate_range(const Polygons &polygons) { for (const Polygon &p : polygons) validate_range(p); } -static inline void validate_range(const Polylines &polylines) +static inline void validate_range(const Polylines &polylines) { for (const Polyline &p : polylines) validate_range(p); @@ -150,7 +141,7 @@ static std::vector>> group_me size_t largest_printed_mesh_idx = 0; - // Group all meshes that can be processed together. NOTE this is different from mesh-groups! Only one setting object is needed per group, + // Group all meshes that can be processed together. NOTE this is different from mesh-groups! Only one setting object is needed per group, // as different settings in the same group may only occur in the tip, which uses the original settings objects from the meshes. for (size_t object_id : print_object_ids) { const PrintObject &print_object = *print.get_object(object_id); @@ -249,7 +240,7 @@ static std::vector>> group_me size_t num_overhang_layers = support_auto ? num_object_layers : std::min(num_object_layers, std::max(size_t(support_enforce_layers), enforcers_layers.size())); tbb::parallel_for(tbb::blocked_range(1, num_overhang_layers), - [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, + [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, radius_sample_resolution, &throw_on_cancel, &out] (const tbb::blocked_range &range) { for (LayerIndex layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { @@ -284,7 +275,7 @@ static std::vector>> group_me overhangs = diff(overhangs, offset_ex(union_(blockers_layers[layer_id]), scale_(radius_sample_resolution)), ApplySafetyOffset::Yes); //if (config.bridge_no_support) { // for (const LayerRegion *layerm : current_layer.regions()) - // remove_bridges_from_contacts(print_config, lower_layer, *layerm, + // remove_bridges_from_contacts(print_config, lower_layer, *layerm, // float(layerm->flow(frExternalPerimeter).scaled_width()), overhangs); //} } @@ -305,7 +296,7 @@ static std::vector>> group_me enforced_overhangs = diff(offset(union_ex(enforced_overhangs), enforcer_overhang_offset), lower_layer.lslices); #ifdef TREESUPPORT_DEBUG_SVG -// if (! intersecting_edges(enforced_overhangs).empty()) +// if (! intersecting_edges(enforced_overhangs).empty()) { static int irun = 0; SVG::export_expolygons(debug_out_path("treesupport-self-intersections-%d.svg", ++irun), @@ -318,7 +309,7 @@ static std::vector>> group_me overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs); //check_self_intersections(overhangs, "generate_overhangs - enforcers"); } - } + } out[layer_id + num_raft_layers] = std::move(overhangs); throw_on_cancel(); } @@ -350,6 +341,28 @@ static std::vector>> group_me return max_layer; } +// picked from convert_lines_to_internal() +[[nodiscard]] LineStatus get_avoidance_status(const Point& p, coord_t radius, LayerIndex layer_idx, + const TreeModelVolumes& volumes, const TreeSupportSettings& config) +{ + const bool min_xy_dist = config.xy_distance > config.xy_min_distance; + + LineStatus type = LineStatus::INVALID; + + if (!contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, false, min_xy_dist), p)) + type = LineStatus::TO_BP_SAFE; + else if (!contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::Fast, false, min_xy_dist), p)) + type = LineStatus::TO_BP; + else if (config.support_rests_on_model && !contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, true, min_xy_dist), p)) + type = LineStatus::TO_MODEL_GRACIOUS_SAFE; + else if (config.support_rests_on_model && !contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::Fast, true, min_xy_dist), p)) + type = LineStatus::TO_MODEL_GRACIOUS; + else if (config.support_rests_on_model && !contains(volumes.getCollision(radius, layer_idx, min_xy_dist), p)) + type = LineStatus::TO_MODEL; + + return type; +} + /*! * \brief Converts a Polygons object representing a line into the internal format. * @@ -431,7 +444,7 @@ static std::vector>> group_me return true; if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) return ! contains( - p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? + p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, true, min_xy_dist) : volumes.getCollision(config.getRadius(0), current_layer - 1, min_xy_dist), p.first); @@ -503,7 +516,7 @@ static std::optional> polyline_sample_next_point_at_dis // Squared distance of "start_pt" from the ray (p0, p1). double l2_from_line = xf.squaredNorm(); // Squared distance of an intersection point of a circle with center at the foot point. - if (double l2_intersection = dist2 - l2_from_line; + if (double l2_intersection = dist2 - l2_from_line; l2_intersection > - SCALED_EPSILON) { // The ray (p0, p1) touches or intersects a circle centered at "start_pt" with radius "dist". // Distance of the circle intersection point from the foot point. @@ -598,7 +611,7 @@ static std::optional> polyline_sample_next_point_at_dis } else { if (current_point == next_point->first) { // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... - BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << + BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << ") is smaller than 100"; tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing."sv, true); if (next_distance > 2 * current_distance) @@ -663,9 +676,9 @@ static std::optional> polyline_sample_next_point_at_dis int divisor = static_cast(angles.size()); int index = ((layer_idx % divisor) + divisor) % divisor; const AngleRadians fill_angle = angles[index]; - Infill roof_computation(pattern, true /* zig_zaggify_infill */, connect_polygons, polygon, - roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, - fill_angle, z, support_shift, config.resolution, wall_line_count, infill_origin, + Infill roof_computation(pattern, true /* zig_zaggify_infill */, connect_polygons, polygon, + roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, + fill_angle, z, support_shift, config.resolution, wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, false /* skip_some_zags */, zag_skip_count, pocket_size); Polygons polygons; Polygons lines; @@ -679,7 +692,7 @@ static std::optional> polyline_sample_next_point_at_dis filler->layer_id = layer_idx; filler->spacing = flow.spacing(); - filler->angle = roof ? + filler->angle = roof ? //fixme support_layer.interface_id() instead of layer_idx (support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) : support_params.base_angle; @@ -750,7 +763,7 @@ static std::optional> polyline_sample_next_point_at_dis result = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); } } - + return result; } @@ -767,7 +780,7 @@ static std::optional> polyline_sample_next_point_at_dis { bool do_final_difference = last_step_offset_without_check == 0; Polygons ret = safe_union(me); // ensure sane input - + // Trim the collision polygons with the region of interest for diff() efficiency. Polygons collision_trimmed_buffer; auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { @@ -846,7 +859,7 @@ public: // called by sample_overhang_area() void add_points_along_lines( // Insert points (tree tips or top contact interfaces) along these lines. - LineInformations lines, + LineInformations lines, // Start at this layer. LayerIndex insert_layer_idx, // Insert this number of interface layers. @@ -886,7 +899,7 @@ public: // add all points that would not be valid for (const LineInformation &line : points) for (const std::pair &point_data : line) - add_point_as_influence_area(point_data, this_layer_idx, + add_point_as_influence_area(point_data, this_layer_idx, // don't move until roof_tip_layers - dtt_roof_tip, // supports roof @@ -973,7 +986,7 @@ private: // Temps static constexpr const auto m_base_radius = scaled(0.01); const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; - + // Mutexes, guards std::mutex m_mutex_movebounds; std::vector> m_already_inserted; @@ -1058,10 +1071,10 @@ void finalize_raft_contact( // Produce // 1) Maximum num_support_roof_layers roof (top interface & contact) layers. // 2) Tree tips supporting either the roof layers or the object itself. -// num_support_roof_layers should always be respected: +// num_support_roof_layers should always be respected: // If num_support_roof_layers contact layers could not be produced, then the tree tip // is augmented with SupportElementState::missing_roof_layers -// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to +// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to // roofs aka interface layers by the tool path generator. void sample_overhang_area( // Area to support @@ -1073,18 +1086,18 @@ void sample_overhang_area( const size_t layer_idx, // Maximum number of roof (contact, interface) layers between the overhang and tree tips to be generated. const size_t num_support_roof_layers, - // + // const coord_t connect_length, // Configuration classes const TreeSupportMeshGroupSettings& mesh_group_settings, // Configuration & Output RichInterfacePlacer& interface_placer) { - // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area - // is still large enough to be a roof and aborted as soon as it is not. This part was already reworked a few times, and there could be an argument + // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area + // is still large enough to be a roof and aborted as soon as it is not. This part was already reworked a few times, and there could be an argument // made to change it again if there are actual issues encountered regarding supporting roofs. - // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, - // as the pattern may be different one layer below. Same with calculating which points are now no longer being generated as result from + // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, + // as the pattern may be different one layer below. Same with calculating which points are now no longer being generated as result from // a decreasing roof, as there is no guarantee that a line will be above these points. Implementing a separate roof support behavior // for each pattern harms maintainability as it very well could be >100 LOC auto generate_roof_lines = [&interface_placer, &mesh_group_settings](const Polygons& area, LayerIndex layer_idx) -> Polylines { @@ -1151,7 +1164,7 @@ void sample_overhang_area( if (overhang_lines.empty()) { // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, but not only is this the only reasonable choice, - // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. + // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. // This is not doen when a roof is above as the roof will support the model and the trees only need to support the roof bool supports_roof = dtt_roof > 0; bool continuous_tips = !supports_roof && large_horizontal_roof; @@ -1165,8 +1178,8 @@ void sample_overhang_area( const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length))); if (point_count <= min_support_points) { // add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width. - // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them - // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60 degrees so there is a fallback, + // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them + // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60 degrees so there is a fallback, // as some support is better than none. Polygons reduced_overhang_area = offset(union_ex(overhang_area), -interface_placer.config.support_line_width / 2.2, jtMiter, 1.2); polylines = ensure_maximum_distance_polyline( @@ -1201,15 +1214,6 @@ void sample_overhang_area( } } -inline SupportGeneratorLayer& layer_allocate( - SupportGeneratorLayerStorage& layer_storage, - SupporLayerType layer_type, - const SlicingParameters &slicing_params, - size_t layer_idx) -{ - auto& layer = layer_storage.allocate(layer_type); - return layer_initialize(layer, layer_type, slicing_params, layer_idx); -} /*! * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. @@ -1243,11 +1247,11 @@ void generate_initial_areas( const coord_t connect_length = (config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2. * config.min_radius - 1.0 * config.support_line_width, 0.0); // As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. - // This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. + // This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. // In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. // As a circle is round this length is identical for every axis as long as the 90 degrees angle between both remains. - const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? - config.min_radius / 2 : + const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? + config.min_radius / 2 : scale_(sqrt(sqr(unscale(config.min_radius)) - sqr(unscale(config.min_radius - config.support_line_width / 2)))); // Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is better than not supporting it at all. //FIXME Vojtech: This is not sufficient for support enforcers to work. @@ -1260,16 +1264,16 @@ void generate_initial_areas( const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; const bool roof_enabled = num_support_roof_layers > 0; const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.soluble_interface || sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area); - // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point - // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang - // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. + // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point + // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang + // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. // The 2*z_distance_delta is only a catch for when the support angle is very high. // Used only if not min_xy_dist. coord_t max_overhang_insert_lag = 0; if (config.z_distance_top_layers > 0) { max_overhang_insert_lag = 2 * config.z_distance_top_layers; - //FIXME + //FIXME if (mesh_group_settings.support_angle > EPSILON && mesh_group_settings.support_angle < 0.5 * M_PI - EPSILON) { //FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width). // take the least restrictive avoidance possible @@ -1315,12 +1319,12 @@ void generate_initial_areas( relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); } - // every overhang has saved if a roof should be generated for it. This can NOT be done in the for loop as an area may NOT have a roof - // even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and + // every overhang has saved if a roof should be generated for it. This can NOT be done in the for loop as an area may NOT have a roof + // even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and // it would not have a roof if the overhang is offset by support roof horizontal expansion instead. (At least this is the current behavior of the regular support) Polygons overhang_regular; { - // When support_offset = 0 safe_offset_inc will only be the difference between overhang_raw and relevant_forbidden, that has to be calculated anyway. + // When support_offset = 0 safe_offset_inc will only be the difference between overhang_raw and relevant_forbidden, that has to be calculated anyway. overhang_regular = safe_offset_inc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, config.min_radius * 1.75 + config.xy_min_distance, 0, 1); //check_self_intersections(overhang_regular, "overhang_regular1"); @@ -1339,7 +1343,7 @@ void generate_initial_areas( for (coord_t extra_total_offset_acc = 0; ! remaining_overhang.empty() && extra_total_offset_acc + config.support_line_width / 8 < extra_outset; ) { const coord_t offset_current_step = std::min( extra_total_offset_acc + 2 * config.support_line_width > config.min_radius ? - config.support_line_width / 8 : + config.support_line_width / 8 : circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); extra_total_offset_acc += offset_current_step; @@ -1359,10 +1363,10 @@ void generate_initial_areas( LineInformations overhang_lines; { //Vojtech: Generate support heads at support_tree_branch_distance spacing by producing a zig-zag infill at support_tree_branch_distance spacing, - // which is then resmapled - // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, - // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate - // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that + // which is then resmapled + // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, + // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate + // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that // the area that is valid a layer below is to small for support roof. Polylines polylines = ensure_maximum_distance_polyline( generate_support_infill_lines(remaining_overhang, support_params, false, layer_idx, mesh_group_settings.support_tree_branch_distance), @@ -1380,7 +1384,7 @@ void generate_initial_areas( } for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { // get least restricted avoidance for layer_idx-lag_ctr - const Polygons &relevant_forbidden_below = config.support_rests_on_model ? + const Polygons &relevant_forbidden_below = config.support_rests_on_model ? volumes.getCollision(config.getRadius(0), layer_idx - lag_ctr, min_xy_dist) : volumes.getAvoidance(config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist); // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. @@ -1419,7 +1423,7 @@ void generate_initial_areas( remove_small(overhang_regular, mesh_group_settings.minimum_support_area); for (ExPolygon &support_part : union_ex(overhang_regular)) { - sample_overhang_area(to_polygons(std::move(support_part)), + sample_overhang_area(to_polygons(std::move(support_part)), false, layer_idx, num_support_roof_layers, connect_length, mesh_group_settings, rich_interface_placer); throw_on_cancel(); @@ -1461,7 +1465,7 @@ static unsigned int move_inside(const Polygons &polygons, Point &from, int dista } int64_t dot_prod = ab.dot(ap); if (dot_prod <= 0) { // x is projected to before ab - if (projected_p_beyond_prev_segment) { + if (projected_p_beyond_prev_segment) { // case which looks like: > . projected_p_beyond_prev_segment = false; Point& x = p1; @@ -1496,7 +1500,7 @@ static unsigned int move_inside(const Polygons &polygons, Point &from, int dista p0 = p1; p1 = p2; continue; - } else { + } else { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . projected_p_beyond_prev_segment = false; Point x = a + (ab.cast() * (double(dot_prod) / double(ab_length2))).cast(); @@ -1570,7 +1574,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di Polygons &to_bp_data, Polygons &to_model_data, Polygons &increased, - const coord_t overspeed, + const coord_t overspeed, const bool mergelayer) { SupportElementState current_elem{ SupportElementState::propagate_down(parent.state) }; @@ -1582,18 +1586,18 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (settings.move) { increased = relevant_offset; if (overspeed > 0) { - const coord_t safe_movement_distance = - (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + const coord_t safe_movement_distance = + (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. // The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. - increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist), + increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); } if (settings.no_error && settings.move) // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. polygons_simplify(increased, scaled(0.025)); - } else + } else // if no movement is done the areas keep parent area as no move == offset(0) increased = parent.influence_area; @@ -1602,7 +1606,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (! current_elem.to_buildplate && area(to_bp_data) > tiny_area_threshold) { // mostly happening in the tip, but with merges one should check every time, just to be sure. current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } } @@ -1613,7 +1617,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (!current_elem.to_model_gracious) { if (mergelayer && area(to_model_data) >= tiny_area_threshold) { current_elem.to_model_gracious = true; - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } else to_model_data = safe_union(diff_clipped(increased, volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); @@ -1633,8 +1637,8 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di to_bp_data_2 = diff_clipped(increased, volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); Polygons to_model_data_2; if (config.support_rests_on_model && !current_elem.to_buildplate) - to_model_data_2 = diff_clipped(increased, - current_elem.to_model_gracious ? + to_model_data_2 = diff_clipped(increased, + current_elem.to_model_gracious ? volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; @@ -1649,8 +1653,8 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di while (current_ceil_radius < target_radius && validWithRadius(volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) current_ceil_radius = volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); size_t resulting_eff_dtt = current_elem.effective_radius_height; - while (resulting_eff_dtt + 1 < current_elem.distance_to_top && - config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && + while (resulting_eff_dtt + 1 < current_elem.distance_to_top && + config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= support_element_radius(config, current_elem)) ++ resulting_eff_dtt; current_elem.effective_radius_height = resulting_eff_dtt; @@ -1658,7 +1662,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di radius = support_element_collision_radius(config, current_elem); const coord_t foot_radius_increase = std::max(config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer, 0.0); - // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, + // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, // which could cause the radius to become bigger than precalculated. double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - support_element_radius(config, current_elem)) / foot_radius_increase); //FIXME @@ -1675,14 +1679,14 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (current_elem.to_buildplate) to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) - to_model_data = safe_union(diff_clipped(increased, - current_elem.to_model_gracious ? + to_model_data = safe_union(diff_clipped(increased, + current_elem.to_model_gracious ? volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) )); check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; if (area(check_layer_data) < tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << + BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << volumes.ceilRadius(support_element_collision_radius(config, current_elem), settings.use_min_distance); tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true); } @@ -1720,7 +1724,7 @@ struct SupportElementMerging { const Eigen::AlignedBox& bbox() const { return bbox_data;} const Point centroid() const { return (bbox_data.min() + bbox_data.max()) / 2; } - void set_bbox(const BoundingBox& abbox) + void set_bbox(const BoundingBox& abbox) { Point eps { coord_t(SCALED_EPSILON), coord_t(SCALED_EPSILON) }; bbox_data = { abbox.min - eps, abbox.max + eps }; } // Called by the AABBTree builder to get an index into the vector of source elements. @@ -1752,7 +1756,7 @@ static void increase_areas_one_layer( // New areas at the layer below layer_idx std::vector &merging_areas, // Layer above merging_areas. - const LayerIndex layer_idx, + const LayerIndex layer_idx, // Layer elements above merging_areas. SupportElements &layer_elements, // If false, the merging_areas will not be merged for performance reasons. @@ -1768,7 +1772,7 @@ static void increase_areas_one_layer( assert(merging_area.parents.size() == 1); SupportElement &parent = layer_elements[merging_area.parents.front()]; SupportElementState elem = SupportElementState::propagate_down(parent.state); - const Polygons &wall_restriction = + const Polygons &wall_restriction = // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist); @@ -1799,10 +1803,10 @@ static void increase_areas_one_layer( * layer z-1:dddddxxxxxxxxxx * For more detailed visualisation see calculateWallRestrictions */ - const coord_t safe_movement_distance = - (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + const coord_t safe_movement_distance = + (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); - if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) || + if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) || projected_radius_increased < config.increase_radius_until_radius) // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall extra_speed += projected_radius_delta; @@ -1811,7 +1815,7 @@ static void increase_areas_one_layer( // Ensure that the slow movement distance can not become larger than the fast one. extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); - if (config.layer_start_bp_radius > layer_idx && + if (config.layer_start_bp_radius > layer_idx && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { // can guarantee elephant foot radius increase if (ceiled_parent_radius == volumes.ceilRadius(config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases + 1), parent.state.use_min_xy_dist)) @@ -1846,7 +1850,7 @@ static void increase_areas_one_layer( if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) { // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. - insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, + insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); @@ -1866,7 +1870,7 @@ static void increase_areas_one_layer( insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } else { insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); - // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, + // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, // which looks similar to a layer shift and can reduce stability. // as such idx have chosen to only use the user setting for radius increases as a friendly recommendation. insertSetting({ AvoidanceType::Slow, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a @@ -1903,9 +1907,9 @@ static void increase_areas_one_layer( for (const AreaIncreaseSettings &settings : order) { if (settings.move) { if (offset_slow.empty() && (settings.increase_speed == slow_speed || ! offset_independant_faster)) { - // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class + // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class // was never made for precision in the single digit micron range. - offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, + offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); #ifdef TREESUPPORT_DEBUG_SVG SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-slow-%d-%ld.svg", layer_idx, int(merging_area_idx)), @@ -1915,7 +1919,7 @@ static void increase_areas_one_layer( } if (offset_fast.empty() && settings.increase_speed != slow_speed) { if (offset_independant_faster) - offset_fast = safe_offset_inc(parent.influence_area, extra_speed + config.maximum_move_distance, + offset_fast = safe_offset_inc(parent.influence_area, extra_speed + config.maximum_move_distance, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); else { const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); @@ -1930,12 +1934,12 @@ static void increase_areas_one_layer( } std::optional result; inc_wo_collision.clear(); - if (!settings.no_error) { + if (!settings.no_error) { // ERROR CASE - // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased + // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if it would be a line wrongly, it still actually has an area that can be increased Polygons lines_offset = offset(to_polylines(parent.influence_area), scaled(0.005), jtMiter, 1.2); Polygons base_error_area = union_(parent.influence_area, lines_offset); - result = increase_single_area(volumes, config, settings, layer_idx, parent, + result = increase_single_area(volumes, config, settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (config.maximum_move_distance + extra_speed) * 1.5, mergelayer); #ifdef TREE_SUPPORT_SHOW_ERRORS BOOST_LOG_TRIVIAL(error) @@ -1944,7 +1948,7 @@ static void increase_areas_one_layer( #endif // TREE_SUPPORT_SHOW_ERRORS << "Influence area could not be increased! Data about the Influence area: " "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.layer_idx << " Distance to top: " << elem.distance_to_top << - " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << + " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << " gracious " << elem.to_model_gracious << " safe " << elem.can_use_safe_radius << " until move " << elem.dont_move_until << " \n " "Parent " << &parent << ": Radius: " << support_element_collision_radius(config, parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << @@ -1972,7 +1976,7 @@ static void increase_areas_one_layer( elem.use_min_xy_dist = false; if (!settings.no_error) #ifdef TREE_SUPPORT_SHOW_ERRORS - BOOST_LOG_TRIVIAL(error) + BOOST_LOG_TRIVIAL(error) #else // TREE_SUPPORT_SHOW_ERRORS BOOST_LOG_TRIVIAL(info) #endif // TREE_SUPPORT_SHOW_ERRORS @@ -1999,7 +2003,7 @@ static void increase_areas_one_layer( merging_area.areas.to_model_areas = std::move(to_model_data); } } else { - // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. + // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. // But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent.state.result_on_layer_reset(); @@ -2039,7 +2043,7 @@ static void increase_areas_one_layer( out.elephant_foot_increases = 0; if (config.bp_radius_increase_per_layer > 0) { coord_t foot_increase_radius = std::abs(std::max(support_element_collision_radius(config, second), support_element_collision_radius(config, first)) - support_element_collision_radius(config, out)); - // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch + // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. out.elephant_foot_increases = foot_increase_radius / (config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer); } @@ -2062,8 +2066,8 @@ static bool merge_influence_areas_two_elements( { // Don't merge gracious with a non gracious area as bad placement could negatively impact reliability of the whole subtree. const bool merging_gracious_and_non_gracious = dst.state.to_model_gracious != src.state.to_model_gracious; - // Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased - // by the delta to the larger it is engulfed by it already. But because a different collision + // Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased + // by the delta to the larger it is engulfed by it already. But because a different collision // may be removed from the in draw_area() generated circles, this assumption could be wrong. const bool merging_min_and_regular_xy = dst.state.use_min_xy_dist != src.state.use_min_xy_dist; @@ -2107,10 +2111,10 @@ static bool merge_influence_areas_two_elements( if (increased_to_model_radius > config.max_to_model_radius_increase) return false; } - // if a merge could place a stable branch on unstable ground, would be increasing the radius further - // than allowed to when merging to model and to_bp trees or would merge to model before it is known + // if a merge could place a stable branch on unstable ground, would be increasing the radius further + // than allowed to when merging to model and to_bp trees or would merge to model before it is known // they will even been drawn the merge is skipped - if (! dst.state.supports_roof && ! src.state.supports_roof && + if (! dst.state.supports_roof && ! src.state.supports_roof && std::max(src.state.distance_to_top, dst.state.distance_to_top) < config.min_dtt_to_model) return false; } @@ -2121,7 +2125,7 @@ static bool merge_influence_areas_two_elements( return false; // the bigger radius is used to verify that the area is still valid after the increase with the delta. - // If there were a point where the big influence area could be valid with can_use_safe_radius + // If there were a point where the big influence area could be valid with can_use_safe_radius // the element would already be can_use_safe_radius. // the smaller radius, which gets increased by delta may reach into the area where use_min_xy_dist is no longer required. const bool use_min_radius = bigger_rad.state.use_min_xy_dist && smaller_rad.state.use_min_xy_dist; @@ -2145,7 +2149,7 @@ static bool merge_influence_areas_two_elements( merging_to_bp ? bigger_rad.areas.to_bp_areas : bigger_rad.areas.to_model_areas); // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) - // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). + // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). if (area(intersect) <= tiny_area_threshold) return false; @@ -2160,7 +2164,7 @@ static bool merge_influence_areas_two_elements( Point new_pos = move_inside_if_outside(intersect, dst.state.next_position); SupportElementState new_state = merge_support_element_states(dst.state, src.state, new_pos, layer_idx - 1, config); - new_state.increased_to_model_radius = increased_to_model_radius == 0 ? + new_state.increased_to_model_radius = increased_to_model_radius == 0 ? // increased_to_model_radius was not set yet. Propagate maximum. std::max(dst.state.increased_to_model_radius, src.state.increased_to_model_radius) : increased_to_model_radius; @@ -2248,7 +2252,7 @@ static SupportElementMerging* merge_influence_areas_two_sets( SupportElementMerging * const dst_begin, SupportElementMerging * dst_end, SupportElementMerging * src_begin, SupportElementMerging * const src_end) { - // Merging src into dst. + // Merging src into dst. // Areas of src should not overlap with areas of another elements of src. // Areas of dst should not overlap with areas of another elements of dst. // The memory from dst_begin to src_end is reserved for the merging operation, @@ -2301,8 +2305,8 @@ static SupportElementMerging* merge_influence_areas_two_sets( * \param layer_idx[in] The current layer. */ static void merge_influence_areas( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, const LayerIndex layer_idx, std::vector &influence_areas, std::function throw_on_cancel) @@ -2489,7 +2493,7 @@ void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSett throw_on_cancel(); } - BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << + BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << " ms merging areas: " << (dur_total - dur_inc).count() / 1000000 << " ms"; } @@ -2518,7 +2522,7 @@ static void set_points_on_areas(const SupportElement &elem, SupportElements *lay // if the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. if (! next_elem.state.result_on_layer_is_set()) { // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 - // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. + // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. // While this seems like a problem it may for example occur after merges. next_elem.state.result_on_layer = move_inside_if_outside(next_elem.influence_area, elem.state.result_on_layer); // do not call recursive because then amount of layers would be restricted by the stack size @@ -2543,9 +2547,9 @@ static void set_to_model_contact_simple(SupportElement &elem) * \param layer_idx[in] The current layer. */ static void set_to_model_contact_to_model_gracious( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds, SupportElement &first_elem, std::function throw_on_cancel) { @@ -2614,7 +2618,7 @@ static void remove_deleted_elements(std::vector &move_bounds) assert(i == layer.size() || i + 1 < layer.size()); if (i + 1 < int32_t(layer.size())) { element = std::move(layer.back()); - layer.pop_back(); + layer.pop_back(); // Mark the current element as deleted. map_current[i] = -1; // Mark the moved element as moved to index i. @@ -2643,7 +2647,7 @@ void create_nodes_from_area( std::vector &move_bounds, std::function throw_on_cancel) { - // Initialize points on layer 0, with a "random" point in the influence area. + // Initialize points on layer 0, with a "random" point in the influence area. // Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. { SupportElements *layer_above = move_bounds.size() > 1 ? &move_bounds[1] : nullptr; @@ -2773,9 +2777,9 @@ struct DrawArea * \param inverse_tree_order[in] A mapping that returns the child of every influence area. */ static void generate_branch_areas( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &move_bounds, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + const std::vector &move_bounds, std::vector &linear_data, std::function throw_on_cancel) { @@ -2933,7 +2937,7 @@ static void smooth_branch_areas( assert(parent.state.layer_idx == layer_idx + 1); if (support_element_radius(config, parent) != support_element_collision_radius(config, parent)) { do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, + max_outer_wall_distance = std::max(max_outer_wall_distance, (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm() - (support_element_radius(config, *draw_area.element) - support_element_radius(config, parent))); } } @@ -3081,7 +3085,7 @@ static void finalize_interface_and_support_areas( SupportGeneratorLayersPtr &top_contacts, SupportGeneratorLayersPtr &intermediate_layers, SupportGeneratorLayerStorage &layer_storage, - + std::function throw_on_cancel) { assert(std::all_of(bottom_contacts.begin(), bottom_contacts.end(), [](auto *p) { return p == nullptr; })); @@ -3174,7 +3178,7 @@ static void finalize_interface_and_support_areas( while (layers_below <= config.support_bottom_layers) { // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); - //FIXME subtract the wipe tower + //FIXME subtract the wipe tower append(floor_layer, intersection(layer_outset, overhangs[sample_layer])); if (layers_below < config.support_bottom_layers) layers_below = std::min(layers_below + 1, config.support_bottom_layers); @@ -3226,7 +3230,7 @@ static void finalize_interface_and_support_areas( */ static void draw_areas( PrintObject &print_object, - const TreeModelVolumes &volumes, + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const std::vector &overhangs, std::vector &move_bounds, @@ -3368,7 +3372,7 @@ static void draw_areas( auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); - BOOST_LOG_TRIVIAL(info) << + BOOST_LOG_TRIVIAL(info) << "Time used for drawing subfuctions: generate_branch_areas: " << dur_gen_tips << " ms " "smooth_branch_areas: " << dur_smooth << " ms " "drop_non_gracious_areas: " << dur_drop << " ms " @@ -3380,7 +3384,7 @@ static void draw_areas( // Circles are considered intersecting, if the lowest point on one circle is below the other circle's plane. // Assumption: The two planes are oriented the same way. static bool circles_intersect( - const Vec3d &p1, const Vec3d &n1, const double r1, + const Vec3d &p1, const Vec3d &n1, const double r1, const Vec3d &p2, const Vec3d &n2, const double r2) { assert(n1.dot(n2) >= 0); @@ -3522,10 +3526,10 @@ static std::pair discretize_polygon(const Vec3f& center, const Polygon // Returns Z span of the generated mesh. static std::pair extrude_branch( - const std::vector&path, + const std::vector&path, const TreeSupportSettings &config, const SlicingParameters &slicing_params, - const std::vector &move_bounds, + const std::vector &move_bounds, indexed_triangle_set &result) { Vec3d p1, p2, p3; @@ -3678,7 +3682,7 @@ void organic_smooth_branches_avoid_collisions( Vec3f position; // Previous position, for Laplacian smoothing. Vec3f prev_position; - // + // Vec3f last_collision; double last_collision_depth; // Minimum Z for which the sphere collision will be evaluated. @@ -3780,7 +3784,7 @@ void organic_smooth_branches_avoid_collisions( // Collision detected to be removed. // Nudge the circle center away from the collision. if (collision_sphere.last_collision_depth > EPSILON) - // a little bit of hysteresis to detect end of + // a little bit of hysteresis to detect end of ++ num_moved; // Shift by maximum 2mm. double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance); @@ -3938,7 +3942,7 @@ static void organic_smooth_branches_avoid_collisions( // Organic specific: Smooth branches and produce one cummulative mesh to be sliced. indexed_triangle_set draw_branches( PrintObject &print_object, - const TreeModelVolumes &volumes, + const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector &move_bounds, std::function throw_on_cancel) @@ -4058,7 +4062,7 @@ indexed_triangle_set draw_branches( // Organic specific: Slice the cummulative mesh produced by draw_branches(). void slice_branches( PrintObject &print_object, - const TreeModelVolumes &volumes, + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const std::vector &overhangs, std::vector &move_bounds, @@ -4068,7 +4072,7 @@ void slice_branches( SupportGeneratorLayersPtr &top_contacts, SupportGeneratorLayersPtr &intermediate_layers, SupportGeneratorLayerStorage &layer_storage, - + std::function throw_on_cancel) { const SlicingParameters &slicing_params = print_object.slicing_parameters(); @@ -4097,7 +4101,7 @@ void slice_branches( if (! slices[layer_idx].empty()) { SupportGeneratorLayer *&l = intermediate_layers[layer_idx]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::sltBase, slicing_params, layer_idx); + l = &layer_allocate(layer_storage, SupporLayerType::sltBase, slicing_params, config, layer_idx); append(l->polygons, to_polygons(std::move(slices[layer_idx]))); } @@ -4153,7 +4157,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // Generator for model collision, avoidance and internal guide volumes. TreeModelVolumes volumes{ print_object, build_volume, config.maximum_move_distance, config.maximum_move_distance_slow, processing.second.front(), #ifdef SLIC3R_TREESUPPORTS_PROGRESS - m_progress_multiplier, m_progress_offset, + m_progress_multiplier, m_progress_offset, #endif // SLIC3R_TREESUPPORTS_PROGRESS /* additional_excluded_areas */{} }; @@ -4170,12 +4174,27 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons for (size_t i = 0; i < print_object.layer_count(); i++) { for (ExPolygon& expoly : print_object.get_layer(i)->loverhangs) { Polygons polys = to_polygons(expoly); - if (tree_support->overhang_types[&expoly] == TreeSupport::SharpTail) { - polys = offset(to_polygons(expoly), scale_(0.2)); + if (tree_support->overhang_types[&expoly] == TreeSupport::SharpTail) { polys = offset(polys, scale_(0.2)); } append(overhangs[i + num_raft_layers], polys); } } + // add vertical enforcer points + std::vector zs = zs_from_layers(print_object.layers()); + Polygon base_circle = make_circle(scale_(0.5), SUPPORT_TREE_CIRCLE_RESOLUTION); + for (auto &pt_and_normal :tree_support->m_vertical_enforcer_points) { + auto pt = pt_and_normal.first; + auto normal = pt_and_normal.second; // normal seems useless + auto iter = std::lower_bound(zs.begin(), zs.end(), pt.z()); + if (iter != zs.end()) { + size_t layer_nr = iter - zs.begin(); + if (layer_nr > 0 && layer_nr < print_object.layer_count()) { + Polygon circle = base_circle; + circle.translate(to_2d(pt).cast()); + overhangs[layer_nr + num_raft_layers].emplace_back(std::move(circle)); + } + } + } #else std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); #endif @@ -4197,6 +4216,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons support_params.support_density = 0; } + SupportGeneratorLayerStorage layer_storage; SupportGeneratorLayersPtr top_contacts; SupportGeneratorLayersPtr bottom_contacts; @@ -4275,7 +4295,9 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons *print.get_object(processing.second.front()), volumes, config, move_bounds, bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, throw_on_cancel); -#endif +#endif + + //tree_support->move_bounds_to_contact_nodes(move_bounds, print_object, config); remove_undefined_layers(); @@ -4310,11 +4332,18 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); SupportGeneratorLayersPtr layers_sorted = generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + // QDS: This is a hack to avoid the support being generated outside the bed area. See #4769. + tbb::parallel_for_each(layers_sorted.begin(), layers_sorted.end(), [&](SupportGeneratorLayer *layer) { + if (layer) layer->polygons = intersection(layer->polygons, volumes.m_bed_area); + }); + // Don't fill in the tree supports, make them hollow with just a single sheath line. print.set_status(69, _L("Generating support")); - generate_support_toolpaths(print_object, print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), + generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - + + auto t_end = std::chrono::high_resolution_clock::now(); + BOOST_LOG_TRIVIAL(info) << "Total time of organic tree support: " << 0.001 * std::chrono::duration_cast(t_end - t_start).count() << " ms"; #if 0 //#ifdef SLIC3R_DEBUG { @@ -4563,9 +4592,10 @@ void organic_draw_branches( std::vector slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel); bottom_contacts.clear(); //FIXME parallelize? - for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) - slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); //FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); - + for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) { + slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); + slices[i] = intersection(slices[i], volumes.m_bed_area); + } size_t num_empty = 0; if (slices.front().empty()) { // Some of the initial layers are empty. @@ -4785,7 +4815,9 @@ void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_suppo } Points bedpts = tree_support->m_machine_border.contour.points; - BuildVolume build_volume{ Pointfs{ unscaled(bedpts[0]), unscaled(bedpts[1]),unscaled(bedpts[2]),unscaled(bedpts[3])}, tree_support->m_print_config->printable_height }; + Pointfs bedptsf; + std::transform(bedpts.begin(), bedpts.end(), std::back_inserter(bedptsf), [](const Point &p) { return unscale(p); }); + BuildVolume build_volume{ bedptsf, tree_support->m_print_config->printable_height }; TreeSupport3D::generate_support_areas(*print_object.print(), tree_support, build_volume, { idx }, throw_on_cancel); } diff --git a/src/libslic3r/Support/TreeSupport3D.hpp b/src/libslic3r/Support/TreeSupport3D.hpp index 7a6d342..96eea2c 100644 --- a/src/libslic3r/Support/TreeSupport3D.hpp +++ b/src/libslic3r/Support/TreeSupport3D.hpp @@ -45,8 +45,6 @@ using SupportGeneratorLayersPtr = std::vector; namespace TreeSupport3D { -// The number of vertices in each circle. -static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; struct AreaIncreaseSettings { @@ -321,7 +319,7 @@ void organic_draw_branches( SupportGeneratorLayersPtr& intermediate_layers, SupportGeneratorLayerStorage& layer_storage, - std::function throw_on_cancel); + std::function throw_on_cancel); } // namespace TreeSupport3D diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index 6e547f3..8819674 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -6,11 +6,12 @@ #include "../BoundingBox.hpp" #include "../Utils.hpp" #include "../Slicing.hpp" // SlicingParams -#include "TreeModelVolumes.hpp" #include "SupportLayer.hpp" #include "SupportParameters.hpp" namespace Slic3r { + // The number of vertices in each circle. + static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; namespace TreeSupport3D { using LayerIndex = int; @@ -76,12 +77,12 @@ struct TreeSupportMeshGroupSettings { this->support_wall_count = std::max(1, config.tree_support_wall_count.value); // at least 1 wall for organic tree support this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; double support_tree_angle_slow = 25;// TODO add a setting? - double support_tree_branch_diameter_angle = 5; // TODO add a setting? double tree_support_tip_diameter = 0.8; + this->support_tree_branch_distance = scaled(config.tree_support_branch_distance.value); this->support_tree_angle = std::clamp(config.tree_support_branch_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); this->support_tree_angle_slow = std::clamp(support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); this->support_tree_branch_diameter = scaled(config.tree_support_branch_diameter.value); - this->support_tree_branch_diameter_angle = std::clamp(support_tree_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_branch_diameter_angle = std::clamp(config.tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); this->support_tree_top_rate = 30; // percent // this->support_tree_tip_diameter = this->support_line_width; this->support_tree_tip_diameter = std::clamp(scaled(tree_support_tip_diameter), 0, this->support_tree_branch_diameter); @@ -728,5 +729,15 @@ private: std::mutex m_mutex_layer_storage; }; +enum class LineStatus +{ + INVALID, + TO_MODEL, + TO_MODEL_GRACIOUS, + TO_MODEL_GRACIOUS_SAFE, + TO_BP, + TO_BP_SAFE +}; + } // namespace TreeSupport3D } // namespace slic3r \ No newline at end of file diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index aa014a6..65aa405 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -11,7 +11,7 @@ // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with camera related data -#define ENABLE_CAMERA_STATISTICS 0 +//#define ENABLE_CAMERA_STATISTICS 0// by ctrl +shift +space quick key // Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) #define ENABLE_RENDER_PICKING_PASS 0 // Enable extracting thumbnails from selected gcode and save them as png files @@ -22,7 +22,7 @@ #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING 0 // Enable G-Code viewer statistics imgui dialog #define ENABLE_GCODE_VIEWER_STATISTICS 0 -// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation +// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation #define ENABLE_GCODE_VIEWER_DATA_CHECKING 0 // Enable project dirty state manager debug window #define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0 diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp new file mode 100644 index 0000000..d33b67b --- /dev/null +++ b/src/libslic3r/TextConfiguration.hpp @@ -0,0 +1,187 @@ +#ifndef slic3r_TextConfiguration_hpp_ +#define slic3r_TextConfiguration_hpp_ + +#include +#include +#include +#include +#include +#include +#include +#include "Point.hpp" // Transform3d + +namespace Slic3r { + +/// +/// User modifiable property of text style +/// NOTE: OnEdit fix serializations: EmbossStylesSerializable, TextConfigurationSerialization +/// +struct FontProp +{ + // define extra space between letters, negative mean closer letter + // When not set value is zero and is not stored + std::optional char_gap; // [in font point] + + // define extra space between lines, negative mean closer lines + // When not set value is zero and is not stored + std::optional line_gap; // [in font point] + + // positive value mean wider character shape + // negative value mean tiner character shape + // When not set value is zero and is not stored + std::optional boldness; // [in mm] + + // positive value mean italic of character (CW) + // negative value mean CCW skew (unItalic) + // When not set value is zero and is not stored + std::optional skew; // [ration x:y] + + // Parameter for True Type Font collections + // Select index of font in collection + std::optional collection_number; + + // Distiguish projection per glyph + bool per_glyph; + + // NOTE: way of serialize to 3mf force that zero must be default value + enum class HorizontalAlign { left = 0, center, right }; + enum class VerticalAlign { top = 0, center, bottom }; + using Align = std::pair; + // change pivot of text + // When not set, center is used and is not stored + Align align = Align(HorizontalAlign::center, VerticalAlign::center); + + ////// + // Duplicit data to wxFontDescriptor + // used for store/load .3mf file + ////// + + // Height of text line (letters) + // duplicit to wxFont::PointSize + float size_in_mm; // [in mm] + + // Additional data about font to be able to find substitution, + // when same font is not installed + std::optional family; + std::optional face_name; + std::optional style; + std::optional weight; + + /// + /// Only constructor with restricted values + /// + /// Y size of text [in mm] + /// Z size of text [in mm] + FontProp(float line_height = 10.f) : size_in_mm(line_height), per_glyph(false) + {} + + bool operator==(const FontProp& other) const { + return + char_gap == other.char_gap && + line_gap == other.line_gap && + per_glyph == other.per_glyph && + align == other.align && + is_approx(size_in_mm, other.size_in_mm) && + is_approx(boldness, other.boldness) && + is_approx(skew, other.skew); + } + + // undo / redo stack recovery + template void save(Archive &ar) const + { + ar(size_in_mm, per_glyph, align.first, align.second); + cereal::save(ar, char_gap); + cereal::save(ar, line_gap); + cereal::save(ar, boldness); + cereal::save(ar, skew); + cereal::save(ar, collection_number); + } + template void load(Archive &ar) + { + ar(size_in_mm, per_glyph, align.first, align.second); + cereal::load(ar, char_gap); + cereal::load(ar, line_gap); + cereal::load(ar, boldness); + cereal::load(ar, skew); + cereal::load(ar, collection_number); + } +}; + +/// +/// Style of embossed text +/// (Path + Type) must define how to open font for using on different OS +/// NOTE: OnEdit fix serializations: EmbossStylesSerializable, TextConfigurationSerialization +/// +struct EmbossStyle +{ + // Human readable name of style it is shown in GUI + std::string name; + + // Define how to open font + // Meaning depend on type + std::string path; + + enum class Type; + // Define what is stored in path + Type type { Type::undefined }; + + // User modification of font style + FontProp prop; + + // when name is empty than Font item was loaded from .3mf file + // and potentionaly it is not reproducable + // define data stored in path + // when wx change way of storing add new descriptor Type + enum class Type { + undefined = 0, + + // wx font descriptors are platform dependent + // path is font descriptor generated by wxWidgets + wx_win_font_descr, // on Windows + wx_lin_font_descr, // on Linux + wx_mac_font_descr, // on Max OS + + // TrueTypeFont file loacation on computer + // for privacy: only filename is stored into .3mf + file_path + }; + + bool operator==(const EmbossStyle &other) const + { + return + type == other.type && + prop == other.prop && + name == other.name && + path == other.path + ; + } + + // undo / redo stack recovery + template void serialize(Archive &ar){ ar(name, path, type, prop); } +}; + +// Emboss style name inside vector is unique +// It is not map beacuse items has own order (view inside of slect) +// It is stored into AppConfig by EmbossStylesSerializable +using EmbossStyles = std::vector; + +/// +/// Define how to create 'Text volume' +/// It is stored into .3mf by TextConfigurationSerialization +/// It is part of ModelVolume optional data +/// +struct TextConfiguration +{ + // Style of embossed text + EmbossStyle style; + + // Embossed text value + std::string text = "None"; + + // undo / redo stack recovery + template void serialize(Archive &ar) { ar(style, text); } +}; + +} // namespace Slic3r + +#endif // slic3r_TextConfiguration_hpp_ diff --git a/src/libslic3r/Timer.cpp b/src/libslic3r/Timer.cpp new file mode 100644 index 0000000..34d3af0 --- /dev/null +++ b/src/libslic3r/Timer.cpp @@ -0,0 +1,21 @@ +#include "Timer.hpp" +#include + +using namespace std::chrono; + +Slic3r::Timer::Timer(const std::string &name) : m_name(name), m_start(steady_clock::now()) {} + +Slic3r::Timer::~Timer() +{ + BOOST_LOG_TRIVIAL(debug) << "Timer '" << m_name << "' spend " << + duration_cast(steady_clock::now() - m_start).count() << "ms"; +} + + +namespace Slic3r::Timing { + +void TimeLimitAlarm::report_time_exceeded() const { + BOOST_LOG_TRIVIAL(error) << "Time limit exceeded for " << m_limit_exceeded_message << ": " << m_timer.elapsed_seconds() << "s"; +} + +} diff --git a/src/libslic3r/Timer.hpp b/src/libslic3r/Timer.hpp new file mode 100644 index 0000000..febe2af --- /dev/null +++ b/src/libslic3r/Timer.hpp @@ -0,0 +1,92 @@ +#ifndef libslic3r_Timer_hpp_ +#define libslic3r_Timer_hpp_ + +#include +#include + +namespace Slic3r { + +/// +/// Instance of this class is used for measure time consumtion +/// of block code until instance is alive and write result to debug output +/// +class Timer +{ + std::string m_name; + std::chrono::steady_clock::time_point m_start; +public: + /// + /// name describe timer + /// + /// Describe timer in consol log + Timer(const std::string& name); + + /// + /// name describe timer + /// + ~Timer(); +}; + +namespace Timing { + + // Timing code from Catch2 unit testing library + static inline uint64_t nanoseconds_since_epoch() { + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + } + + // Timing code from Catch2 unit testing library + class Timer { + public: + void start() { + m_nanoseconds = nanoseconds_since_epoch(); + } + uint64_t elapsed_nanoseconds() const { + return nanoseconds_since_epoch() - m_nanoseconds; + } + uint64_t elapsed_microseconds() const { + return elapsed_nanoseconds() / 1000; + } + unsigned int elapsed_milliseconds() const { + return static_cast(elapsed_microseconds()/1000); + } + double elapsed_seconds() const { + return elapsed_microseconds() / 1000000.0; + } + private: + uint64_t m_nanoseconds = 0; + }; + + // Emits a Boost::log error if the life time of this timing object exceeds a limit. + class TimeLimitAlarm { + public: + TimeLimitAlarm(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) : + m_time_limit_nanoseconds(time_limit_nanoseconds), m_limit_exceeded_message(limit_exceeded_message) { + m_timer.start(); + } + ~TimeLimitAlarm() { + auto elapsed = m_timer.elapsed_nanoseconds(); + if (elapsed > m_time_limit_nanoseconds) + this->report_time_exceeded(); + } + static TimeLimitAlarm new_nanos(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) { + return TimeLimitAlarm(time_limit_nanoseconds, limit_exceeded_message); + } + static TimeLimitAlarm new_milis(uint64_t time_limit_milis, std::string_view limit_exceeded_message) { + return TimeLimitAlarm(uint64_t(time_limit_milis) * 1000000l, limit_exceeded_message); + } + static TimeLimitAlarm new_seconds(uint64_t time_limit_seconds, std::string_view limit_exceeded_message) { + return TimeLimitAlarm(uint64_t(time_limit_seconds) * 1000000000l, limit_exceeded_message); + } + private: + void report_time_exceeded() const; + + Timer m_timer; + uint64_t m_time_limit_nanoseconds; + std::string_view m_limit_exceeded_message; + }; + +} // namespace Catch + +} // namespace Slic3r + +#endif // libslic3r_Timer_hpp_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index bd8f4b1..d695952 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -394,11 +394,37 @@ std::vector TriangleMesh::split() const out.reserve(itss.size()); for (indexed_triangle_set &m : itss) { // The TriangleMesh constructor shall fill in the mesh statistics including volume. - out.emplace_back(std::move(m)); - if (TriangleMesh &triangle_mesh = out.back(); triangle_mesh.volume() < 0) - // Some source mesh parts may be incorrectly oriented. Correct them. - triangle_mesh.flip_triangles(); + TriangleMesh temp_triangle_mesh(std::move(m)); + if (abs(temp_triangle_mesh.volume()< 0.01)) {//0.01mm^3 + continue; + } + if (temp_triangle_mesh.volume() < 0) {// Some source mesh parts may be incorrectly oriented. Correct them. + temp_triangle_mesh.flip_triangles(); + } + out.emplace_back(temp_triangle_mesh); + } + return out; +} +std::vector TriangleMesh::split_and_save_relationship(std::vector> &result) const { + auto itss_and_ships = its_split_and_save_relationship<>(this->its); + std::vector out; + out.reserve(itss_and_ships.itses.size()); + result.reserve(itss_and_ships.itses.size()); + unsigned int index = 0; + for (indexed_triangle_set &m : itss_and_ships.itses) { + // The TriangleMesh constructor shall fill in the mesh statistics including volume. + TriangleMesh temp_triangle_mesh(std::move(m)); + if (abs(temp_triangle_mesh.volume() < 0.01)) { // 0.01mm^3 + index++; + continue; + } + if (temp_triangle_mesh.volume() < 0) { // Some source mesh parts may be incorrectly oriented. Correct them. + temp_triangle_mesh.flip_triangles(); + } + out.emplace_back(temp_triangle_mesh); + result.emplace_back(itss_and_ships.ships[index]); + index++; } return out; } @@ -1694,7 +1720,7 @@ float its_volume(const indexed_triangle_set &its) volume += (area * height) / 3.0f; } - return volume; + return std::abs(volume); } float its_average_edge_length(const indexed_triangle_set &its) diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index c3815af..4b1a7a0 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -119,6 +119,7 @@ public: void align_to_origin(); void rotate(double angle, Point* center); std::vector split() const; + std::vector split_and_save_relationship(std::vector> &result) const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; // 2D convex hull of a 3D mesh projected into the Z=0 plane. diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 7bb0edf..aaef21b 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -957,7 +957,6 @@ inline std::pair slice_slabs_make_lines( } slice_facet_with_slabs(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_top, lines_mutex_top); } - // QDS: add vertical faces option if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Degenerate)) { Vec3i neighbors = face_neighbors[face_idx]; // Reset neighborship of this triangle in case the other triangle is oriented backwards from this one. @@ -2063,6 +2062,7 @@ void slice_mesh_slabs( const Transform3d &trafo, std::vector *out_top, std::vector *out_bottom, + std::vector> *vertical_points, std::function throw_on_cancel) { BOOST_LOG_TRIVIAL(debug) << "slice_mesh_slabs to polygons"; @@ -2133,6 +2133,11 @@ void slice_mesh_slabs( // Is the triangle vertical or degenerate? assert(d == 0); fo = fa == fb || fa == fc || fb == fc ? FaceOrientation::Degenerate : FaceOrientation::Vertical; + if(vertical_points && fo==FaceOrientation::Vertical) + { + Vec3f normal = (fb - fa).cross(fc - fa).normalized(); + vertical_points->push_back({ (fa + fb + fc) / 3,normal }); + } } face_orientation[&tri - mesh.indices.data()] = fo; } @@ -2297,7 +2302,7 @@ void project_mesh( { std::vector top, bottom; std::vector zs { -1e10, 1e10 }; - slice_mesh_slabs(mesh, zs, trafo, out_top ? &top : nullptr, out_bottom ? &bottom : nullptr, throw_on_cancel); + slice_mesh_slabs(mesh, zs, trafo, out_top ? &top : nullptr, out_bottom ? &bottom : nullptr, nullptr, throw_on_cancel); if (out_top) *out_top = std::move(top.front()); if (out_bottom) @@ -2311,7 +2316,7 @@ Polygons project_mesh( { std::vector top, bottom; std::vector zs { -1e10, 1e10 }; - slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, throw_on_cancel); + slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, nullptr, throw_on_cancel); return union_(top.front(), bottom.back()); } diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index 203c371..11af29f 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -107,6 +107,7 @@ void slice_mesh_slabs( const Transform3d &trafo, std::vector *out_top, std::vector *out_bottom, + std::vector> *vertical_points, std::function throw_on_cancel); // Project mesh upwards pointing surfaces / downwards pointing surfaces into 2D polygons. diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 37a2ce0..21a5ae8 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -218,7 +218,10 @@ public: // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh, float edge_limit = 0.6f); - + int get_orig_size_vertices() { return m_orig_size_vertices; } + const std::vector &get_triangles() { return m_triangles; } + const std::vector& get_vertices() { return m_vertices; } + const std::vector& get_neighbors() { return m_neighbors; } // Returns the facet_idx of the unsplit triangle containing the "hit". Returns -1 if the triangle isn't found. [[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx) const; [[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const; diff --git a/src/libslic3r/Triangulation.cpp b/src/libslic3r/Triangulation.cpp new file mode 100644 index 0000000..bb1fba9 --- /dev/null +++ b/src/libslic3r/Triangulation.cpp @@ -0,0 +1,329 @@ +#include "Triangulation.hpp" +#include "IntersectionPoints.hpp" +#include +#include +#include +#include +#include + +using namespace Slic3r; +namespace priv{ +inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon, const Triangulation::Changes& changes) { + const Points &pts = polygon.points; + uint32_t size = static_cast(pts.size()); + uint32_t last_index = offset + size - 1; + uint32_t prev_index = changes[last_index]; + for (uint32_t i = 0; i < size; ++i) { + uint32_t index = changes[offset + i]; + // when duplicit points are neighbor + if (prev_index == index) continue; + edges.push_back({prev_index, index}); + prev_index = index; + } + offset += size; +} + +inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon) { + const Points &pts = polygon.points; + uint32_t size = static_cast(pts.size()); + uint32_t prev_index = offset + size - 1; + for (uint32_t i = 0; i < size; ++i) { + uint32_t index = offset + i; + edges.push_back({prev_index, index}); + prev_index = index; + } + offset += size; +} + +inline bool has_bidirectional_constrained( + const Triangulation::HalfEdges &constrained) +{ + for (const auto &c : constrained) { + auto key = std::make_pair(c.second, c.first); + auto it = std::lower_bound(constrained.begin(), constrained.end(), + key); + if (it != constrained.end() && *it == key) return true; + } + return false; +} + +inline bool is_unique(const Points &points) { + Points pts = points; // copy + std::sort(pts.begin(), pts.end()); + auto it = std::adjacent_find(pts.begin(), pts.end()); + return it == pts.end(); +} + +inline bool has_self_intersection( + const Points &points, + const Triangulation::HalfEdges &constrained_half_edges) +{ + Lines lines; + lines.reserve(constrained_half_edges.size()); + for (const auto &he : constrained_half_edges) + lines.emplace_back(points[he.first], points[he.second]); + return !get_intersections(lines).empty(); +} + +} // namespace priv + +//#define VISUALIZE_TRIANGULATION +#ifdef VISUALIZE_TRIANGULATION +#include "admesh/stl.h" // indexed triangle set +static void visualize(const Points &points, + const Triangulation::Indices &indices, + const char *filename) +{ + // visualize + indexed_triangle_set its; + its.vertices.reserve(points.size()); + for (const Point &p : points) its.vertices.emplace_back(p.x(), p.y(), 0.); + its.indices = indices; + its_write_obj(its, filename); +} +#endif // VISUALIZE_TRIANGULATION + +Triangulation::Indices Triangulation::triangulate(const Points &points, + const HalfEdges &constrained_half_edges) +{ + assert(!points.empty()); + assert(!constrained_half_edges.empty()); + // constrained must be sorted + assert(std::is_sorted(constrained_half_edges.begin(), + constrained_half_edges.end())); + // check that there is no duplicit constrained edge + assert(std::adjacent_find(constrained_half_edges.begin(), constrained_half_edges.end()) == constrained_half_edges.end()); + // edges can NOT contain bidirectional constrained + assert(!priv::has_bidirectional_constrained(constrained_half_edges)); + // check that there is only unique poistion of points + assert(priv::is_unique(points)); + assert(!priv::has_self_intersection(points, constrained_half_edges)); + // use cgal triangulation + using K = CGAL::Exact_predicates_inexact_constructions_kernel; + using Vb = CGAL::Triangulation_vertex_base_with_info_2; + using Fb = CGAL::Constrained_triangulation_face_base_2; + using Tds = CGAL::Triangulation_data_structure_2; + using CDT = CGAL::Constrained_Delaunay_triangulation_2; + + // construct a constrained triangulation + CDT cdt; + { + std::vector vertices_handle(points.size()); // for constriants + using Point_with_ord = std::pair; + using SearchTrait = CGAL::Spatial_sort_traits_adapter_2 + >; + + std::vector cdt_points; + cdt_points.reserve(points.size()); + size_t ord = 0; + for (const auto &p : points) + cdt_points.emplace_back(std::make_pair(CDT::Point{p.x(), p.y()}, ord++)); + + SearchTrait st; + CGAL::spatial_sort(cdt_points.begin(), cdt_points.end(), st); + CDT::Face_handle f; + for (const auto& p : cdt_points) { + auto handle = cdt.insert(p.first, f); + handle->info() = p.second; + vertices_handle[p.second] = handle; + f = handle->face(); + } + + // Constrain the triangulation. + for (const HalfEdge &edge : constrained_half_edges) + cdt.insert_constraint(vertices_handle[edge.first], vertices_handle[edge.second]); + } + + auto faces = cdt.finite_face_handles(); + + // Unmark constrained edges of outside faces. + size_t num_faces = 0; + for (CDT::Face_handle fh : faces) { + for (int i = 0; i < 3; ++i) { + if (!fh->is_constrained(i)) continue; + auto key = std::make_pair(fh->vertex((i + 2) % 3)->info(), fh->vertex((i + 1) % 3)->info()); + auto it = std::lower_bound(constrained_half_edges.begin(), constrained_half_edges.end(), key); + if (it == constrained_half_edges.end() || *it != key) continue; + // This face contains a constrained edge and it is outside. + for (int j = 0; j < 3; ++ j) + fh->set_constraint(j, false); + --num_faces; + break; + } + ++num_faces; + } + + auto inside = [](CDT::Face_handle &fh) { + return fh->neighbor(0) != fh && + (fh->is_constrained(0) || + fh->is_constrained(1) || + fh->is_constrained(2)); + }; + +#ifdef VISUALIZE_TRIANGULATION + std::vector indices2; + indices2.reserve(num_faces); + for (CDT::Face_handle fh : faces) + if (inside(fh)) indices2.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info()); + visualize(points, indices2, "C:/data/temp/triangulation_without_floodfill.obj"); +#endif // VISUALIZE_TRIANGULATION + + // Propagate inside the constrained regions. + std::vector queue; + queue.reserve(num_faces); + for (CDT::Face_handle seed : faces){ + if (!inside(seed)) continue; + // Seed fill to neighbor faces. + queue.emplace_back(seed); + while (! queue.empty()) { + CDT::Face_handle fh = queue.back(); + queue.pop_back(); + for (int i = 0; i < 3; ++i) { + if (fh->is_constrained(i)) continue; + // Propagate along this edge. + fh->set_constraint(i, true); + CDT::Face_handle nh = fh->neighbor(i); + bool was_inside = inside(nh); + // Mark the other side of this edge. + nh->set_constraint(nh->index(fh), true); + if (! was_inside) + queue.push_back(nh); + } + } + } + + std::vector indices; + indices.reserve(num_faces); + for (CDT::Face_handle fh : faces) + if (inside(fh)) + indices.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info()); + +#ifdef VISUALIZE_TRIANGULATION + visualize(points, indices, "C:/data/temp/triangulation.obj"); +#endif // VISUALIZE_TRIANGULATION + + return indices; +} + +Triangulation::Indices Triangulation::triangulate(const Polygon &polygon) +{ + const Points &pts = polygon.points; + HalfEdges edges; + edges.reserve(pts.size()); + uint32_t offset = 0; + priv::insert_edges(edges, offset, polygon); + std::sort(edges.begin(), edges.end()); + return triangulate(pts, edges); +} + +Triangulation::Indices Triangulation::triangulate(const Polygons &polygons) +{ + size_t count = count_points(polygons); + Points points; + points.reserve(count); + + HalfEdges edges; + edges.reserve(count); + uint32_t offset = 0; + + for (const Polygon &polygon : polygons) { + Slic3r::append(points, polygon.points); + priv::insert_edges(edges, offset, polygon); + } + + std::sort(edges.begin(), edges.end()); + return triangulate(points, edges); +} + +Triangulation::Indices Triangulation::triangulate(const ExPolygon &expolygon){ + ExPolygons expolys({expolygon}); + return triangulate(expolys); +} + +Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons){ + Points pts = to_points(expolygons); + Points d_pts = collect_duplicates(pts); + if (d_pts.empty()) return triangulate(expolygons, pts); + + Changes changes = create_changes(pts, d_pts); + Indices indices = triangulate(expolygons, pts, changes); + // reverse map for changes + Changes changes2(changes.size(), std::numeric_limits::max()); + for (size_t i = 0; i < changes.size(); ++i) + changes2[changes[i]] = i; + + // convert indices into expolygons indicies + for (Vec3i32 &t : indices) + for (size_t ti = 0; ti < 3; ti++) t[ti] = changes2[t[ti]]; + + return indices; +} + +Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points &points) +{ + assert(count_points(expolygons) == points.size()); + // when contain duplicit coordinate in points will not work properly + assert(collect_duplicates(points).empty()); + + HalfEdges edges; + edges.reserve(points.size()); + uint32_t offset = 0; + for (const ExPolygon &expolygon : expolygons) { + priv::insert_edges(edges, offset, expolygon.contour); + for (const Polygon &hole : expolygon.holes) + priv::insert_edges(edges, offset, hole); + } + std::sort(edges.begin(), edges.end()); + return triangulate(points, edges); +} + +Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes) +{ + assert(!points.empty()); + assert(count_points(expolygons) == points.size()); + assert(changes.size() == points.size()); + // IMPROVE: search from end and somehow distiquish that value is not a change + uint32_t count_points = *std::max_element(changes.begin(), changes.end())+1; + Points pts(count_points); + for (size_t i = 0; i < changes.size(); i++) + pts[changes[i]] = points[i]; + + HalfEdges edges; + edges.reserve(points.size()); + uint32_t offset = 0; + for (const ExPolygon &expolygon : expolygons) { + priv::insert_edges(edges, offset, expolygon.contour, changes); + for (const Polygon &hole : expolygon.holes) + priv::insert_edges(edges, offset, hole, changes); + } + + std::sort(edges.begin(), edges.end()); + return triangulate(pts, edges); +} + +Triangulation::Changes Triangulation::create_changes(const Points &points, const Points &duplicits) +{ + assert(!duplicits.empty()); + assert(duplicits.size() < points.size()/2); + std::vector duplicit_indices(duplicits.size(), std::numeric_limits::max()); + Changes changes; + changes.reserve(points.size()); + uint32_t index = 0; + for (const Point &p: points) { + auto it = std::lower_bound(duplicits.begin(), duplicits.end(), p); + if (it == duplicits.end() || *it != p) { + changes.push_back(index); + ++index; + continue; + } + uint32_t &d_index = duplicit_indices[it - duplicits.begin()]; + if (d_index == std::numeric_limits::max()) { + d_index = index; + changes.push_back(index); + ++index; + } else { + changes.push_back(d_index); + } + } + return changes; +} diff --git a/src/libslic3r/Triangulation.hpp b/src/libslic3r/Triangulation.hpp new file mode 100644 index 0000000..00c46cd --- /dev/null +++ b/src/libslic3r/Triangulation.hpp @@ -0,0 +1,72 @@ +#ifndef libslic3r_Triangulation_hpp_ +#define libslic3r_Triangulation_hpp_ + +#include +#include +#include +#include +#include + +namespace Slic3r { + +class Triangulation +{ +public: + Triangulation() = delete; + + // define oriented connection of 2 vertices(defined by its index) + using HalfEdge = std::pair; + using HalfEdges = std::vector; + using Indices = std::vector; + + /// + /// Connect points by triangulation to create filled surface by triangles + /// Input points have to be unique + /// Inspiration for make unique points is Emboss::dilate_to_unique_points + /// + /// Points to connect + /// Constraint for edges, pair is from point(first) to + /// point(second), sorted lexicographically + /// Triangles + static Indices triangulate(const Points &points, + const HalfEdges &half_edges); + static Indices triangulate(const Polygon &polygon); + static Indices triangulate(const Polygons &polygons); + static Indices triangulate(const ExPolygon &expolygon); + static Indices triangulate(const ExPolygons &expolygons); + + // Map for convert original index to set without duplication + // from_index + using Changes = std::vector; + + /// + /// Create conversion map from original index into new + /// with respect of duplicit point + /// + /// input set of points + /// duplicit points collected from points + /// Conversion map for point index + static Changes create_changes(const Points &points, const Points &duplicits); + + /// + /// Triangulation for expolygons, speed up when points are already collected + /// NOTE: Not working properly for ExPolygons with multiple point on same coordinate + /// You should check it by "collect_changes" + /// + /// Input shape to triangulation - define edges + /// Points from expolygons + /// Triangle indices + static Indices triangulate(const ExPolygons &expolygons, const Points& points); + + /// + /// Triangulation for expolygons containing multiple points with same coordinate + /// + /// Input shape to triangulation - define edge + /// Points from expolygons + /// Changes swap for indicies into points + /// Triangle indices + static Indices triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes); +}; + +} // namespace Slic3r +#endif // libslic3r_Triangulation_hpp_ \ No newline at end of file diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 73797fa..c11c976 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -63,7 +63,7 @@ #define CLI_SLICING_ERROR -100 #define CLI_GCODE_PATH_CONFLICTS -101 - +#define CLI_FILAMENT_UNPRINTABLE_ON_FIRST_LAYER -103 namespace boost { namespace filesystem { class directory_entry; }} @@ -354,6 +354,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER } extern std::string xml_escape(std::string text, bool is_marked = false); +extern std::string xml_escape_double_quotes_attribute_value(std::string text); extern std::string xml_unescape(std::string text); @@ -577,6 +578,45 @@ inline std::string get_qdt_monitor_time_dhm(float time_in_secs) return buffer; } +inline std::string get_qdt_finish_time_dhm(float time_in_secs) +{ + if (time_in_secs < 1) return "Finished"; + time_t finish_time = std::time(nullptr) + static_cast(time_in_secs); + std::tm *finish_tm = std::localtime(&finish_time); + int finish_hour = finish_tm->tm_hour; + int finish_minute = finish_tm->tm_min; + int finish_day = finish_tm->tm_yday; + int finish_year = finish_tm->tm_year + 1900; + time_t current_time = std::time(nullptr); + std::tm *current_tm = std::localtime(¤t_time); + int current_day = current_tm->tm_yday; + int current_year = current_tm->tm_year + 1900; + + int diff_day = 0; + if (current_year != finish_year) { + if ((current_year % 4 == 0 && current_year % 100 != 0) || current_year % 400 == 0) + diff_day = 366 - current_day; + else + diff_day = 365 - current_day; + for (int year = current_year + 1; year < finish_year; year++) { + if ((current_year % 4 == 0 && current_year % 100 != 0) || current_year % 400 == 0) + diff_day += 366; + else + diff_day += 365; + } + diff_day += finish_day; + } else { + diff_day = finish_day - current_day; + } + + std::ostringstream formattedTime; + formattedTime << std::setw(2) << std::setfill('0') << finish_hour << ":" << std::setw(2) << std::setfill('0') << finish_minute; + std::string finish_time_str = formattedTime.str(); + if (diff_day != 0) finish_time_str += "+" + std::to_string(diff_day); + + return finish_time_str; +} + inline std::string get_qdt_remain_time_dhms(float time_in_secs) { int days = (int) (time_in_secs / 86400.0f); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index d927e45..49fb8bd 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -57,6 +57,7 @@ static constexpr double EPSILON = 1e-4; // with int64_t we don't have to worry anymore about the size of the int. static constexpr double SCALING_FACTOR = 0.000001; static constexpr double PI = 3.141592653589793238; +#define POLY_SIDE_COUNT 24 // for brim ear circle // When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15; static constexpr double RESOLUTION = 0.0125; @@ -64,7 +65,7 @@ static constexpr double RESOLUTION = 0.0125; static constexpr double SPARSE_INFILL_RESOLUTION = 0.04; #define SCALED_SPARSE_INFILL_RESOLUTION (SPARSE_INFILL_RESOLUTION / SCALING_FACTOR) -static constexpr double SUPPORT_RESOLUTION = 0.1; +static constexpr double SUPPORT_RESOLUTION = 0.0375; #define SCALED_SUPPORT_RESOLUTION (SUPPORT_RESOLUTION / SCALING_FACTOR) // Maximum perimeter length for the loop to apply the small perimeter speed. #define SMALL_PERIMETER_LENGTH(LENGTH) (((LENGTH)/SCALING_FACTOR)*2*PI) diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 7931cce..443d91f 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1210,6 +1210,34 @@ std::string xml_escape(std::string text, bool is_marked/* = false*/) return text; } +// Definition of escape symbols https://www.w3.org/TR/REC-xml/#AVNormalize +// During the read of xml attribute normalization of white spaces is applied +// Soo for not lose white space character it is escaped before store +std::string xml_escape_double_quotes_attribute_value(std::string text) +{ + std::string::size_type pos = 0; + for (;;) { + pos = text.find_first_of("\"&<\r\n\t", pos); + if (pos == std::string::npos) break; + + std::string replacement; + switch (text[pos]) { + case '\"': replacement = """; break; + case '&': replacement = "&"; break; + case '<': replacement = "<"; break; + case '\r': replacement = " "; break; + case '\n': replacement = " "; break; + case '\t': replacement = " "; break; + default: break; + } + + text.replace(pos, 1, replacement); + pos += replacement.size(); + } + + return text; +} + std::string xml_unescape(std::string s) { std::string ret;