diff --git a/resources/profiles/Creality/ENDER5S1_thumbnail.png b/resources/profiles/Creality/ENDER5S1_thumbnail.png new file mode 100644 index 0000000000..410c4f3d4c Binary files /dev/null and b/resources/profiles/Creality/ENDER5S1_thumbnail.png differ diff --git a/resources/profiles/Creality/SERMOONV1_thumbnail.png b/resources/profiles/Creality/SERMOONV1_thumbnail.png new file mode 100644 index 0000000000..7ec62ef848 Binary files /dev/null and b/resources/profiles/Creality/SERMOONV1_thumbnail.png differ diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e413ce991b..e1e8a559ec 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3002,9 +3002,14 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string Polyline travel { this->last_pos(), point }; if (this->config().avoid_curled_filament_during_travels) { - Point scaled_origin = Point(scaled(this->origin())); - travel = m_avoid_curled_filaments.find_path(this->last_pos() + scaled_origin, point + scaled_origin); - travel.translate(-scaled_origin); + if (m_config.avoid_crossing_perimeters) { + BOOST_LOG_TRIVIAL(warning) + << "Option >avoid curled filament during travels< is not compatible with avoid crossing perimeters and it will be ignored!"; + } else { + Point scaled_origin = Point(scaled(this->origin())); + travel = m_avoid_curled_filaments.find_path(this->last_pos() + scaled_origin, point + scaled_origin); + travel.translate(-scaled_origin); + } } // check whether a straight travel move would need retraction diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 6ab1354ee5..32dcb82d05 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -112,7 +112,7 @@ inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBas template Eigen::Matrix to_2d(const Eigen::MatrixBase &ptN) { static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) >= 3, "to_2d(): first parameter is not a 3D or higher dimensional vector"); - return { ptN.x(), ptN.y() }; + return ptN.template head<2>(); } template diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 66c6de7a9d..c714bc7c11 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -121,7 +121,7 @@ public: Vec3f get_cell_center(const Vec3i &cell_coords) const { - return origin + cell_coords.cast().cwiseProduct(this->cell_size) + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0)); + return origin + cell_coords.cast().cwiseProduct(this->cell_size) + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0f)); } void take_position(const Vec3f &position) { taken_cells.insert(to_cell_index(to_cell_coords(position))); } @@ -199,16 +199,6 @@ struct ExtrusionPropertiesAccumulator } }; -// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) -// checkout e.g. here: https://www.geogebra.org/calculator -float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) -{ - float shifted = value - mean_x_coord; - float denominator = falloff_speed * shifted * shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); -} - std::vector to_short_lines(const ExtrusionEntity *e, float length_limit) { assert(!e->is_collection()); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 82dc254ca0..f4e27982b2 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -977,8 +977,10 @@ indexed_triangle_set its_make_cone(double r, double h, double fa) vertices.emplace_back(Vec3f(0., 0., h)); size_t i = 0; + const auto vec = Eigen::Vector2f(0, float(r)); for (double angle=0; angle<2*PI; angle+=fa) { - vertices.emplace_back(r*std::cos(angle), r*std::sin(angle), 0.); + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); if (angle > 0.) { facets.emplace_back(0, i+2, i+1); facets.emplace_back(1, i+1, i+2); diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 461d8c22e5..85e170bd03 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -137,18 +137,41 @@ enum class FacetSliceType { Cutting = 2 }; -// Return true, if the facet has been sliced and line_out has been filled. -static FacetSliceType slice_facet( - // Z height of the slice in XY plane. Scaled or unscaled (same as vertices[].z()). - float slice_z, - // 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z). - const stl_vertex *vertices, - const stl_triangle_vertex_indices &indices, - const Vec3i &edge_ids, - const int idx_vertex_lowest, - const bool horizontal, - IntersectionLine &line_out) +// Convert an int32_t scaled coordinate into an unscaled 3D floating point coordinate (mesh vertex). +template +inline Vec3f contour_point_to_v3f(const Point &pt, const T z) { + return to_3d( + // unscale using doubles for higher accuracy + unscaled(pt). + // then convert to floats + cast(), + float(z)); +} + +// Convert 2D projection of an int32_t scaled coordinate into an unscaled 3D floating point coordinate (mesh vertex). +template +inline Point v3f_scaled_to_contour_point(const Eigen::MatrixBase &v) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) >= 2, "v3f_scaled_to_contour_point(): Not a 2D or 3D vector."); + using T = typename Derived::Scalar; + return { coord_t(std::floor(v.x() + T(0.5))), coord_t(std::floor(v.y() + T(0.5))) }; +} + +// Return true, if the facet has been sliced and line_out has been filled. +template +inline FacetSliceType slice_facet( + // Z height of the slice in XY plane. Scaled or unscaled (same as vertices[].z()). + T slice_z, + // 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z). + const Eigen::Matrix *vertices, + const stl_triangle_vertex_indices &indices, + const Vec3i &edge_ids, + const int idx_vertex_lowest, + const bool horizontal, + IntersectionLine &line_out) +{ + using Vector = Eigen::Matrix; IntersectionPoint points[3]; size_t num_points = 0; auto point_on_layer = size_t(-1); @@ -158,7 +181,7 @@ static FacetSliceType slice_facet( // (external on the right of the line) for (int j = 0; j < 3; ++ j) { // loop through facet edges int edge_id; - const stl_vertex *a, *b; + const Vector *a, *b; int a_id, b_id; { int k = (idx_vertex_lowest + j) % 3; @@ -174,16 +197,16 @@ static FacetSliceType slice_facet( if (a->z() == slice_z && b->z() == slice_z) { // Edge is horizontal and belongs to the current layer. // The following rotation of the three vertices may not be efficient, but this branch happens rarely. - const stl_vertex &v0 = vertices[0]; - const stl_vertex &v1 = vertices[1]; - const stl_vertex &v2 = vertices[2]; + const Vector &v0 = vertices[0]; + const Vector &v1 = vertices[1]; + const Vector &v2 = vertices[2]; // We may ignore this edge for slicing purposes, but we may still use it for object cutting. FacetSliceType result = FacetSliceType::Slicing; if (horizontal) { // All three vertices are aligned with slice_z. line_out.edge_type = IntersectionLine::FacetEdgeType::Horizontal; result = FacetSliceType::Cutting; - double normal = (v1.x() - v0.x()) * (v2.y() - v1.y()) - (v1.y() - v0.y()) * (v2.x() - v1.x()); + double normal = cross2((to_2d(v1) - to_2d(v0)).template cast(), (to_2d(v2) - to_2d(v1)).template cast()); if (normal < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); @@ -205,10 +228,8 @@ static FacetSliceType slice_facet( } else line_out.edge_type = IntersectionLine::FacetEdgeType::Bottom; } - line_out.a.x() = a->x(); - line_out.a.y() = a->y(); - line_out.b.x() = b->x(); - line_out.b.y() = b->y(); + line_out.a = v3f_scaled_to_contour_point(*a); + line_out.b = v3f_scaled_to_contour_point(*b); line_out.a_id = a_id; line_out.b_id = b_id; assert(line_out.a != line_out.b); @@ -220,8 +241,7 @@ static FacetSliceType slice_facet( if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { point_on_layer = num_points; IntersectionPoint &point = points[num_points ++]; - point.x() = a->x(); - point.y() = a->y(); + static_cast(point) = v3f_scaled_to_contour_point(*a); point.point_id = a_id; } } else if (b->z() == slice_z) { @@ -229,8 +249,7 @@ static FacetSliceType slice_facet( if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { point_on_layer = num_points; IntersectionPoint &point = points[num_points ++]; - point.x() = b->x(); - point.y() = b->y(); + static_cast(point) = v3f_scaled_to_contour_point(*b); point.point_id = b_id; } } else if ((a->z() < slice_z && b->z() > slice_z) || (b->z() < slice_z && a->z() > slice_z)) { @@ -270,16 +289,10 @@ static FacetSliceType slice_facet( } #else // Just clamp the intersection point to source triangle edge. - if (t <= 0.) { - point.x() = a->x(); - point.y() = a->y(); - } else if (t >= 1.) { - point.x() = b->x(); - point.y() = b->y(); - } else { - point.x() = coord_t(floor(double(a->x()) + (double(b->x()) - double(a->x())) * t + 0.5)); - point.y() = coord_t(floor(double(a->y()) + (double(b->y()) - double(a->y())) * t + 0.5)); - } + static_cast(point) = + t <= 0. ? v3f_scaled_to_contour_point(*a) : + t >= 1. ? v3f_scaled_to_contour_point(*b) : + v3f_scaled_to_contour_point(a->template head<2>().template cast() * (1. - t) + b->template head<2>().template cast() * t + Vec2d(0.5, 0.5)); point.edge_id = edge_id; ++ num_points; #endif @@ -2182,28 +2195,44 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); FacetSliceType slice_type = FacetSliceType::NoSlice; if (z > min_z - EPSILON && z < max_z + EPSILON) { - Vec3f vertices_scaled[3]; + Vec3d vertices_scaled[3]; for (int i = 0; i < 3; ++ i) { const Vec3f &src = vertices[i]; - Vec3f &dst = vertices_scaled[i]; - dst.x() = scale_(src.x()); - dst.y() = scale_(src.y()); + Vec3d &dst = vertices_scaled[i]; + dst.x() = scaled(src.x()); + dst.y() = scaled(src.y()); dst.z() = src.z(); } - slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line); + slice_type = slice_facet(double(z), vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line); } if (slice_type != FacetSliceType::NoSlice) { // Save intersection lines for generating correct triangulations. if (line.edge_type == IntersectionLine::FacetEdgeType::Top) { - lower_lines.emplace_back(line); lower_slice_vertices.emplace_back(line.a_id); lower_slice_vertices.emplace_back(line.b_id); + if (lower) { + lower_lines.emplace_back(line); + if (triangulate_caps) { + // Snap these vertices to coord_t grid, so that they will be matched with the vertices produced + // by triangulating opening on the cut. + lower->vertices[line.a_id] = contour_point_to_v3f(line.a, z); + lower->vertices[line.b_id] = contour_point_to_v3f(line.b, z); + } + } } else if (line.edge_type == IntersectionLine::FacetEdgeType::Bottom) { - upper_lines.emplace_back(line); upper_slice_vertices.emplace_back(line.a_id); upper_slice_vertices.emplace_back(line.b_id); - } else if (line.edge_type == IntersectionLine::FacetEdgeType::General) { + if (upper) { + upper_lines.emplace_back(line); + if (triangulate_caps) { + // Snap these vertices to coord_t grid, so that they will be matched with the vertices produced + // by triangulating opening on the cut. + upper->vertices[line.a_id] = contour_point_to_v3f(line.a, z); + upper->vertices[line.b_id] = contour_point_to_v3f(line.b, z); + } + } + } else if (line.edge_type == IntersectionLine::FacetEdgeType::General && triangulate_caps) { lower_lines.emplace_back(line); upper_lines.emplace_back(line); } @@ -2235,11 +2264,11 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id || facets_edge_ids[facet_idx](iv) == line.edge_b_id); if (facets_edge_ids[facet_idx](iv) == line.edge_a_id) { // Unscale to doubles first, then to floats to reach the same accuracy as triangulate_expolygons_2d(). - v0v1 = to_3d(unscaled(line.a).cast().eval(), z); - v2v0 = to_3d(unscaled(line.b).cast().eval(), z); + v0v1 = contour_point_to_v3f(line.a, z); + v2v0 = contour_point_to_v3f(line.b, z); } else { - v0v1 = to_3d(unscaled(line.b).cast().eval(), z); - v2v0 = to_3d(unscaled(line.a).cast().eval(), z); + v0v1 = contour_point_to_v3f(line.b, z); + v2v0 = contour_point_to_v3f(line.a, z); } const stl_vertex &v0 = vertices[iv]; const int iv0 = facet[iv]; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index b004938021..d83890a40c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -127,7 +127,9 @@ void GLVolume::SinkingContours::update() init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } } - m_model.init_from(std::move(init_data)); + + if (init_data.vertices_count() > 0) + m_model.init_from(std::move(init_data)); } void GLVolume::NonManifoldEdges::render() @@ -437,9 +439,9 @@ std::vector GLVolumeCollection::load_object( int GLVolumeCollection::load_object_volume( const ModelObject* model_object, - int obj_idx, - int volume_idx, - int instance_idx) + int obj_idx, + int volume_idx, + int instance_idx) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); @@ -452,10 +454,12 @@ int GLVolumeCollection::load_object_volume( v.printable = instance->printable; #if ENABLE_SMOOTH_NORMALS v.model.init_from(*mesh, true); - v.mesh_raycaster = std::make_unique(mesh); + if (m_use_raycasters) + v.mesh_raycaster = std::make_unique(mesh); #else v.model.init_from(*mesh); - v.mesh_raycaster = std::make_unique(mesh); + if (m_use_raycasters) + v.mesh_raycaster = std::make_unique(mesh); #endif // ENABLE_SMOOTH_NORMALS v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2eb161480b..674c3ce824 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -397,6 +397,7 @@ private: Slope m_slope; bool m_show_sinking_contours{ false }; bool m_show_non_manifold_edges{ true }; + bool m_use_raycasters{ true }; public: GLVolumePtrs volumes; @@ -445,6 +446,7 @@ public: bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); } + void set_use_raycasters(bool value) { m_use_raycasters = value; } void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 50dd4e743e..aed5b139cb 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -315,6 +315,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); + toggle_field("avoid_curled_filament_during_travels", !config->opt_bool("avoid_crossing_perimeters")); + toggle_field("avoid_crossing_perimeters", !config->opt_bool("avoid_curled_filament_during_travels")); + bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters"); toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b0e11422db..097757ff94 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -643,7 +643,7 @@ const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); GCodeViewer::GCodeViewer() { m_extrusions.reset_role_visibility_flags(); - + m_shells.volumes.set_use_raycasters(false); // m_sequential_view.skip_invisible_moves = true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9b3def4dd8..21a8285d7c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -30,6 +30,7 @@ static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::DARK_GRAY(); static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); static const ColorRGBA CONNECTOR_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); +static const ColorRGBA HOVERED_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 1.0f); const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; @@ -1849,7 +1850,7 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c return translation_transform(offset) * scale_transform(Vec3d::Ones() - border_scale) * vol_matrix; } -bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) { // check if connector pos is out of clipping plane if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) { @@ -1857,16 +1858,54 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co return true; } + // check if connector bottom contour is out of clipping plane const CutConnector& cur_connector = connectors[idx]; + const CutConnectorShape shape = CutConnectorShape(cur_connector.attribs.shape); + const int sectorCount = shape == CutConnectorShape::Triangle ? 3 : + shape == CutConnectorShape::Square ? 4 : + shape == CutConnectorShape::Circle ? 60: // supposably, 60 points are enough for conflict detection + shape == CutConnectorShape::Hexagon ? 6 : 1 ; + + indexed_triangle_set mesh; + auto& vertices = mesh.vertices; + vertices.reserve(sectorCount + 1); + + float fa = 2 * PI / sectorCount; + auto vec = Eigen::Vector2f(0, cur_connector.radius); + for (float angle = 0; angle < 2.f * PI; angle += fa) { + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); + } + its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); + + for (auto vertex : vertices) { + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast())) { + m_info_stats.outside_cut_contour++; + return true; + } + } + + return false; +} + +bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +{ + if (is_outside_of_cut_contour(idx, connectors, cur_pos)) + return true; + + const CutConnector& cur_connector = connectors[idx]; + const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m * scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); + // check if connector's bounding box is inside the object's bounding box if (!bounding_box().contains(cur_tbb)) { m_info_stats.outside_bb++; return true; } + // check if connectors are overlapping for (size_t i = 0; i < connectors.size(); ++i) { if (i == idx) continue; @@ -1920,7 +1959,8 @@ void GLGizmoCut3D::render_connectors() Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // First decide about the color of the point. - if (is_conflict_for_connector(i, connectors, pos)) { + const bool conflict_connector = is_conflict_for_connector(i, connectors, pos); + if (conflict_connector) { m_has_invalid_connector = true; render_color = CONNECTOR_ERR_COLOR; } @@ -1930,7 +1970,8 @@ void GLGizmoCut3D::render_connectors() if (!m_connectors_editing) render_color = CONNECTOR_ERR_COLOR; else if (size_t(m_hover_id - m_connectors_group_id) == i) - render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; + render_color = conflict_connector ? HOVERED_ERR_COLOR : + connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; else if (m_selected[i]) render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 8fea38849e..15d53c9de1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -241,6 +241,7 @@ private: bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; bool render_connect_type_radio_button(CutConnectorType type); Transform3d get_volume_transformation(const ModelVolume* volume) const; + bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); void render_connectors();