diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 2f2ca5fb5..b2c38e4a1 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -32,6 +32,8 @@ struct SlopeDetection uniform vec4 uniform_color; uniform SlopeDetection slope; +uniform bool offset_depth_buffer; + #ifdef ENABLE_ENVIRONMENT_MAP uniform sampler2D environment_tex; uniform bool use_environment_tex; @@ -50,8 +52,6 @@ varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -59,36 +59,7 @@ void main() vec3 color = uniform_color.rgb; float alpha = uniform_color.a; - vec2 intensity_fs = intensity; - vec3 eye_normal_fs = eye_normal; - float world_normal_z_fs = world_normal_z; - if (compute_triangle_normals_in_fs) { - vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); -#ifdef FLIP_TRIANGLE_NORMALS - triangle_normal = -triangle_normal; -#endif - - // First transform the normal into camera space and normalize the result. - eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0); - - intensity_fs = vec2(0.0, 0.0); - intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * model_pos).xyz; - intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0); - intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE; - - // z component of normal vector in world coordinate used for slope shading - world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0; - } - - if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) { + if (slope.actived && world_normal_z < slope.normal_z - EPSILON) { color = vec3(0.7, 0.7, 1.0); alpha = 1.0; } @@ -96,8 +67,13 @@ void main() color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color; #ifdef ENABLE_ENVIRONMENT_MAP if (use_environment_tex) - gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha); + gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha); else #endif - gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha); + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); + + // In the support painting gizmo and the seam painting gizmo are painted triangles rendered over the already + // rendered object. To resolved z-fighting between previously rendered object and painted triangles, values + // inside the depth buffer are offset by small epsilon for painted triangles inside those gizmos. + gl_FragDepth = gl_FragCoord.z - (offset_depth_buffer ? EPSILON : 0.0); } diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index 20a142452..d5e74b60b 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -54,26 +54,22 @@ varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { - if (!compute_triangle_normals_in_fs) { - // First transform the normal into camera space and normalize the result. - eye_normal = normalize(gl_NormalMatrix * gl_Normal); + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(gl_NormalMatrix * gl_Normal); - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - } + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; model_pos = gl_Vertex; // Point in homogenous coordinates. @@ -90,8 +86,7 @@ void main() } // z component of normal vector in world coordinate used for slope shading - if (!compute_triangle_normals_in_fs) - world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; gl_Position = ftransform(); // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. diff --git a/resources/shaders/gouraud_mod.fs b/resources/shaders/gouraud_mod.fs index 959c9cb27..a1ba85b5c 100644 --- a/resources/shaders/gouraud_mod.fs +++ b/resources/shaders/gouraud_mod.fs @@ -45,6 +45,8 @@ struct SlopeDetection uniform vec4 uniform_color; uniform SlopeDetection slope; +uniform bool offset_depth_buffer; + #ifdef ENABLE_ENVIRONMENT_MAP uniform sampler2D environment_tex; uniform bool use_environment_tex; @@ -62,8 +64,6 @@ varying vec4 world_pos; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -71,36 +71,7 @@ void main() vec3 color = uniform_color.rgb; float alpha = uniform_color.a; - vec2 intensity_fs = intensity; - vec3 eye_normal_fs = eye_normal; - float world_normal_z_fs = world_normal_z; - if (compute_triangle_normals_in_fs) { - vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); -#ifdef FLIP_TRIANGLE_NORMALS - triangle_normal = -triangle_normal; -#endif - - // First transform the normal into camera space and normalize the result. - eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0); - - intensity_fs = vec2(0.0, 0.0); - intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * model_pos).xyz; - intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0); - intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE; - - // z component of normal vector in world coordinate used for slope shading - world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0; - } - - if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) { + if (slope.actived && world_normal_z < slope.normal_z - EPSILON) { color = vec3(0.7, 0.7, 1.0); alpha = 1.0; } @@ -123,8 +94,13 @@ void main() #ifdef ENABLE_ENVIRONMENT_MAP if (use_environment_tex) - gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha); + gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha); else #endif - gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha); + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); + + // In the support painting gizmo and the seam painting gizmo are painted triangles rendered over the already + // rendered object. To resolved z-fighting between previously rendered object and painted triangles, values + // inside the depth buffer are offset by small epsilon for painted triangles inside those gizmos. + gl_FragDepth = gl_FragCoord.z - (offset_depth_buffer ? EPSILON : 0.0); } diff --git a/resources/shaders/gouraud_mod.vs b/resources/shaders/gouraud_mod.vs index bc83c38fb..79d7a63c0 100644 --- a/resources/shaders/gouraud_mod.vs +++ b/resources/shaders/gouraud_mod.vs @@ -43,34 +43,29 @@ varying vec4 world_pos; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { - if (!compute_triangle_normals_in_fs) { - // First transform the normal into camera space and normalize the result. - eye_normal = normalize(gl_NormalMatrix * gl_Normal); + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(gl_NormalMatrix * gl_Normal); - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - } + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; model_pos = gl_Vertex; // Point in homogenous coordinates. world_pos = volume_world_matrix * gl_Vertex; // z component of normal vector in world coordinate used for slope shading - if (!compute_triangle_normals_in_fs) - world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; gl_Position = ftransform(); // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. diff --git a/resources/shaders/mm_gouraud.fs b/resources/shaders/mm_gouraud.fs new file mode 100644 index 000000000..f7154b419 --- /dev/null +++ b/resources/shaders/mm_gouraud.fs @@ -0,0 +1,55 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const float EPSILON = 0.0001; + +uniform vec4 uniform_color; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + vec3 color = uniform_color.rgb; + float alpha = uniform_color.a; + + vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); +#ifdef FLIP_TRIANGLE_NORMALS + triangle_normal = -triangle_normal; +#endif + + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(gl_NormalMatrix * triangle_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + // x = diffuse, y = specular; + vec2 intensity = vec2(0.0, 0.0); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * model_pos).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); +} diff --git a/resources/shaders/mm_gouraud.vs b/resources/shaders/mm_gouraud.vs new file mode 100644 index 000000000..2847c3136 --- /dev/null +++ b/resources/shaders/mm_gouraud.vs @@ -0,0 +1,23 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform mat4 volume_world_matrix; +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; + +void main() +{ + model_pos = gl_Vertex; + // Point in homogenous coordinates. + vec4 world_pos = volume_world_matrix * gl_Vertex; + + gl_Position = ftransform(); + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index e46a0797c..84d68b1e6 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -155,7 +155,20 @@ bool PolyNode::IsHole() const node = node->Parent; } return result; -} +} + +void PolyTree::RemoveOutermostPolygon() +{ + if (this->ChildCount() == 1 && this->Childs[0]->ChildCount() > 0) { + PolyNode *outerNode = this->Childs[0]; + this->Childs.reserve(outerNode->ChildCount()); + this->Childs[0] = outerNode->Childs[0]; + this->Childs[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + this->AddChild(*outerNode->Childs[i]); + } else + this->Clear(); +} //------------------------------------------------------------------------------ // Miscellaneous global functions @@ -3444,7 +3457,8 @@ void ClipperOffset::Execute(Paths& solution, double delta) clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - if (solution.size() > 0) solution.erase(solution.begin()); + if (! solution.empty()) + solution.erase(solution.begin()); } } //------------------------------------------------------------------------------ @@ -3475,17 +3489,7 @@ void ClipperOffset::Execute(PolyTree& solution, double delta) clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); //remove the outer PolyNode rectangle ... - if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) - { - PolyNode* outerNode = solution.Childs[0]; - solution.Childs.reserve(outerNode->ChildCount()); - solution.Childs[0] = outerNode->Childs[0]; - solution.Childs[0]->Parent = outerNode->Parent; - for (int i = 1; i < outerNode->ChildCount(); ++i) - solution.AddChild(*outerNode->Childs[i]); - } - else - solution.Clear(); + solution.RemoveOutermostPolygon(); } } //------------------------------------------------------------------------------ diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 74e6601f9..b1dae3c24 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -180,6 +180,7 @@ public: PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); } void Clear() { AllNodes.clear(); Childs.clear(); } int Total() const; + void RemoveOutermostPolygon(); private: PolyTree(const PolyTree &src) = delete; PolyTree& operator=(const PolyTree &src) = delete; @@ -521,6 +522,7 @@ public: double MiterLimit; double ArcTolerance; double ShortestEdgeLength; + private: Paths m_destPolys; Path m_srcPoly; @@ -528,6 +530,8 @@ private: std::vector m_normals; double m_delta, m_sinA, m_sin, m_cos; double m_miterLim, m_StepsPerRad; + // x: index of the lowest contour in m_polyNodes + // y: index of the lowest point in the lowest contour IntPoint m_lowest; PolyNode m_polyNodes; diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index db31975e3..a13d578a3 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -50,7 +50,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr { ExPolygons ex_polygons; for (LayerRegion *region : print_object.layers().front()->regions()) - Slic3r::append(ex_polygons, offset_ex(offset_ex(region->slices.surfaces, float(SCALED_EPSILON)), -float(SCALED_EPSILON))); + Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON))); return ex_polygons; } @@ -177,7 +177,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); + append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare)); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); @@ -230,13 +230,13 @@ static ExPolygons inner_brim_area(const Print &print, } if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) - append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare))); + append(brim_area_object, diff_ex(shrink_ex(ex_poly.holes, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly.holes, brim_width + brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); + append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare)); append(holes_object, ex_poly.holes); } @@ -385,10 +385,10 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing())); for (size_t i = 0; i < num_loops; ++i) { try_cancel(); - islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); + islands = expand(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); for (Polygon &poly : islands) poly.douglas_peucker(SCALED_RESOLUTION); - polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); + polygons_append(loops, shrink(islands, 0.5f * float(flow.scaled_spacing()))); } loops = union_pt_chained_outside_in(loops); diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index e8f87a6e3..28fb09313 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -117,15 +117,6 @@ Polylines PolyTreeToPolylines(ClipperLib::PolyTree &&polytree) return out; } -ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) -{ - ClipperLib::Clipper clipper; - clipper.AddPaths(input, ClipperLib::ptSubject, true); - ClipperLib::PolyTree polytree; - clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero - return PolyTreeToExPolygons(std::move(polytree)); -} - #if 0 // Global test. bool has_duplicate_points(const ClipperLib::PolyTree &polytree) @@ -165,23 +156,28 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree) } #endif -// Offset outside by 10um, one by one. -template -static ClipperLib::Paths safety_offset(PathsProvider &&paths) +// 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) { ClipperLib::ClipperOffset co; ClipperLib::Paths out; out.reserve(paths.size()); ClipperLib::Paths out_this; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); for (const ClipperLib::Path &path : paths) { co.Clear(); - co.MiterLimit = 2.; // 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.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); - bool ccw = ClipperLib::Orientation(path); - co.Execute(out_this, ccw ? ClipperSafetyOffset : - ClipperSafetyOffset); + co.AddPath(path, joinType, endType); + bool ccw = endType == ClipperLib::etClosedPolygon ? ClipperLib::Orientation(path) : true; + co.Execute(out_this, ccw ? offset : - offset); if (! ccw) { // Reverse the resulting contours. for (ClipperLib::Path &path : out_this) @@ -192,38 +188,122 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths) return out; } -// Only safe for a single path. +// Offset outside by 10um, one by one. template -ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) +static ClipperLib::Paths safety_offset(PathsProvider &&paths) { - // perform offset - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - float delta_scaled = delta; - co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPaths(std::forward(input), joinType, endType); - ClipperLib::Paths retval; - co.Execute(retval, delta_scaled); + return raw_offset(std::forward(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit); +} + +template +TResult clipper_do( + const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType) +{ + ClipperLib::Clipper clipper; + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); + clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); + TResult retval; + clipper.Execute(clipType, retval, fillType, fillType); return retval; } -Slic3r::Polygons offset(const Slic3r::Polygon& polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +template +TResult clipper_do( + const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType, + const ApplySafetyOffset do_safety_offset) +{ + // Safety offset only allowed on intersection and difference. + assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); + 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); +} + +template +TResult clipper_union( + TSubj && subject, + // fillType pftNonZero and pftPositive "should" produce the same result for "normalized with implicit union" set of polygons + const ClipperLib::PolyFillType fillType = ClipperLib::pftNonZero) +{ + ClipperLib::Clipper clipper; + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); + TResult retval; + clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType); + return retval; +} + +// Perform union of input polygons using the positive rule, convert to ExPolygons. +//FIXME is there any benefit of not doing the boolean / using pftEvenOdd? +ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union) +{ + 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) +{ + assert(offset > 0); + return raw_offset(std::forward(paths), offset, joinType, miterLimit); +} + +template +static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset > 0); + return clipper_union(raw_offset(std::forward(paths), offset, joinType, miterLimit)); +} + +// used by shrink_paths() +template static void remove_outermost_polygon(Container & solution); +template<> void remove_outermost_polygon(ClipperLib::Paths &solution) + { if (! solution.empty()) solution.erase(solution.begin()); } +template<> void remove_outermost_polygon(ClipperLib::PolyTree &solution) + { solution.RemoveOutermostPolygon(); } + +template +static TResult shrink_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset > 0); + TResult out; + if (auto raw = raw_offset(std::forward(paths), - offset, joinType, miterLimit); ! raw.empty()) { + ClipperLib::Clipper clipper; + clipper.AddPaths(raw, ClipperLib::ptSubject, true); + ClipperLib::IntRect r = clipper.GetBounds(); + clipper.AddPath({ { r.left - 10, r.bottom + 10 }, { r.right + 10, r.bottom + 10 }, { r.right + 10, r.top - 10 }, { r.left - 10, r.top - 10 } }, ClipperLib::ptSubject, true); + clipper.ReverseSolution(true); + clipper.Execute(ClipperLib::ctUnion, out, ClipperLib::pftNegative, ClipperLib::pftNegative); + remove_outermost_polygon(out); + } + return out; +} + +template +static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset != 0); + return offset > 0 ? + expand_paths(std::forward(paths), offset, joinType, miterLimit) : + shrink_paths(std::forward(paths), - offset, joinType, miterLimit); +} + +Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(raw_offset(ClipperUtils::SinglePathProvider(polygon.points), delta, joinType, miterLimit)); } -#ifdef CLIPPERUTILS_UNSAFE_OFFSET Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } + { return to_polygons(offset_paths(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } -#endif // CLIPPERUTILS_UNSAFE_OFFSET + { 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) - { return to_polygons(_offset(ClipperUtils::SinglePathProvider(polyline.points), ClipperLib::etOpenButt, delta, joinType, 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) - { return to_polygons(_offset(ClipperUtils::PolylinesProvider(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } + { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); } // 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) @@ -274,14 +354,8 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d 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. - ClipperLib::Clipper clipper; - clipper.Clear(); - clipper.AddPaths(contours, ClipperLib::ptSubject, true); - clipper.AddPaths(holes, ClipperLib::ptClip, true); - ClipperLib::Paths output; - clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - if (! output.empty()) { + // 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 { // The offsetted holes have eaten up the offsetted outer contour. @@ -308,7 +382,7 @@ static int offset_expolygon_inner(const Slic3r::Surface &surface, const float de static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) { return offset_expolygon_inner(surface->expolygon, delta, joinType, miterLimit, out); } -ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) +ClipperLib::Paths expolygon_offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) { ClipperLib::Paths out; offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out); @@ -317,9 +391,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, // This is a safe variant of the polygons offset, tailored for multiple ExPolygons. // It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours. -// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united. +// Each ExPolygon is offsetted separately. For outer offset, the the offsetted ExPolygons shall be united outside of this function. template -ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) +static std::pair expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { // Offsetted ExPolygons before they are united. ClipperLib::Paths output; @@ -329,124 +403,101 @@ ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, size_t expolygons_collected = 0; for (const auto &expoly : expolygons) expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output); + return std::make_pair(std::move(output), expolygons_collected); +} - // 4) Unite the offsetted expolygons. - if (expolygons_collected > 1 && delta > 0) { +// See comment on expolygon_offsets_raw. In addition, for positive offset the contours are united. +template +static ClipperLib::Paths expolygons_offset(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. + return expolygons_collected > 1 && delta > 0 ? // There is a chance that the outwards offsetted expolygons may intersect. Perform a union. - ClipperLib::Clipper clipper; - clipper.Clear(); - clipper.AddPaths(output, ClipperLib::ptSubject, true); - clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - } else { + clipper_union(output) : // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. - } - - return output; + output; +} + +// See comment on expolygons_offset_raw. In addition, the polygons are always united to conver to polytree. +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 + return clipper_union(output); } Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); } + { return to_polygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); } Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); } + { return to_polygons(expolygons_offset(expolygons, delta, joinType, miterLimit)); } Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } + { return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); } Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } + { return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } + //FIXME one may spare one Clipper Union call. + { return ClipperPaths_to_Slic3rExPolygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } + { return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(surfaces, delta, joinType, miterLimit)); } + { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } -#ifdef CLIPPERUTILS_UNSAFE_OFFSET -Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &polygons) - { return offset(polygons, ClipperSafetyOffset); } -Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) - { return offset_ex(polygons, ClipperSafetyOffset); } -#endif // CLIPPERUTILS_UNSAFE_OFFSET - -Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons) - { return offset(expolygons, ClipperSafetyOffset); } -Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) - { return offset_ex(expolygons, ClipperSafetyOffset); } - -ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) -{ - // prepare ClipperOffset object - ClipperLib::ClipperOffset co; - if (joinType == jtRound) { - co.ArcTolerance = miterLimit; - } else { - co.MiterLimit = miterLimit; - } - float delta_scaled1 = delta1; - float delta_scaled2 = delta2; - co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR); - - // perform first offset - ClipperLib::Paths output1; - co.AddPaths(ClipperUtils::PolygonsProvider(polygons), joinType, ClipperLib::etClosedPolygon); - co.Execute(output1, delta_scaled1); - - // perform second offset - co.Clear(); - co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths retval; - co.Execute(retval, delta_scaled2); - - return retval; -} - -Polygons offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) -{ - return to_polygons(_offset2(polygons, delta1, delta2, joinType, miterLimit)); -} - -ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) -{ - return ClipperPaths_to_Slic3rExPolygons(_offset2(polygons, delta1, delta2, joinType, miterLimit)); -} - -//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper -// conversions and unnecessary Clipper calls. It is not that bad now as Clipper uses Slic3r's own Point / Polygon types directly. Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - return offset(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); + return to_polygons(offset_paths(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - return offset_ex(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); + return PolyTreeToExPolygons(offset_paths(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +ExPolygons offset2_ex(const Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return PolyTreeToExPolygons(offset_paths(expolygons_offset(surfaces, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } -template -TResult _clipper_do( - const ClipperLib::ClipType clipType, - TSubj && subject, - TClip && clip, - const ClipperLib::PolyFillType fillType) +// Offset outside, then inside produces morphological closing. All deltas should be positive. +Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - ClipperLib::Clipper clipper; - clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); - clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); - TResult retval; - clipper.Execute(clipType, retval, fillType, fillType); - return retval; + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(shrink_paths(expand_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + return PolyTreeToExPolygons(shrink_paths(expand_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return PolyTreeToExPolygons(shrink_paths(expand_paths(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } -template -TResult _clipper_do( - const ClipperLib::ClipType clipType, - TSubj && subject, - TClip && clip, - const ClipperLib::PolyFillType fillType, - const ApplySafetyOffset do_safety_offset) +// Offset inside, then outside produces morphological opening. All deltas should be positive. +Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - // Safety offset only allowed on intersection and difference. - assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); - 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); + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(expand_paths(shrink_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(expand_paths(shrink_paths(ClipperUtils::ExPolygonsProvider(expolygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return to_polygons(expand_paths(shrink_paths(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } // Fix of #117: A large fractal pyramid takes ages to slice @@ -457,29 +508,22 @@ TResult _clipper_do( // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time. // 2) Run Clipper Union once again to extract the PolyTree from the result of 1). template -inline ClipperLib::PolyTree _clipper_do_polytree2( +inline ClipperLib::PolyTree clipper_do_polytree( const ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip, const ClipperLib::PolyFillType fillType) { - ClipperLib::Clipper clipper; - clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); - clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); // Perform the operation with the output to input_subject. // This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library // if there are overapping edges. - ClipperLib::Paths input_subject; - clipper.Execute(clipType, input_subject, fillType, fillType); - // Perform an additional Union operation to generate the PolyTree ordering. - clipper.Clear(); - clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); - ClipperLib::PolyTree retval; - clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType); - return retval; + if (auto output = clipper_do(clipType, subject, clip, fillType); ! output.empty()) + // Perform an additional Union operation to generate the PolyTree ordering. + return clipper_union(output, fillType); + return ClipperLib::PolyTree(); } template -inline ClipperLib::PolyTree _clipper_do_polytree2( +inline ClipperLib::PolyTree clipper_do_polytree( const ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip, @@ -488,14 +532,14 @@ inline ClipperLib::PolyTree _clipper_do_polytree2( { assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); return do_safety_offset == ApplySafetyOffset::Yes ? - _clipper_do_polytree2(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : - _clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), fillType); + clipper_do_polytree(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : + clipper_do_polytree(clipType, std::forward(subject), std::forward(clip), fillType); } template static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset) { - return to_polygons(_clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); + return to_polygons(clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) @@ -506,6 +550,8 @@ Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) @@ -529,7 +575,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons template static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero) - { return PolyTreeToExPolygons(_clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), fill_type, do_safety_offset)); } + { return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward(subject), std::forward(clip), fill_type, do_safety_offset)); } Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } @@ -578,9 +624,9 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type) { 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_polytree2(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } + { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) - { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } + { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } template Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) @@ -692,14 +738,15 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol return retval; } +// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed. +// If the contours are not intersecting, their orientation shall not be modified by union_pt(). ClipperLib::PolyTree union_pt(const Polygons &subject) { - return _clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); + return clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } - ClipperLib::PolyTree union_pt(const ExPolygons &subject) { - return _clipper_do(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); + return clipper_do(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } // Simple spatial ordering of Polynodes @@ -730,7 +777,7 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou }); } -static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval) +static void traverse_pt_outside_in(ClipperLib::PolyNodes &&nodes, Polygons *retval) { // collect ordering points Points ordering_points; @@ -740,22 +787,20 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons // Perform the ordering, push results recursively. //FIXME pass the last point to chain_clipper_polynodes? - for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) { - retval->emplace_back(node->Contour); + for (ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) { + retval->emplace_back(std::move(node->Contour)); if (node->IsHole()) // Orient a hole, which is clockwise oriented, to CCW. retval->back().reverse(); // traverse the next depth - traverse_pt_outside_in(node->Childs, retval); + traverse_pt_outside_in(std::move(node->Childs), retval); } } Polygons union_pt_chained_outside_in(const Polygons &subject) { - ClipperLib::PolyTree polytree = union_pt(subject); - Polygons retval; - traverse_pt_outside_in(polytree.Childs, &retval); + traverse_pt_outside_in(union_pt(subject).Childs, &retval); return retval; } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index f49d922c1..bbd91c0fd 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -12,16 +12,26 @@ using Slic3r::ClipperLib::jtMiter; using Slic3r::ClipperLib::jtRound; using Slic3r::ClipperLib::jtSquare; -static constexpr const float ClipperSafetyOffset = 10.f; +namespace Slic3r { + +static constexpr const float ClipperSafetyOffset = 10.f; + +static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; +//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 +// is extended excessively. +static constexpr const double DefaultMiterLimit = 3.; + +static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Slic3r::ClipperLib::jtSquare; +// Miter limit is ignored for jtSquare. +static constexpr const double DefaultLineMiterLimit = 0.; + enum class ApplySafetyOffset { No, Yes }; -#define CLIPPERUTILS_UNSAFE_OFFSET - -namespace Slic3r { - namespace ClipperUtils { class PathsProviderIteratorBase { public: @@ -81,6 +91,33 @@ namespace ClipperUtils { static Points s_end; }; + template + class PathsProvider { + public: + PathsProvider(const std::vector &paths) : m_paths(paths) {} + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(typename std::vector::const_iterator it) : m_it(it) {} + const Points& operator*() const { return *m_it; } + bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + const Points& operator++(int) { return *(m_it ++); } + iterator& operator++() { ++ m_it; return *this; } + private: + typename std::vector::const_iterator m_it; + }; + + iterator cbegin() const { return iterator(m_paths.begin()); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(m_paths.end()); } + iterator end() const { return this->cend(); } + size_t size() const { return m_paths.size(); } + + private: + const std::vector &m_paths; + }; + template class MultiPointsProvider { public: @@ -261,36 +298,82 @@ namespace ClipperUtils { }; } -ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); +// Perform union of input polygons using the non-zero rule, convert to ExPolygons. +ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false); // offset Polygons -Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. +Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); // offset Polylines -Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +// 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::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); +Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = 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); } +inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) { return offset_ex(expolygons, ClipperSafetyOffset); } + +Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons); Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons); +Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons); -Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +// Aliases for the various offset(...) functions, conveying the purpose of the offset. +inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); } +// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons. +inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); } +inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } -#ifdef CLIPPERUTILS_UNSAFE_OFFSET -Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons); -Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); -#endif // CLIPPERUTILS_UNSAFE_OFFSET +// Wherever applicable, please use the opening() / closing() 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 offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); + +// Offset outside, then inside produces morphological closing. All deltas should be positive. +Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return closing(polygons, delta, delta, joinType, miterLimit); } +Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return closing_ex(polygons, delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); } +inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); } + +// Offset inside, then outside produces morphological opening. All deltas should be positive. +// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons. +Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(polygons, delta, delta, joinType, miterLimit); } +inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(expolygons, delta, delta, joinType, miterLimit); } +inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(surfaces, delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); } Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); @@ -299,6 +382,7 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); @@ -366,6 +450,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); +// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed. +// If the contours are not intersecting, their orientation shall not be modified by union_pt(). ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject); ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 7ba6de7d4..117066690 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -252,11 +252,11 @@ std::vector group_fills(const Layer &layer) // Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2 Polygons collapsed = diff( surfaces_polygons, - offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2 + ClipperSafetyOffset)); + opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset))); //FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being // added if two offsetted void regions merge. // polygons_append(voids, collapsed); - ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, ApplySafetyOffset::Yes); + ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes); // Now find an internal infill SurfaceFill to add these extrusions to. SurfaceFill *internal_solid_fill = nullptr; unsigned int region_id = 0; diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index baf57f426..fc0c1b30c 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -402,19 +402,19 @@ public: hole.rotate(angle); } - double mitterLimit = 3.; + double miterLimit = DefaultMiterLimit; // for the infill pattern, don't cut the corners. // default miterLimt = 3 - //double mitterLimit = 10.; + //double miterLimit = 10.; assert(aoffset1 < 0); assert(aoffset2 <= 0); assert(aoffset2 == 0 || aoffset2 < aoffset1); // bool sticks_removed = remove_sticks(polygons_src); // if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!"; - polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, mitterLimit); + polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit); if (aoffset2 < 0) - polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), ClipperLib::jtMiter, mitterLimit); + polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit); // Filter out contours with zero area or small area, contours with 2 points only. const double min_area_threshold = 0.01 * aoffset2 * aoffset2; remove_small(polygons_outer, min_area_threshold); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 49de854f2..1f538862b 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace Slic3r { @@ -33,6 +34,16 @@ struct Intersection float distance; }; +struct ClosestLine +{ + // Index of the polygon containing this line. + size_t border_idx; + // Index of this line on the polygon containing it. + size_t line_idx; + // Closest point on the line. + Point point; +}; + // Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { @@ -53,7 +64,7 @@ struct AllIntersectionsVisitor bool operator()(coord_t iy, coord_t ix) { - // Called with a row and colum of the grid cell, which is intersected by a line. + // Called with a row and column of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { Point intersection_point; @@ -82,7 +93,7 @@ struct FirstIntersectionVisitor { assert(pt_current != nullptr); assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. + // Called with a row and column of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); this->intersect = false; for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { @@ -103,6 +114,180 @@ struct FirstIntersectionVisitor bool intersect = false; }; +// Visitor to create a list of closet lines to a defined point. +struct MinDistanceVisitor +{ + explicit MinDistanceVisitor(const EdgeGrid::Grid &grid, const Point ¢er, double max_distance_squared) + : grid(grid), center(center), max_distance_squared(max_distance_squared) + {} + + void init() + { + this->closest_lines.clear(); + this->closest_lines_set.clear(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and column of the grid cell, which is inside a bounding box. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + Point closest_point; + if (closest_lines_set.find(*it_contour_and_segment) == closest_lines_set.end() && + line_alg::distance_to_squared(Line(segment.first, segment.second), center, &closest_point) <= this->max_distance_squared) { + closest_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, closest_point}); + closest_lines_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid & grid; + const Slic3r::Point center; + std::vector closest_lines; + std::unordered_set, boost::hash>> closest_lines_set; + double max_distance_squared = std::numeric_limits::max(); +}; + +// Returns sorted list of closest lines to a passed point within a passed radius +static std::vector get_closest_lines_in_radius(const EdgeGrid::Grid &grid, const Point ¢er, float search_radius) +{ + Point radius_vector(search_radius, search_radius); + MinDistanceVisitor visitor(grid, center, search_radius * search_radius); + grid.visit_cells_intersecting_box(BoundingBox(center - radius_vector, center + radius_vector), visitor); + std::sort(visitor.closest_lines.begin(), visitor.closest_lines.end(), [¢er](const auto &l, const auto &r) { + return (center - l.point).template cast().squaredNorm() < (center - r.point).template cast().squaredNorm(); + }); + + return visitor.closest_lines; +} + +// When the offset is too big, then original travel doesn't have to cross created boundaries. +// For these cases, this function adds another intersection with lines around the start and the end point of the original travel. +static std::vector extend_for_closest_lines(const std::vector &intersections, + const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + const float search_radius) +{ + const std::vector start_lines = get_closest_lines_in_radius(boundary.grid, start, search_radius); + const std::vector end_lines = get_closest_lines_in_radius(boundary.grid, end, search_radius); + + // Compute distance to the closest point in the ClosestLine from begin of contour. + auto compute_distance = [&boundary](const ClosestLine &closest_line) -> float { + float dist_from_line_begin = (closest_line.point - boundary.boundaries[closest_line.border_idx][closest_line.line_idx]).cast().norm(); + return boundary.boundaries_params[closest_line.border_idx][closest_line.line_idx] + dist_from_line_begin; + }; + + // It tries to find closest lines for both start point and end point of the travel which has the same border_idx + auto endpoints_close_to_same_boundary = [&start_lines, &end_lines]() -> std::pair { + std::unordered_set boundaries_from_start; + for (const ClosestLine &cl_start : start_lines) + boundaries_from_start.insert(cl_start.border_idx); + for (const ClosestLine &cl_end : end_lines) + if (boundaries_from_start.find(cl_end.border_idx) != boundaries_from_start.end()) + for (const ClosestLine &cl_start : start_lines) + if (cl_start.border_idx == cl_end.border_idx) { + size_t cl_start_idx = &cl_start - &start_lines.front(); + size_t cl_end_idx = &cl_end - &end_lines.front(); + return std::make_pair(cl_start_idx, cl_end_idx); + } + return std::make_pair(std::numeric_limits::max(), std::numeric_limits::max()); + }; + + // If the existing two lines within the search radius start and end point belong to the same boundary, + // discard all intersection points because the whole detour could be on one boundary. + if (!start_lines.empty() && !end_lines.empty()) { + std::pair cl_indices = endpoints_close_to_same_boundary(); + if (cl_indices.first != std::numeric_limits::max()) { + assert(cl_indices.second != std::numeric_limits::max()); + const ClosestLine &cl_start = start_lines[cl_indices.first]; + const ClosestLine &cl_end = end_lines[cl_indices.second]; + std::vector new_intersections; + new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}); + new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}); + return new_intersections; + } + } + + // Returns ClosestLine which is closer to the point "close_to" then point inside passed Intersection. + auto get_closer = [&search_radius](const std::vector &closest_lines, const Intersection &intersection, + const Point &close_to) -> size_t { + for (const ClosestLine &cl : closest_lines) { + double old_dist = (close_to - intersection.point).cast().squaredNorm(); + if (cl.border_idx == intersection.border_idx && old_dist <= (search_radius * search_radius) && + (close_to - cl.point).cast().squaredNorm() < old_dist) + return &cl - &closest_lines.front(); + } + return std::numeric_limits::max(); + }; + + // Try to find ClosestLine with same boundary_idx as any existing Intersection + auto find_closest_line_with_same_boundary_idx = [](const std::vector & closest_lines, + const std::vector &intersections, const bool reverse) -> size_t { + std::unordered_set boundaries_indices; + for (const ClosestLine &closest_line : closest_lines) + boundaries_indices.insert(closest_line.border_idx); + + // This function must be called only in the case that exists closest_line with boundary_idx equals to intersection.border_idx + auto find_closest_line_index = [&closest_lines](const Intersection &intersection) -> size_t { + for (const ClosestLine &closest_line : closest_lines) + if (closest_line.border_idx == intersection.border_idx) return &closest_line - &closest_lines.front(); + // This is an invalid state. + assert(false); + return std::numeric_limits::max(); + }; + + if (reverse) { + for (const Intersection &intersection : boost::adaptors::reverse(intersections)) + if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end()) + return find_closest_line_index(intersection); + } else { + for (const Intersection &intersection : intersections) + if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end()) + return find_closest_line_index(intersection); + } + return std::numeric_limits::max(); + }; + + std::vector new_intersections = intersections; + if (!intersections.empty() && !start_lines.empty()) { + size_t cl_start_idx = get_closer(start_lines, new_intersections.front(), start); + if (cl_start_idx != std::numeric_limits::max()) { + // If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine. + const ClosestLine &cl_start = start_lines[cl_start_idx]; + new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}; + } else { + // Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the + // vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which + // minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then + // use the first one, which is the closest one to the start point. + size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, intersections, true); + const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits::max()) ? start_lines[start_closest_lines_idx] : start_lines.front(); + new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}); + } + } else if (!intersections.empty() && !end_lines.empty()) { + size_t cl_end_idx = get_closer(end_lines, new_intersections.back(), end); + if (cl_end_idx != std::numeric_limits::max()) { + // If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine. + const ClosestLine &cl_end = end_lines[cl_end_idx]; + new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}; + } else { + // Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the + // vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which + // minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then + // use the first one, which is the closest one to the end point. + size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, intersections, false); + const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits::max()) ? end_lines[end_closest_lines_idx] : end_lines.front(); + new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}); + } + } + return new_intersections; +} + // point_idx is the index from which is different vertex is searched. template static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) @@ -268,10 +453,63 @@ static std::vector simplify_travel(const AvoidCrossingPerimeters::B return simplified_path; } +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const PrintObject &print_object) +{ + std::vector printing_extruders = print_object.object_extruders(); + assert(!printing_extruders.empty()); + float avg_extruder = 0; + for(unsigned int extruder_id : printing_extruders) + avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); + avg_extruder /= printing_extruders.size(); + return avg_extruder; +} + +// called by get_boundary() / avoid_perimeters_inner() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const LayerRegion *layer_region : layer.regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + // Called by avoid_perimeters() and by simplify_travel_heuristics(). static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, + const Layer &layer, std::vector &result_out) { const Polygons &boundaries = boundary.boundaries; @@ -288,23 +526,31 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin; } std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + + // Search radius should always be at least equals to the value of offset used for computing boundaries. + const float search_radius = 2.f * get_perimeter_spacing(layer); + // When the offset is too big, then original travel doesn't have to cross created boundaries. + // These cases are fixed by calling extend_for_closest_lines. + intersections = extend_for_closest_lines(intersections, boundary, start, end, search_radius); } std::vector result; result.push_back({start, -1}); +#if 0 auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) { const Polygon &poly = boundary.boundaries[intersection.border_idx]; Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast(); Vec2d intersection_vec = (intersection.point - start).cast(); return poly_line.normalized().dot(intersection_vec.normalized()) >= 0; }; +#endif for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { // The entry point to the boundary polygon const Intersection &intersection_first = *it_first; - if(!crossing_boundary_from_inside(start, intersection_first)) - continue; +// if(!crossing_boundary_from_inside(start, intersection_first)) +// continue; // Skip the it_first from the search for the farthest exit point from the boundary polygon auto it_last_item = std::make_reverse_iterator(it_first) - 1; // Search for the farthest intersection different from it_first but with the same border_idx @@ -353,8 +599,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, Line(start, end), result, intersections, - debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, debug_out_path("AvoidCrossingPerimetersInner-initial-%d-%d.svg", layer.id(), iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ @@ -365,7 +610,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo { static int iRun = 0; export_travel_to_svg(boundaries, Line(start, end), result, intersections, - debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); + debug_out_path("AvoidCrossingPerimetersInner-final-%d-%d.svg", layer.id(), iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ @@ -377,17 +622,18 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, + const Layer &layer, Polyline &result_out) { // Travel line is completely or partially inside the bounding box. std::vector path; - size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); + size_t num_intersections = avoid_perimeters_inner(boundary, start, end, layer, path); result_out = to_polyline(path); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d-%d.svg", layer.id(), iRun ++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ @@ -482,58 +728,6 @@ static bool need_wipe(const GCode &gcodegen, return wipe_needed; } -// called by get_perimeter_spacing() / get_perimeter_spacing_external() -static inline float get_default_perimeter_spacing(const PrintObject &print_object) -{ - std::vector printing_extruders = print_object.object_extruders(); - assert(!printing_extruders.empty()); - float avg_extruder = 0; - for(unsigned int extruder_id : printing_extruders) - avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); - avg_extruder /= printing_extruders.size(); - return avg_extruder; -} - -// called by get_boundary() -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const LayerRegion *layer_region : layer.regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()); - return perimeter_spacing; -} - -// called by get_boundary_external() -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) - for (const LayerRegion *layer_region : l->regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()); - return perimeter_spacing; -} - // Adds points around all vertices so that the offset affects only small sections around these vertices. static void resample_polygon(Polygon &polygon, double dist_from_vertex) { @@ -795,14 +989,14 @@ static ExPolygons get_boundary(const Layer &layer) const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; auto const *support_layer = dynamic_cast(&layer); - ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); + ExPolygons boundary = union_ex(inner_offset(layer.lslices, 1.5 * perimeter_spacing)); if(support_layer) { #ifdef INCLUDE_SUPPORTS_IN_BOUNDARY - append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset)); + append(boundary, inner_offset(support_layer->support_islands.expolygons, 1.5 * perimeter_spacing)); #endif auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON); if (layer_below) - append(boundary, inner_offset(layer_below->lslices, perimeter_offset)); + append(boundary, inner_offset(layer_below->lslices, 1.5 * perimeter_spacing)); // After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons boundary = union_ex(boundary); } @@ -868,7 +1062,7 @@ static Polygons get_boundary_external(const Layer &layer) } // Used offset_ex for cases when another object will be in the hole of another polygon - boundary = to_polygons(offset_ex(boundary, perimeter_offset)); + boundary = expand(boundary, perimeter_offset); // Reverse all polygons for making normals point from the polygon out. for (Polygon &poly : boundary) poly.reverse(); @@ -925,7 +1119,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & // Trim the travel line by the bounding box. if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { - travel_intersection_count = avoid_perimeters(m_internal, startf.cast(), endf.cast(), result_pl); + travel_intersection_count = avoid_perimeters(m_internal, startf.cast(), endf.cast(), *gcodegen.layer(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } @@ -936,7 +1130,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & // Trim the travel line by the bounding box. if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { - travel_intersection_count = avoid_perimeters(m_external, startf.cast(), endf.cast(), result_pl); + travel_intersection_count = avoid_perimeters(m_external, startf.cast(), endf.cast(), *gcodegen.layer(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index d178e3c89..412822c66 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -35,13 +35,13 @@ public: struct Boundary { // Collection of boundaries used for detection of crossing perimeters for travels - Polygons boundaries; + Polygons boundaries; // Bounding box of boundaries - BoundingBoxf bbox; + BoundingBoxf bbox; // Precomputed distances of all points in boundaries std::vector> boundaries_params; // Used for detection of intersection between line and any polygon from boundaries - EdgeGrid::Grid grid; + EdgeGrid::Grid grid; void clear() { diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 9ca85c728..3dcc121c1 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -35,6 +35,7 @@ void CoolingBuffer::reset(const Vec3d &position) m_current_pos[1] = float(position.y()); m_current_pos[2] = float(position.z()); m_current_pos[4] = float(m_config.travel_speed.value); + m_fan_speed = -1; } struct CoolingLine @@ -689,10 +690,9 @@ std::string CoolingBuffer::apply_layer_cooldown( // Second generate the adjusted G-code. std::string new_gcode; new_gcode.reserve(gcode.size() * 2); - int fan_speed = -1; bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { + auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() { #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; @@ -733,9 +733,9 @@ std::string CoolingBuffer::apply_layer_cooldown( bridge_fan_speed = 0; fan_speed_new = 0; } - if (fan_speed_new != fan_speed) { - fan_speed = fan_speed_new; - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); + if (fan_speed_new != m_fan_speed) { + m_fan_speed = fan_speed_new; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); } }; @@ -759,7 +759,7 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { if (bridge_fan_control) - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 5f49ef455..1fe040518 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -41,6 +41,8 @@ private: // X,Y,Z,E,F std::vector m_axis; std::vector m_current_pos; + // Current known fan speed or -1 if not known yet. + int m_fan_speed; // Cached from GCodeWriter. // Printing extruder IDs, zero based. std::vector m_extruder_ids; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 225a86f37..6121fb7fd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1267,7 +1267,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::functionprocess_gcode_line(line, true); - }); + }, m_result.lines_ends); // Don't post-process the G-code to update time stamps. this->finalize(false); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 8cc22647f..878a9ef58 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print) std::vector deltas(input.points.size(), offset); input.make_counter_clockwise(); out.front() = mittered_offset_path_scaled(input.points, deltas, 3.); - return ClipperPaths_to_Slic3rExPolygons(out); + return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union }; diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 7b106463a..aa04e69f2 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -152,7 +152,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine auto it_end = it; for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) if (*it_end == '\n') - line_end_callback((it_end - buffer.begin()) + 1); + line_end_callback(file_pos + (it_end - buffer.begin()) + 1); // End of line is indicated also if end of file was reached. eol |= eof && it_end == it_bufend; if (eol) { @@ -173,7 +173,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine if (it != it_bufend && *it == '\r') ++ it; if (it != it_bufend && *it == '\n') { - line_end_callback((it - buffer.begin()) + 1); + line_end_callback(file_pos + (it - buffer.begin()) + 1); ++ it; } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index e6bf4b4fc..60440de97 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -188,17 +188,23 @@ public: // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; + // Is there any valid extrusion assigned to this LayerRegion? virtual bool has_extrusions() const { return ! support_fills.empty(); } + // Zero based index of an interface layer, used for alternating direction of interface / contact layers. + size_t interface_id() const { return m_interface_id; } + protected: friend class PrintObject; // 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, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : - Layer(id, object, height, print_z, slice_z) {} + 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) {} virtual ~SupportLayer() = default; + + size_t m_interface_id; }; template diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 356811b74..4dbffe7b0 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -431,9 +431,8 @@ void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_comp for (const Surface &surface : this->slices.surfaces) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - ExPolygons surfaces = to_expolygons(std::move(this->slices.surfaces)); - Polygons tmp = intersection(surfaces, trimming_polygons); - append(tmp, diff(surfaces, offset(offset_ex(surfaces, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step))); + Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); + append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); this->slices.set(union_ex(tmp), stInternal); } diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index b662daa2b..4a33c7c9c 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -40,23 +40,42 @@ template auto get_b(L &&l) { return Traits>::get_b(l) // Distance to the closest point of line. template -double distance_to_squared(const L &line, const Vec, Scalar> &point) +double distance_to_squared(const L &line, const Vec, Scalar> &point, Vec, Scalar> *nearest_point) { const Vec, double> v = (get_b(line) - get_a(line)).template cast(); const Vec, double> va = (point - get_a(line)).template cast(); const double l2 = v.squaredNorm(); // avoid a sqrt - if (l2 == 0.0) + if (l2 == 0.0) { // a == b case + *nearest_point = get_a(line); return va.squaredNorm(); + } // Consider the line extending the segment, parameterized as a + t (b - a). // We find projection of this point onto the line. // It falls where t = [(this-a) . (b-a)] / |b-a|^2 const double t = va.dot(v) / l2; - if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment - else if (t > 1.0) return (point - get_b(line)).template cast().squaredNorm(); // beyond the 'b' end of the segment + if (t < 0.0) { + // beyond the 'a' end of the segment + *nearest_point = get_a(line); + return va.squaredNorm(); + } else if (t > 1.0) { + // beyond the 'b' end of the segment + *nearest_point = get_b(line); + return (point - get_b(line)).template cast().squaredNorm(); + } + + *nearest_point = (get_a(line).template cast() + t * v).template cast>(); return (t * v - va).squaredNorm(); } +// Distance to the closest point of line. +template +double distance_to_squared(const L &line, const Vec, Scalar> &point) +{ + Vec, Scalar> nearest_point; + return distance_to_squared(line, point, &nearest_point); +} + template double distance_to(const L &line, const Vec, Scalar> &point) { @@ -81,6 +100,7 @@ public: bool intersection_infinite(const Line &other, Point* point) const; bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); } + double distance_to_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); } double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); } double perp_distance_to(const Point &point) const; bool parallel_to(double angle) const; diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 25250e234..95daa33a6 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -159,8 +159,9 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) int i = 0; Vec3i facet; for (auto v : vtc) { - if (i > 2) { i = 0; break; } - facet(i++) = v; + int iv = v; + if (i > 2 || iv < 0 || iv >= int(cgalmesh.vertices().size())) { i = 0; break; } + facet(i++) = iv; } if (i == 3) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index a91d43e78..b17ee000a 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1400,7 +1400,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { // Clean up thin projections. They are not printable anyways. - top_ex = offset2_ex(top_ex, - stat.small_region_threshold, + stat.small_region_threshold); + top_ex = opening_ex(top_ex, stat.small_region_threshold); if (! top_ex.empty()) { append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); float offset = 0.f; @@ -1408,8 +1408,7 @@ static inline std::vector> mmu_segmentation_top_and_bott for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) { offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = offset2_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), - - stat.small_region_threshold, + stat.small_region_threshold); + ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); if (last.empty()) break; append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); @@ -1419,7 +1418,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { // Clean up thin projections. They are not printable anyways. - bottom_ex = offset2_ex(bottom_ex, - stat.small_region_threshold, + stat.small_region_threshold); + bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); if (! bottom_ex.empty()) { append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); float offset = 0.f; @@ -1427,8 +1426,7 @@ static inline std::vector> mmu_segmentation_top_and_bott for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) { offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = offset2_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), - - stat.small_region_threshold, + stat.small_region_threshold); + ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); if (last.empty()) break; append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 3190845bd..55d3c6aa5 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -347,10 +347,10 @@ void PerimeterGenerator::process() // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); - ExPolygons expp = offset2_ex( + ExPolygons expp = opening_ex( // medial axis requires non-overlapping geometry diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), - - float(min_width / 2.), float(min_width / 2.)); + float(min_width / 2.)); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygon &ex : expp) ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); @@ -495,7 +495,7 @@ void PerimeterGenerator::process() double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( //FIXME offset2 would be enough and cheaper. - offset2_ex(gaps, - float(min / 2.), float(min / 2.)), + opening_ex(gaps, float(min / 2.)), offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); ThickPolylines polylines; for (const ExPolygon &ex : gaps_ex) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b33e8bda2..03a3d1e57 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1139,7 +1139,7 @@ void Print::_make_wipe_tower() // Insert the new support layer. double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z); //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. - it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); + it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height); ++ it_layer; } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 0056aee33..ac986216e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -300,8 +300,8 @@ public: size_t support_layer_count() const { return m_support_layers.size(); } void clear_support_layers(); SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } - SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); - SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); + SupportLayer* add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z); + SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2917a9a19..b34a6cb58 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1853,7 +1853,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters " "(and thus any ooze will be probably invisible)."); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("ooze_prevention", coBool); def->label = L("Enable"); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f49eddf3e..fd98feff7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -461,15 +461,15 @@ void PrintObject::clear_support_layers() m_support_layers.clear(); } -SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z) +SupportLayer* PrintObject::add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z) { - m_support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1)); + m_support_layers.emplace_back(new SupportLayer(id, interface_id, this, height, print_z, -1)); return m_support_layers.back(); } -SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) +SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z) { - return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); + return m_support_layers.insert(pos, new SupportLayer(id, interface_id, this, height, print_z, slice_z)); } // Called by Print::apply(). @@ -774,7 +774,7 @@ void PrintObject::detect_surfaces_type() ExPolygons upper_slices = interface_shells ? diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); - surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop); + surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection @@ -792,15 +792,15 @@ void PrintObject::detect_surfaces_type() to_polygons(lower_layer->get_region(region_id)->slices.surfaces) : to_polygons(lower_layer->slices); surfaces_append(bottom, - offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset), + opening_ex(diff(layerm->slices.surfaces, lower_slices, true), offset), surface_type_bottom_other); #else // Any surface lying on the void is a true bottom bridge (an overhang) surfaces_append( bottom, - offset2_ex( + opening_ex( diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), - -offset, offset), + offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces // lying on other slices not belonging to this region @@ -809,12 +809,12 @@ void PrintObject::detect_surfaces_type() // on something else, excluding those lying on our own region surfaces_append( bottom, - offset2_ex( + opening_ex( diff_ex( intersection(layerm->slices.surfaces, lower_layer->lslices), // supported lower_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes), - -offset, offset), + offset), stBottom); } #endif @@ -1088,7 +1088,7 @@ void PrintObject::discover_vertical_shells() // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. - polygons_append(cache.holes, offset(offset_ex(layer.lslices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing)); + polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); @@ -1325,7 +1325,7 @@ void PrintObject::discover_vertical_shells() #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); + shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else @@ -1337,7 +1337,7 @@ void PrintObject::discover_vertical_shells() // get a triangle in $too_narrow; if we grow it below then the shell // would have a different shape from the external surface and we'd still // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true); + Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -1453,7 +1453,7 @@ void PrintObject::bridge_over_infill() // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. { float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); + to_bridge_pp = opening(to_bridge_pp, min_width); } if (to_bridge_pp.empty()) continue; @@ -1744,7 +1744,7 @@ void PrintObject::clip_fill_surfaces() for (const LayerRegion *layerm : layer->m_regions) pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); // Append such thick perimeters to the areas that need support - polygons_append(overhangs, offset2(perimeters, -pw, +pw)); + polygons_append(overhangs, opening(perimeters, pw)); } // Find new internal infill. polygons_append(overhangs, std::move(upper_internal)); @@ -1884,7 +1884,7 @@ void PrintObject::discover_horizontal_shells() float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, jtMiter, 5)); + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5)); // Trim the regularized region by the original region. if (! too_narrow.empty()) new_internal_solid = solid = diff(new_internal_solid, too_narrow); @@ -1903,7 +1903,7 @@ void PrintObject::discover_horizontal_shells() // have the same angle, so the next shell would be grown even more and so on. Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -1915,7 +1915,7 @@ void PrintObject::discover_horizontal_shells() polygons_append(internal, to_polygons(surface.expolygon)); polygons_append(new_internal_solid, intersection( - offset(too_narrow, +margin), + expand(too_narrow, +margin), // Discard bridges as they are grown for anchoring and we can't // remove such anchors. (This may happen when a bridge is being // anchored onto a wall where little space remains after the bridge diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 7db0de626..e2844a624 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -393,7 +393,7 @@ static std::vector> slices_to_regions( } } if (merged) - expolygons = offset2_ex(expolygons, float(scale_(EPSILON)), -float(scale_(EPSILON))); + expolygons = closing_ex(expolygons, float(scale_(EPSILON))); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); i = j; } @@ -648,7 +648,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance ByRegion &src = by_region[region_id]; if (src.needs_merge) // Multiple regions were merged into one. - src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON))); + src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON))); layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); } } diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index bbc6b03fa..c32da0431 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -186,8 +186,8 @@ static std::vector make_layers( // Produce 2 bands around the island, a safe band for dangling overhangs // and an unsafe band for sloped overhangs. // These masks include the original island - auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); - auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); + auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); + auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare); // Absolutely hopeless overhangs are those outside the unsafe band top.overhangs = diff_ex(*top.polygon, overh_mask); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 9a5638c01..a668a385b 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -367,6 +367,29 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object // Object is printed with the same extruder as the support. m_support_params.can_merge_support_regions = true; } + + + m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); + m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); + m_support_params.interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); + m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing); + m_support_params.support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); + m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing); + if (m_object_config->support_material_interface_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + m_support_params.interface_spacing = m_support_params.support_spacing; + m_support_params.interface_density = m_support_params.support_density; + } + + SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; + m_support_params.with_sheath = m_object_config->support_material_with_sheath; + m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (m_support_params.support_density > 0.95 ? ipRectilinear : ipSupportBase); + m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); + m_support_params.contact_fill_pattern = + (m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || + m_object_config->support_material_interface_pattern == smipConcentric ? + ipConcentric : + (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); } // Using the std::deque as an allocator. @@ -397,6 +420,11 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr dst.insert(dst.end(), src.begin(), src.end()); } +// Support layer that is covered by some form of dense interface. +static constexpr const std::initializer_list support_types_interface { + PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface +}; + void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -546,6 +574,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // 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. @@ -557,17 +586,43 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) 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) { MyLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) - empty = false; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == 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. - object.add_support_layer(layer_id ++, height_min, zavg); + 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; } @@ -883,7 +938,14 @@ public: // Merge the support polygons by applying morphological closing and inwards smoothing. auto closing_distance = scaled(m_support_material_closing_radius); auto smoothing_distance = scaled(m_extrusion_width); - return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance); +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z), + { { { diff_ex(expand(*m_support_polygons, closing_distance), closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "closed", "blue", 0.5f } }, + { { union_ex(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance)) }, { "regularized", "red", "black", "", scaled(0.1f), 0.5f } }, + { { union_ex(*m_support_polygons) }, { "src", "green", 0.5f } }, + }); +#endif /* SLIC3R_DEBUG */ + return smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance); } assert(false); return Polygons(); @@ -1250,7 +1312,7 @@ namespace SupportMaterialInternal { Polygons bridges; { // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. - Polygons lower_grown_slices = offset(lower_layer_polygons, + Polygons lower_grown_slices = expand(lower_layer_polygons, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); @@ -1414,7 +1476,7 @@ static inline std::tuple detect_overhangs( overhang_polygons = to_polygons(layer.lslices); #endif // Expand for better stability. - contact_polygons = offset(overhang_polygons, scaled(object_config.raft_expansion.value)); + contact_polygons = expand(overhang_polygons, scaled(object_config.raft_expansion.value)); } else if (! layer.regions().empty()) { @@ -1475,20 +1537,20 @@ static inline std::tuple detect_overhangs( //FIXME cache the lower layer offset if this layer has multiple regions. #if 0 //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 - diff_polygons = offset2( + diff_polygons = opening( diff(layerm_polygons, // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they // are not supporting this layer. // However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports. // For example, see GH issue #3094 - offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + opening(lower_layer_polygons, 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This opening is targeted to reduce very thin regions to support, but it may lead to // no support at all for not so steep overhangs. - - 0.1f * fw, 0.1f * fw); + 0.1f * fw); #else diff_polygons = diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #endif if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { // Don't support overhangs above the top surfaces. @@ -1500,7 +1562,7 @@ static inline std::tuple detect_overhangs( // This is done to increase size of the supporting columns below, as they are calculated by // propagating these contact surfaces downwards. diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), lower_layer_polygons); } //FIXME add user defined filtering here based on minimal area or minimum radius or whatever. @@ -1516,7 +1578,7 @@ static inline std::tuple detect_overhangs( // Subtracting them as they are may leave unwanted narrow // residues of diff_polygons that would then be supported. diff_polygons = diff(diff_polygons, - offset(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); + expand(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); } #ifdef SLIC3R_DEBUG @@ -1588,7 +1650,7 @@ static inline std::tuple detect_overhangs( #endif // SLIC3R_DEBUG enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - offset(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, @@ -1641,15 +1703,18 @@ static inline std::pair 1 ? slicing_params.raft_interface_top_z + support_layer_height_min + EPSILON : slicing_params.first_print_layer_height - EPSILON; - if (print_z < min_print_z) { + if (print_z < slicing_params.first_print_layer_height - EPSILON) { // This contact layer is below the first layer height, therefore not printable. Don't support this surface. return std::pair(nullptr, nullptr); - } else if (print_z < slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - print_z = slicing_params.first_print_layer_height; - bottom_z = 0; - height = slicing_params.first_print_layer_height; + } + const bool has_raft = slicing_params.raft_layers() > 1; + const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height; + if (print_z < min_print_z + support_layer_height_min) { + // Align the layer with the 1st layer height or the raft contact layer. + // With raft active, any contact layer below the raft_contact_top_z will be brought to raft_contact_top_z to extend the raft area. + print_z = min_print_z; + bottom_z = has_raft ? slicing_params.raft_interface_top_z : 0; + height = has_raft ? slicing_params.contact_raft_layer_height : min_print_z; } else { // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and // its height will be set adaptively later on. @@ -1665,9 +1730,9 @@ static inline std::pair= min_print_z) { // Not below the first layer height means this layer is printable. - if (print_z < slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - bridging_print_z = slicing_params.first_print_layer_height; + if (print_z < min_print_z + support_layer_height_min) { + // Align the layer with the 1st layer height or the raft contact layer. + bridging_print_z = min_print_z; } if (bridging_print_z < print_z - EPSILON) { // Allocate the new layer. @@ -1720,7 +1785,7 @@ static inline void fill_contact_layer( if (lower_layer_polygons_for_dense_interface_cache.empty()) lower_layer_polygons_for_dense_interface_cache = //FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height. - offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); + opening(lower_layer_polygons, no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); return lower_layer_polygons_for_dense_interface_cache; }; @@ -1733,7 +1798,7 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG )); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - bool reduce_interfaces = layer_id > 0 && ! slicing_params.soluble_interface; + bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; if (reduce_interfaces) { // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); @@ -1741,7 +1806,7 @@ static inline void fill_contact_layer( dense_interface_polygons = diff( // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), + expand(dense_interface_polygons, no_interface_offset * 0.1f), slices_margin.polygons); // Support islands, to be stretched into a grid. //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, @@ -1799,7 +1864,7 @@ static inline void fill_contact_layer( dense_interface_polygons = diff( // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), + expand(dense_interface_polygons, no_interface_offset * 0.1f), slices_margin.all_polygons); // Support islands, to be stretched into a grid. //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, @@ -1937,7 +2002,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ ); // Now apply the contact areas to the layer where they need to be made. - if (! contact_polygons.empty()) { + if (! contact_polygons.empty() || ! overhang_polygons.empty()) { // Allocate the two empty layers. auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); if (new_layer) { @@ -2041,8 +2106,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( layer_new.idx_object_layer_below = layer_id; layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. - //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. - layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); + layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! slicing_params.soluble_interface) { // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, @@ -2081,7 +2145,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? - touching = offset(touching, float(SCALED_EPSILON)); + touching = expand(touching, float(SCALED_EPSILON)); for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers()[layer_id_above]; if (layer_above.print_z > layer_new.print_z - EPSILON) @@ -2249,7 +2313,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta #endif // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. // Use a slight positive offset to overlap the touching regions. - polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON))); + polygons_append(polygons_new, expand(*top_contact.overhang_polygons, float(SCALED_EPSILON))); polygons_append(overhangs_projection, union_(polygons_new)); polygons_append(enforcers_projection, enforcers_new); } @@ -2478,14 +2542,16 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. // Intermediate layers are always printed with a normal etrusion flow (non-bridging). size_t idx_layer_object = 0; - for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) { + size_t idx_extreme_first = 0; + if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) { + // This is a raft contact layer, its height has been decided in this->top_contact_layers(). + // Ignore this layer when calculating the intermediate support layers. + assert(extremes.front()->layer_type == sltTopContact); + ++ idx_extreme_first; + } + for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) { MyLayer *extr2 = extremes[idx_extreme]; coordf_t extr2z = extr2->extreme_z(); - if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) { - // This is a raft contact layer, its height has been decided in this->top_contact_layers(). - assert(extr2->layer_type == sltTopContact); - continue; - } if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). assert(extr2->layer_type == sltTopContact); @@ -2502,7 +2568,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int } assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); - MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1]; + MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; // Fuse a support layer firmly to the raft top interface (not to the raft contacts). coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); assert(extr2z >= extr1z); @@ -2736,7 +2802,6 @@ void PrintObjectSupportMaterial::generate_base_layers( ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons layer_intermediate.layer_type = sltBase; - // For snug supports, expand the interfaces into the intermediate layer to make it printable. #if 0 // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); // Fillet the base polygons and trim them again with the top, interface and contact layers. @@ -2868,7 +2933,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf if (brim_inner) { Polygons holes = ex.holes; polygons_reverse(holes); - holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1))); + holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); polygons_reverse(holes); polygons_append(brim, std::move(holes)); } else @@ -2900,11 +2965,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf Polygons interface_polygons; if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + 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, offset(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Output vector. MyLayersPtr raft_layers; @@ -2931,7 +2996,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.height = m_slicing_params.first_print_layer_height; new_layer.bottom_z = 0.; - new_layer.polygons = inflate_factor_1st_layer > 0 ? offset(base, inflate_factor_1st_layer) : base; + 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 < m_slicing_params.base_raft_layers; ++ i) { @@ -2965,7 +3030,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; for (int i = 0; i < nsteps; ++ i) - raft = diff(offset(raft, step), trimming); + raft = diff(expand(raft, step), trimming); } else raft = diff(raft, trimming); if (contacts != nullptr) @@ -3028,26 +3093,43 @@ std::pair(m_object_config->support_material_closing_radius.value); tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, &layer_storage_mutex](MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) { + auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* { assert(! bottom.empty() || ! top.empty()); - MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); - 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; // Merge top into bottom, unite them with a safety offset. append(bottom, std::move(top)); - layer_new.polygons = intersection(union_safety_offset(std::move(bottom)), intermediate_layer.polygons); - // 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; + // 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 (! 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()) { + MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, 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, &intermediate_layers, &insert_layer, @@ -3196,7 +3278,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( return; if (! with_sheath) { - fill_expolygons_generate_paths(dst, offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)), filler, density, role, flow); + fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); return; } @@ -3208,7 +3290,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( // 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 : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) { + 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) { @@ -3461,10 +3543,10 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const // make more loops Polygons loop_polygons = loops0; for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - offset2( + polygons_append(loop_polygons, + opening( loops0, - - i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), + 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. @@ -3476,7 +3558,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const map_split_points[it->first_point()] = -1; loop_lines.push_back(it->split_at_first_point()); } - loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + 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) { @@ -3816,27 +3898,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; - float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); - float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); - coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); - coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing); - coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); - coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing); - if (m_object_config->support_material_interface_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - interface_spacing = support_spacing; - interface_density = support_density; - } - - // Prepare fillers. - SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; - bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density > 0.95 ? ipRectilinear : ipSupportBase); - std::vector angles; - angles.push_back(base_angle); - - if (support_pattern == smpRectilinearGrid) - angles.push_back(interface_angle); + std::vector angles { m_support_params.base_angle }; + if (m_object_config->support_material_pattern == smpRectilinearGrid) + angles.push_back(m_support_params.interface_angle); BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); @@ -3848,16 +3912,16 @@ void PrintObjectSupportMaterial::generate_toolpaths( float raft_angle_interface = 0.f; if (m_slicing_params.base_raft_layers > 1) { // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = interface_angle; - raft_angle_base = base_angle; - raft_angle_interface = interface_angle; + raft_angle_1st_layer = m_support_params.interface_angle; + raft_angle_base = m_support_params.base_angle; + raft_angle_interface = m_support_params.interface_angle; } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { // 1st layer, interface & contact layers available. - raft_angle_1st_layer = base_angle; + raft_angle_1st_layer = m_support_params.base_angle; if (this->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 = interface_angle; + raft_angle_interface = m_support_params.interface_angle; } else if (m_slicing_params.interface_raft_layers == 1) { // Only the contact raft layer is non-empty, which will be printed as the 1st layer. assert(m_slicing_params.base_raft_layers == 0); @@ -3874,7 +3938,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [this, &support_layers, &raft_layers, - infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath] + &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) { @@ -3883,8 +3947,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(support_layer.support_fills.entities.empty()); MyLayer &raft_layer = *raft_layers[support_layer_id]; - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(ipRectilinear)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); @@ -3900,17 +3964,17 @@ void PrintObjectSupportMaterial::generate_toolpaths( Fill * filler = filler_support.get(); filler->angle = raft_angle_base; filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_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_density), + filler, float(m_support_params.support_density), // Extrusion parameters erSupportMaterial, flow, - with_sheath, false); + m_support_params.with_sheath, false); } } @@ -3929,7 +3993,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler->spacing = m_support_params.support_material_flow.spacing(); assert(! raft_layer.bridging); flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - density = float(interface_density); + density = float(m_support_params.interface_density); } else continue; filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); @@ -3970,15 +4034,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( }; std::vector layer_caches(support_layers.size()); - - const auto fill_type_interface = - (m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_material_interface_pattern == smipConcentric ? - ipConcentric : ipRectilinear; - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - infill_pattern, &bbox_object, support_density, fill_type_interface, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath] + &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); @@ -3987,14 +4045,14 @@ void PrintObjectSupportMaterial::generate_toolpaths( 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(fill_type_interface)); + auto filler_interface = std::unique_ptr(Fill::new_from_type(m_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 && fill_type_interface != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && m_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(ipRectilinear)); - auto filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : Fill::new_from_type(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(m_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); @@ -4005,6 +4063,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; + float interface_angle_delta = m_object_config->support_material_style.value == smsSnug ? + (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : + 0; // Find polygons with the same print_z. MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; @@ -4084,8 +4145,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( // 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. - interface_angle; - double density = interface_as_base ? support_density : interface_density; + m_support_params.interface_angle + interface_angle_delta; + double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density; filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( @@ -4106,9 +4167,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( // 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 = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = interface_angle; + filler->angle = m_support_params.interface_angle + interface_angle_delta; filler->spacing = m_support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density)); fill_expolygons_generate_paths( // Destination base_interface_layer.extrusions, @@ -4116,7 +4177,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Regions to fill union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), // Filler and its parameters - filler, float(interface_density), + filler, float(m_support_params.interface_density), // Extrusion parameters erSupportMaterial, interface_flow); } @@ -4130,9 +4191,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(! base_layer.layer->bridging); auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); - float density = float(support_density); - bool sheath = with_sheath; + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); + float density = float(m_support_params.support_density); + bool sheath = m_support_params.with_sheath; bool no_sort = false; if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index a1bd81297..65604ef72 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -132,6 +132,18 @@ public: // coordf_t support_layer_height_max; coordf_t gap_xy; + + float base_angle; + float interface_angle; + coordf_t interface_spacing; + coordf_t interface_density; + coordf_t support_spacing; + coordf_t support_density; + + InfillPattern base_fill_pattern; + InfillPattern interface_fill_pattern; + InfillPattern contact_fill_pattern; + bool with_sheath; }; // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 106da4a78..4e7bd073a 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -208,7 +208,7 @@ void name_tbb_thread_pool_threads_set_locale() nthreads = 1; #endif - std::atomic nthreads_running(0); + size_t nthreads_running(0); std::condition_variable cv; std::mutex cv_m; auto master_thread_id = std::this_thread::get_id(); @@ -216,13 +216,13 @@ void name_tbb_thread_pool_threads_set_locale() tbb::blocked_range(0, nthreads, 1), [&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range &range) { assert(range.begin() + 1 == range.end()); - if (nthreads_running.fetch_add(1) + 1 == nthreads) { + if (std::unique_lock lk(cv_m); ++nthreads_running == nthreads) { + lk.unlock(); // All threads are spinning. // Wake them up. cv.notify_all(); } else { // Wait for the last thread to wake the others. - std::unique_lock lk(cv_m); cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;}); } auto thread_id = std::this_thread::get_id(); diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index d3c9a49b5..0f3d0f5b6 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1617,7 +1617,7 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c /* The following line is commented out because it can generate wrong polygons, see for example issue #661 */ - //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); + //ExPolygons ex_slices = closing(p_slices, safety_offset); #ifdef SLIC3R_TRIANGLEMESH_DEBUG size_t holes_count = 0; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 987ef1c0a..f65292f03 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -127,7 +127,8 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, float radius, CursorType cursor_type, EnforcerBlockerType new_state, - const Transform3d& trafo, bool triangle_splitting) + const Transform3d& trafo, const Transform3d& trafo_no_translate, + bool triangle_splitting, float highlight_by_angle_deg) { assert(facet_start < m_orig_size_indices); @@ -143,6 +144,9 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, m_old_cursor_radius_sqr = m_cursor.radius_sqr; } + const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); + Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); + // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check; facets_to_check.reserve(16); @@ -153,14 +157,14 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // Head of the bread-first facets_to_check FIFO. int facet_idx = 0; while (facet_idx < int(facets_to_check.size())) { - int facet = facets_to_check[facet_idx]; - if (! visited[facet]) { + int facet = facets_to_check[facet_idx]; + const Vec3f &facet_normal = m_face_normals[m_triangles[facet].source_triangle]; + if (!visited[facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) { if (select_triangle(facet, new_state, triangle_splitting)) { - // add neighboring facets to list to be proccessed later - for (int neighbor_idx : m_neighbors[facet]) { - if (neighbor_idx >=0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) + // add neighboring facets to list to be processed later + for (int neighbor_idx : m_neighbors[facet]) + if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) facets_to_check.push_back(neighbor_idx); - } } } visited[facet] = true; @@ -168,7 +172,10 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, } } -void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle, bool force_reselection) +void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, + const Transform3d& trafo_no_translate, + float seed_fill_angle, float highlight_by_angle_deg, + bool force_reselection) { assert(facet_start < m_orig_size_indices); @@ -182,14 +189,17 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st std::queue facet_queue; facet_queue.push(facet_start); - const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); + Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. while (!facet_queue.empty()) { int current_facet = facet_queue.front(); facet_queue.pop(); - if (!visited[current_facet]) { + const Vec3f &facet_normal = m_face_normals[m_triangles[current_facet].source_triangle]; + if (!visited[current_facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) { if (m_triangles[current_facet].is_split()) { for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) { assert(split_triangle_idx < int(m_triangles[current_facet].children.size())); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 05a78e2ee..b3c468a6e 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -45,12 +45,16 @@ public: CursorType type, // current type of cursor EnforcerBlockerType new_state, // enforcer or blocker? const Transform3d &trafo, // matrix to get from mesh to world - bool triangle_splitting); // If triangles will be split base on the cursor or not + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + bool triangle_splitting, // If triangles will be split base on the cursor or not + float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - void seed_fill_select_triangles(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - float seed_fill_angle, // the maximal angle between two facets to be painted by the same color - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void seed_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + float seed_fill_angle, // the maximal angle between two facets to be painted by the same color + float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle void bucket_fill_select_triangles(const Vec3f &hit, // point where to start int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index a3613edcf..26a64fdb7 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -334,19 +334,7 @@ void GLVolume::SinkingContours::update() MeshSlicingParams slicing_params; slicing_params.trafo = m_parent.world_matrix(); Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); - for (Polygon& polygon : polygons) { - if (polygon.is_clockwise()) - polygon.reverse(); - Polygons outer_polys = offset(polygon, float(scale_(HalfWidth))); - assert(outer_polys.size() == 1); - if (outer_polys.empty()) - // no outer contour, skip - continue; - - ExPolygon expoly(std::move(outer_polys.front())); - expoly.holes = offset(polygon, -float(scale_(HalfWidth))); - polygons_reverse(expoly.holes); - + for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { GUI::GLModel::InitializationData::Entity entity; entity.type = GUI::GLModel::PrimitiveType::Triangles; const std::vector triangulation = triangulate_expolygon_3d(expoly); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d3c24694c..a18f1ec07 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -456,7 +456,7 @@ private: GLGizmosManager m_gizmos; GLToolbar m_main_toolbar; GLToolbar m_undoredo_toolbar; - ClippingPlane m_clipping_planes[2]; + std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; SlaCap m_sla_caps[2]; @@ -661,6 +661,9 @@ public: void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); } void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; } + bool get_use_clipping_planes() const { return m_use_clipping_planes; } + const std::array &get_clipping_planes() const { return m_clipping_planes; }; + void set_color_by(const std::string& value); void refresh_camera_scene_box(); diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index bf3ee7eee..0214652b2 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -61,39 +61,28 @@ std::pair GLShadersManager::init() // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor - // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. - // Because of this, objects had darker colors inside the multi-material gizmo. - // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (platform_flavor() == PlatformFlavor::OSXOnArm) - valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" }, { "FLIP_TRIANGLE_NORMALS"sv -#if ENABLE_ENVIRONMENT_MAP - , "ENABLE_ENVIRONMENT_MAP"sv -#endif - }); - else - valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" } -#if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } -#endif + // When setting this technology to default rename the following from "gouraud_mod" to "gouraud" + valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" } #else - if (platform_flavor() == PlatformFlavor::OSXOnArm) - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv -#if ENABLE_ENVIRONMENT_MAP - , "ENABLE_ENVIRONMENT_MAP"sv -#endif - }); - else - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } -#if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } -#endif + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#if ENABLE_ENVIRONMENT_MAP + , { "ENABLE_ENVIRONMENT_MAP"sv } +#endif // ENABLE_ENVIRONMENT_MAP ); // used to render variable layers heights in 3d editor valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); // used to render highlight contour around selected triangles inside the multi-material gizmo valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" }); + // Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader. + // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. + // Because of this, objects had darker colors inside the multi-material gizmo. + // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. + if (platform_flavor() == PlatformFlavor::OSXOnArm) + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv}); + else + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}); return { valid, error }; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a19858eea..7d6940bd0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -676,16 +676,18 @@ void GUI_App::post_init() if (this->preset_updater) { this->check_updates(false); CallAfter([this] { - this->config_wizard_startup(); + bool cw_showed = this->config_wizard_startup(); this->preset_updater->slic3r_update_notify(); this->preset_updater->sync(preset_bundle); + if (! cw_showed) { + // The CallAfter is needed as well, without it, GL extensions did not show. + // Also, we only want to show this when the wizard does not, so the new user + // sees something else than "we want something" on the first start. + show_send_system_info_dialog_if_needed(); + } }); } - // 'Send system info' dialog. Again, a CallAfter is needed on mac. - // Without it, GL extensions did not show. - CallAfter([] { show_send_system_info_dialog_if_needed(); }); - #ifdef _WIN32 // Sets window property to mainframe so other instances can indentify it. OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); @@ -2020,14 +2022,14 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } -void GUI_App::open_preferences(size_t open_on_tab) +void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) { bool app_layout_changed = false; { // the dialog needs to be destroyed before the call to recreate_GUI() // or sometimes the application crashes into wxDialogBase() destructor // so we put it into an inner scope - PreferencesDialog dlg(mainframe, open_on_tab); + PreferencesDialog dlg(mainframe, open_on_tab, highlight_option); dlg.ShowModal(); app_layout_changed = dlg.settings_layout_changed(); #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3061bbe13..d350da969 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -259,7 +259,7 @@ public: wxString current_language_code_safe() const; bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } - void open_preferences(size_t open_on_tab = 0); + void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); virtual bool OnExceptionInMainLoop() override; // Calls wxLaunchDefaultBrowser if user confirms in dialog. diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index fef131b88..f5e3083a7 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -77,8 +77,10 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()), wxLC_ICON | wxSIMPLE_BORDER); - m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); - m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); + m_list_ctrl->Bind(wxEVT_LIST_KEY_DOWN, &GalleryDialog::key_down, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &GalleryDialog::show_context_menu, this); m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) { m_selected_items.clear(); select(event); @@ -111,19 +113,11 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) this->Bind(wxEVT_BUTTON, method, this, ID); }; - auto enable_del_fn = [this]() { - if (m_selected_items.empty()) - return false; - for (const Item& item : m_selected_items) - if (item.is_system) - return false; - return true; - }; - - add_btn(0, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes); - add_btn(1, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, enable_del_fn); - add_btn(2, ID_BTN_REPLACE_CUSTOM_PNG, _L("Replace PNG"), _L("Replace PNG for custom shape. You can't raplace PNG for system shape"),&GalleryDialog::replace_custom_png, [this]() { return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); }); - buttons->InsertStretchSpacer(3, 2* BORDER_W); + size_t btn_pos = 0; + add_btn(btn_pos++, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes); + add_btn(btn_pos++, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, [this](){ return can_delete(); }); + //add_btn(btn_pos++, ID_BTN_REPLACE_CUSTOM_PNG, _L("Change thumbnail"), _L("Replace PNG for custom shape. You can't raplace thimbnail for system shape"), &GalleryDialog::change_thumbnail, [this](){ return can_change_thumbnail(); }); + buttons->InsertStretchSpacer(btn_pos, 2* BORDER_W); load_label_icon_list(); @@ -146,6 +140,21 @@ GalleryDialog::~GalleryDialog() { } +bool GalleryDialog::can_delete() +{ + if (m_selected_items.empty()) + return false; + for (const Item& item : m_selected_items) + if (item.is_system) + return false; + return true; +} + +bool GalleryDialog::can_change_thumbnail() +{ + return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); +} + void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -405,7 +414,7 @@ void GalleryDialog::add_custom_shapes(wxEvent& event) load_files(input_files); } -void GalleryDialog::del_custom_shapes(wxEvent& event) +void GalleryDialog::del_custom_shapes() { auto custom_dir = get_dir(false); @@ -438,7 +447,7 @@ static void show_warning(const wxString& title, const std::string& error_file_ty dialog.ShowModal(); } -void GalleryDialog::replace_custom_png(wxEvent& event) +void GalleryDialog::change_thumbnail() { if (m_selected_items.size() != 1 || m_selected_items[0].is_system) return; @@ -494,6 +503,23 @@ void GalleryDialog::deselect(wxListEvent& event) m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; })); } +void GalleryDialog::show_context_menu(wxListEvent& event) +{ + wxMenu* menu = new wxMenu(); + if (can_delete()) + append_menu_item(menu, wxID_ANY, _L("Delete"), "", [this](wxCommandEvent&) { del_custom_shapes(); }); + if (can_change_thumbnail()) + append_menu_item(menu, wxID_ANY, _L("Change thumbnail"), "", [this](wxCommandEvent&) { change_thumbnail(); }); + + this->PopupMenu(menu); +} + +void GalleryDialog::key_down(wxListEvent& event) +{ + if (can_delete() && (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK)) + del_custom_shapes(); +} + void GalleryDialog::update() { m_selected_items.clear(); diff --git a/src/slic3r/GUI/GalleryDialog.hpp b/src/slic3r/GUI/GalleryDialog.hpp index 8cf3f0096..2aa04ffa2 100644 --- a/src/slic3r/GUI/GalleryDialog.hpp +++ b/src/slic3r/GUI/GalleryDialog.hpp @@ -33,10 +33,17 @@ class GalleryDialog : public DPIDialog void load_label_icon_list(); void add_custom_shapes(wxEvent& event); - void del_custom_shapes(wxEvent& event); - void replace_custom_png(wxEvent& event); + void del_custom_shapes(); + void del_custom_shapes(wxEvent& event) { del_custom_shapes(); } + void change_thumbnail(); + void change_thumbnail(wxEvent& event) { change_thumbnail(); } void select(wxListEvent& event); void deselect(wxListEvent& event); + void show_context_menu(wxListEvent& event); + void key_down(wxListEvent& event); + + bool can_delete(); + bool can_change_thumbnail(); void update(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 12b827e64..16856a9e3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/format.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -20,7 +21,7 @@ namespace Slic3r::GUI { void GLGizmoFdmSupports::on_shutdown() { - m_angle_threshold_deg = 0.f; + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); m_parent.toggle_model_objects_visibility(true); } @@ -52,7 +53,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); m_desc["pointer"] = _L("Triangles"); - m_desc["highlight_by_angle"] = _L("Highlight by angle"); + m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); m_desc["enforce_button"] = _L("Enforce"); m_desc["cancel"] = _L("Cancel"); @@ -62,6 +63,9 @@ bool GLGizmoFdmSupports::on_init() m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["split_triangles"] = _L("Split triangles"); + m_desc["on_overhangs_only"] = _L("On overhangs only"); + return true; } @@ -89,18 +93,19 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(20.5f); + const float approx_height = m_imgui->scaled(23.f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.f); - const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); + const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); @@ -116,6 +121,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); + float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"enforce", "block", "remove"}) { @@ -125,10 +133,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); - float sliders_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); - float window_width = minimal_slider_width + sliders_width; + float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); + float window_width = minimal_slider_width + sliders_left_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); + window_width = std::max(window_width, on_overhangs_only_checkbox_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); @@ -144,38 +154,53 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); + float position_before_text_y = ImGui::GetCursorPos().y; ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["highlight_by_angle"] + ":"); + m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); ImGui::AlignTextToFramePadding(); + float position_after_text_y = ImGui::GetCursorPos().y; std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo," "placed after the number with no whitespace in between."); - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); - if (m_imgui->slider_float("##angle_threshold_deg", &m_angle_threshold_deg, 0.f, 90.f, format_str.data())) { - m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); + + float slider_height = m_imgui->get_slider_float_height(); + // Makes slider to be aligned to bottom of the multi-line text. + float slider_start_position = std::max(position_before_text_y, position_after_text_y - slider_height); + ImGui::SetCursorPosY(slider_start_position); + + if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) { + m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); if (! m_parent.is_using_slope()) { m_parent.use_slope(true); m_parent.set_as_dirty(); } } - m_imgui->disabled_begin(m_angle_threshold_deg == 0.f); + // Restores the cursor position to be below the multi-line text. + ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " + "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]), max_tooltip_width); + + m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); ImGui::NewLine(); ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { - select_facets_by_angle(m_angle_threshold_deg, false); - m_angle_threshold_deg = 0.f; + select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } ImGui::SameLine(window_width - buttons_width); if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { - m_angle_threshold_deg = 0.f; + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } m_imgui->disabled_end(); - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::Separator(); @@ -188,26 +213,20 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) m_tool_type = ToolType::BRUSH; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) m_tool_type = ToolType::SMART_FILL; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + + m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); ImGui::Separator(); @@ -221,13 +240,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); @@ -235,13 +249,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_pointer); @@ -249,61 +258,40 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) m_cursor_type = TriangleSelector::CursorType::POINTER; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); - m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); m_imgui->disabled_end(); } else { assert(m_tool_type == ToolType::SMART_FILL); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["smart_fill_angle"] + ":"); - std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," - "placed after the number with no whitespace in between."); - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); + + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); } ImGui::Separator(); @@ -319,19 +307,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } } - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); auto clp_dist = float(m_c->object_clipper()->get_position()); if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 0fb03140a..4929714a2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -35,7 +35,6 @@ private: PainterGizmoType get_painter_type() const override; void select_facets_by_angle(float threshold, bool block); - float m_angle_threshold_deg = 0.f; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 17630e5c6..f7feed44a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -547,13 +547,9 @@ RENDER_AGAIN: ImGui::SameLine(settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left); m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[0].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width); + bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider @@ -563,13 +559,9 @@ RENDER_AGAIN: m_imgui->text(m_desc.at("quality")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[1].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width); + slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); @@ -580,13 +572,9 @@ RENDER_AGAIN: m_imgui->text(m_desc.at("closing_distance")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[2].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width); + slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 557ac6dcc..ee15dab88 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -129,6 +129,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["tool_bucket_fill"] = _L("Bucket fill"); m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["split_triangles"] = _L("Split triangles"); init_extruders_data(); @@ -142,7 +143,7 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_triangles(selection, false); + render_triangles(selection); m_c->object_clipper()->render_cut(); m_c->instances_hider()->render_cut(); @@ -173,6 +174,43 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) } } +void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const +{ + ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data(); + auto *shader = wxGetApp().get_shader("mm_gouraud"); + if (!shader) + return; + shader->start_using(); + shader->set_uniform("clipping_plane", clp_data.clp_dataf); + shader->set_uniform("z_range", clp_data.z_range); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix(); + + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); + + shader->set_uniform("volume_world_matrix", trafo_matrix); + m_triangle_selectors[mesh_id]->render(m_imgui); + + glsafe(::glPopMatrix()); + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + } +} + static void render_extruders_combo(const std::string &label, const std::vector &extruders, const std::vector> &extruders_colors, @@ -261,6 +299,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"first_color", "second_color", "remove"}) { @@ -274,6 +314,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott float window_width = minimal_slider_width + sliders_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); @@ -331,13 +372,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); @@ -349,13 +385,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_bucket_fill); @@ -367,13 +398,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets that have the same color."), max_tooltip_width); ImGui::Separator(); @@ -387,13 +413,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); @@ -401,13 +422,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_pointer); @@ -415,13 +431,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) m_cursor_type = TriangleSelector::CursorType::POINTER; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); @@ -430,23 +441,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); - m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); m_imgui->disabled_end(); @@ -464,13 +465,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott triangle_selector->request_update_render_data(); } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); ImGui::Separator(); } @@ -490,13 +486,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { @@ -600,13 +591,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) auto *shader = wxGetApp().get_current_shader(); if (!shader) return; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - assert(shader->get_name() == "gouraud_mod"); -#else - assert(shader->get_name() == "gouraud"); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);}); - shader->set_uniform("compute_triangle_normals_in_fs", true); + assert(shader->get_name() == "mm_gouraud"); for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx) if (m_gizmo_scene.has_VBOs(color_idx)) { @@ -619,7 +604,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) } if (m_paint_contour.has_VBO()) { - ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); + ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); }); shader->stop_using(); auto *contour_shader = wxGetApp().get_shader("mm_contour"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 604edf64d..0991527f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -89,6 +89,8 @@ public: void set_painter_gizmo_data(const Selection& selection) override; + void render_triangles(const Selection& selection) const override; + // TriangleSelector::serialization/deserialization has a limit to store 19 different states. // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. // When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 272030177..8cfb3fbca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -43,36 +43,33 @@ void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) } } - - -void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool use_polygon_offset_fill) const +GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const { - const ModelObject* mo = m_c->selection_info()->model_object(); - - ScopeGuard offset_fill_guard([&use_polygon_offset_fill]() { - if (use_polygon_offset_fill) - glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); - }); - if (use_polygon_offset_fill) { - glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); - glsafe(::glPolygonOffset(-5.0, -5.0)); - } - + ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; // Take care of the clipping plane. The normal of the clipping plane is // saved with opposite sign than we need to pass to OpenGL (FIXME) - bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; - float clp_dataf[4] = {0.f, 0.f, 1.f, FLT_MAX}; - if (clipping_plane_active) { - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - for (size_t i=0; i<3; ++i) - clp_dataf[i] = -1.f * float(clp->get_data()[i]); - clp_dataf[3] = float(clp->get_data()[3]); + if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + for (size_t i = 0; i < 3; ++i) + clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); + clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); } + // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) + if (m_c->get_canvas()->get_use_clipping_planes()) { + const std::array &clps = m_c->get_canvas()->get_clipping_planes(); + clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; + } + + return clp_data_out; +} + +void GLGizmoPainterBase::render_triangles(const Selection& selection) const +{ #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS auto* shader = wxGetApp().get_shader("gouraud_mod"); #else - auto *shader = wxGetApp().get_shader("gouraud"); + auto* shader = wxGetApp().get_shader("gouraud"); #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (! shader) return; @@ -83,10 +80,11 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool #else shader->set_uniform("print_box.actived", false); #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - shader->set_uniform("clipping_plane", clp_dataf, 4); + shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); - int mesh_id = -1; + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; for (const ModelVolume* mv : mo->volumes) { if (! mv->is_model_part()) continue; @@ -265,7 +263,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); m_parent.set_as_dirty(); if (m_rr.mesh_id != -1) { - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } @@ -296,11 +299,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); } - const Camera &camera = wxGetApp().plater()->get_camera(); - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d &instance_trafo = mi->get_transformation().get_matrix(); + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); // List of mouse positions that will be used as seeds for painting. std::vector mouse_positions{mouse_position}; @@ -326,10 +330,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Precalculate transformations of individual meshes. std::vector trafo_matrices; - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) + std::vector trafo_matrices_not_translate; + for (const ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); - } + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); + } // Now "click" into all the prepared points and spill paint around them. for (const Vec2d& mp : mouse_positions) { @@ -351,7 +357,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return dragging_while_painting; } - const Transform3d& trafo_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); @@ -360,7 +367,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state); if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true); else if (m_tool_type == ToolType::BUCKET_FILL) @@ -369,7 +377,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_seed_fill_last_mesh_id = -1; } else if (m_tool_type == ToolType::BRUSH) m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type, - new_state, trafo_matrix, m_triangle_splitting_enabled); + new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_last_mouse_click = mouse_position; @@ -382,17 +391,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_triangle_selectors.empty()) return false; - const Camera & camera = wxGetApp().plater()->get_camera(); - const Selection & selection = m_parent.get_selection(); - const ModelObject * mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d & instance_trafo = mi->get_transformation().get_matrix(); + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); // Precalculate transformations of individual meshes. std::vector trafo_matrices; + std::vector trafo_matrices_not_translate; for (const ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) + if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); + } // Now "click" into all the prepared points and spill paint around them. update_raycast_cache(mouse_position, camera, trafo_matrices); @@ -417,9 +430,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if(m_rr.mesh_id != m_seed_fill_last_mesh_id) seed_fill_unselect_all(); + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; + assert(m_rr.mesh_id < int(m_triangle_selectors.size())); if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false); else if (m_tool_type == ToolType::BUCKET_FILL) @@ -576,7 +592,8 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) #else assert(shader->get_name() == "gouraud"); #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - + ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);}); + shader->set_uniform("offset_depth_buffer", true); for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), std::make_pair(&m_iva_blockers, blockers_color)}) { if (iva.first->has_VBOs()) { @@ -602,7 +619,7 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) auto *contour_shader = wxGetApp().get_shader("mm_contour"); contour_shader->start_using(); - glsafe(::glDepthFunc(GL_GEQUAL)); + glsafe(::glDepthFunc(GL_LEQUAL)); m_paint_contour.render(); glsafe(::glDepthFunc(GL_LESS)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index cc15af41f..3093b0bec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -126,7 +126,7 @@ public: virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); protected: - void render_triangles(const Selection& selection, const bool use_polygon_offset_fill = true) const; + virtual void render_triangles(const Selection& selection) const; void render_cursor() const; void render_cursor_circle() const; void render_cursor_sphere(const Transform3d& trafo) const; @@ -159,6 +159,9 @@ protected: ToolType m_tool_type = ToolType::BRUSH; float m_smart_fill_angle = 30.f; + bool m_paint_on_overhangs_only = false; + float m_highlight_by_angle_threshold_deg = 0.f; + static constexpr float SmartFillAngleMin = 0.0f; static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; @@ -173,6 +176,14 @@ protected: Right }; + struct ClippingPlaneDataWrapper + { + std::array clp_dataf; + std::array z_range; + }; + + ClippingPlaneDataWrapper get_clipping_plane_data() const; + private: bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; void update_raycast_cache(const Vec2d& mouse_position, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 2f9d16f90..6971e7cdb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -129,13 +129,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_type")); @@ -146,26 +141,16 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { @@ -186,13 +171,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index c09d67317..c43f215d9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -24,15 +24,16 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, , m_obj_index(0) , m_need_reload(false) , m_show_wireframe(false) - + // translation for GUI size , tr_mesh_name(_u8L("Mesh name")) , tr_triangles(_u8L("Triangles")) , tr_preview(_u8L("Preview")) , tr_detail_level(_u8L("Detail level")) , tr_decimate_ratio(_u8L("Decimate ratio")) - + // for wireframe , m_wireframe_VBO_id(0) , m_wireframe_IBO_id(0) + , m_wireframe_IBO_size(0) {} GLGizmoSimplify::~GLGizmoSimplify() { @@ -41,10 +42,11 @@ GLGizmoSimplify::~GLGizmoSimplify() { free_gpu(); } -bool GLGizmoSimplify::on_init() -{ - //m_grabbers.emplace_back(); - //m_shortcut_key = WXK_CONTROL_C; +bool GLGizmoSimplify::on_esc_key_down() { + if (m_state == State::settings || m_state == State::canceling) + return false; + + m_state = State::canceling; return true; } @@ -53,10 +55,6 @@ std::string GLGizmoSimplify::on_get_name() const return _u8L("Simplify"); } -void GLGizmoSimplify::on_render() { } - -void GLGizmoSimplify::on_render_for_picking() {} - void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { create_gui_cfg(); @@ -143,8 +141,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Separator(); if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) { - m_is_valid_result = false; m_configuration.use_count = !m_configuration.use_count; + live_preview(); } ImGui::SameLine(); m_imgui->disabled_begin(m_configuration.use_count); @@ -160,7 +158,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SetNextItemWidth(m_gui_cfg->input_width); static int reduction = 2; if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) { - m_is_valid_result = false; if (reduction < 0) reduction = 0; if (reduction > 4) reduction = 4; switch (reduction) { @@ -170,12 +167,13 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi case 3: m_configuration.max_error = 0.5f; break; case 4: m_configuration.max_error = 1.f; break; } + live_preview(); } m_imgui->disabled_end(); // !use_count if (ImGui::RadioButton("##use_count", m_configuration.use_count)) { - m_is_valid_result = false; m_configuration.use_count = !m_configuration.use_count; + live_preview(); } ImGui::SameLine(); @@ -192,13 +190,14 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SetNextItemWidth(m_gui_cfg->input_width); const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%": ((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%"); + if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) { - m_is_valid_result = false; if (m_configuration.decimate_ratio < 0.f) m_configuration.decimate_ratio = 0.01f; if (m_configuration.decimate_ratio > 100.f) m_configuration.decimate_ratio = 100.f; m_configuration.fix_count_by_ratio(triangle_count); + live_preview(); } ImGui::NewLine(); @@ -206,43 +205,48 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count); m_imgui->disabled_end(); // use_count - if (ImGui::Checkbox(_L("Show wireframe").c_str(), &m_show_wireframe)) { + if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) { if (m_show_wireframe) init_wireframe(); else free_gpu(); } - if (m_state == State::settings) { - if (m_imgui->button(_L("Cancel"))) { - if (m_original_its.has_value()) { + bool is_canceling = m_state == State::canceling; + m_imgui->disabled_begin(is_canceling); + if (m_imgui->button(_L("Cancel"))) { + if (m_state == State::settings) { + if (m_original_its.has_value()) { set_its(*m_original_its); m_state = State::close_on_end; } else { close(); } + } else { + m_state = State::canceling; } - ImGui::SameLine(m_gui_cfg->bottom_left_width); - if (m_imgui->button(_L("Preview"))) { - m_state = State::preview; - // simplify but not apply on mesh - process(); - } - ImGui::SameLine(); - if (m_imgui->button(_L("Apply"))) { - if (!m_is_valid_result) { - m_state = State::close_on_end; - process(); - } else if (m_exist_preview) { - // use preview and close - after_apply(); - } else { // no changes made - close(); - } - } - } else { - m_imgui->disabled_begin(m_state == State::canceling); - if (m_imgui->button(_L("Cancel"))) m_state = State::canceling; - m_imgui->disabled_end(); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling) + ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str()); + m_imgui->disabled_end(); // state canceling + ImGui::SameLine(); + + bool is_processing = m_state != State::settings; + m_imgui->disabled_begin(is_processing); + if (m_imgui->button(_L("Apply"))) { + if (!m_is_valid_result) { + m_state = State::close_on_end; + process(); + } else if (m_exist_preview) { + // use preview and close + after_apply(); + } else { // no changes made + close(); + } + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing) + ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str()); + m_imgui->disabled_end(); // state !settings + + // draw progress bar + if (is_processing) { // apply or preview ImGui::SameLine(m_gui_cfg->bottom_left_width); // draw progress bar char buf[32]; @@ -251,6 +255,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } m_imgui->end(); + // refresh view when needed if (m_need_reload) { m_need_reload = false; bool close_on_end = (m_state == State::close_on_end); @@ -280,6 +285,22 @@ void GLGizmoSimplify::close() { gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } +void GLGizmoSimplify::live_preview() { + m_is_valid_result = false; + if (m_state != State::settings) { + // already canceling process + if (m_state == State::canceling) return; + + // wait until cancel + if (m_worker.joinable()) { + m_state = State::canceling; + m_worker.join(); + } + } + + m_state = State::preview; + process(); +} void GLGizmoSimplify::process() { @@ -408,6 +429,7 @@ void GLGizmoSimplify::create_gui_cfg() { cfg.input_width = cfg.bottom_left_width * 1.5; cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2; cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5; + m_gui_cfg = cfg; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index d624e3351..b609c5cdd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -24,21 +24,25 @@ class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GL public: GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); virtual ~GLGizmoSimplify(); + bool on_esc_key_down(); protected: - virtual bool on_init() override; virtual std::string on_get_name() const override; - virtual void on_render() override; - virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual bool on_is_activable() const override; virtual bool on_is_selectable() const override { return false; } virtual void on_set_state() override; + // must implement + virtual bool on_init() override { return true;}; + virtual void on_render() override{}; + virtual void on_render_for_picking() override{}; + // GLGizmoPainterBase virtual void render_painter_gizmo() const override{ render_wireframe(); } private: void after_apply(); void close(); + void live_preview(); void process(); void set_its(indexed_triangle_set &its); void create_gui_cfg(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 08a94a97d..0c8f161a9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -924,6 +924,10 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } default: { break; } } + } else if (m_current == Simplify && keyCode == WXK_ESCAPE) { + GLGizmoSimplify *simplify = dynamic_cast(get_current()); + if (simplify != nullptr) + processed = simplify->on_esc_key_down(); } } diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index fc410fce2..21aecd15b 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -919,7 +919,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp } if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - wxGetApp().open_preferences(2); + wxGetApp().open_preferences(2, "show_hints"); } ImGui::PopStyleColor(5); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 03e5bdc09..fe6c18db9 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -280,10 +280,10 @@ void ImGuiWrapper::render() m_new_frame_open = false; } -ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) +ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) { auto text_utf8 = into_u8(text); - ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str()); + ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width); /*#ifdef __linux__ size.x *= m_style_scaling; @@ -293,6 +293,13 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) return size; } +float ImGuiWrapper::get_slider_float_height() const +{ + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + return g.FontSize + style.FramePadding.y * 2.0f + style.ItemSpacing.y; +} + void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) { ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); @@ -425,6 +432,42 @@ void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label) this->text_colored(color, label_utf8.c_str()); } +void ImGuiWrapper::text_wrapped(const char *label, float wrap_width) +{ + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + this->text(label); + ImGui::PopTextWrapPos(); +} + +void ImGuiWrapper::text_wrapped(const std::string &label, float wrap_width) +{ + this->text_wrapped(label.c_str(), wrap_width); +} + +void ImGuiWrapper::text_wrapped(const wxString &label, float wrap_width) +{ + auto label_utf8 = into_u8(label); + this->text_wrapped(label_utf8.c_str(), wrap_width); +} + +void ImGuiWrapper::tooltip(const char *label, float wrap_width) +{ + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextUnformatted(label); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); +} + +void ImGuiWrapper::tooltip(const wxString &label, float wrap_width) +{ + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextUnformatted(label.ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); +} + bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/) { bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 441d26ccc..3712ff6a8 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -53,7 +53,9 @@ public: float scaled(float x) const { return x * m_font_size; } ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } - ImVec2 calc_text_size(const wxString &text); + ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f); + + float get_slider_float_height() const; void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); @@ -79,6 +81,11 @@ public: void text_colored(const ImVec4& color, const char* label); void text_colored(const ImVec4& color, const std::string& label); void text_colored(const ImVec4& color, const wxString& label); + void text_wrapped(const char *label, float wrap_width); + void text_wrapped(const std::string &label, float wrap_width); + void text_wrapped(const wxString &label, float wrap_width); + void tooltip(const char *label, float wrap_width); + void tooltip(const wxString &label, float wrap_width); // Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true). bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b12256cf0..40f5c3116 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -60,6 +60,7 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat _u8L("Undo desktop integration was successful.") }, {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, _u8L("Undo desktop integration failed.") }, + {NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") }, //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, //{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") }, //{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification @@ -214,7 +215,8 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init } if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { - ImGui::SetNextWindowFocus(); + // Uncomment if imgui window focus is needed on hover. I cant find any case. + //ImGui::SetNextWindowFocus(); set_hovered(); } @@ -1151,6 +1153,8 @@ bool NotificationManager::SlicingProgressNotification::set_progress_state(Notifi m_sp_state = state; return true; case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: + if (m_sp_state != SlicingProgressState::SP_BEGAN && m_sp_state != SlicingProgressState::SP_PROGRESS) + return false; set_percentage(1); m_has_cancel_button = false; m_has_print_info = false; @@ -1508,6 +1512,16 @@ void NotificationManager::push_notification(NotificationType type, int duration = get_standart_duration(level); push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp); } + +void NotificationManager::push_delayed_notification(const NotificationType type, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) +{ + auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), + boost::bind(&NotificationData::type, boost::placeholders::_1) == type); + assert(it != std::end(basic_notifications)); + if (it != std::end(basic_notifications)) + push_delayed_notification_data(std::make_unique(*it, m_id_provider, m_evt_handler), condition_callback, initial_delay, delay_interval); +} + void NotificationManager::push_validate_error_notification(const std::string& text) { push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); @@ -1911,7 +1925,7 @@ void NotificationManager::push_hint_notification(bool open_next) auto condition = [&self = std::as_const(*this)]() { return self.get_notification_count() == 0; }; - push_delayed_notification(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); + push_delayed_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); } } @@ -1974,7 +1988,7 @@ bool NotificationManager::push_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) +void NotificationManager::push_delayed_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) { if (initial_delay == 0 && condition_callback()) { if( push_notification_data(std::move(notification), 0)) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 00065f795..2031586b8 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -109,8 +109,10 @@ enum class NotificationType // Give user advice to simplify object with big amount of triangles // Contains ObjectID for closing when object is deleted SimplifySuggestion, - // information about netfabb is finished repairing model (blocking proccess) - NetfabbFinished + // information about netfabb is finished repairing model (blocking proccess) + NetfabbFinished, + // Short meesage to fill space between start and finish of export + ExportOngoing }; class NotificationManager @@ -151,6 +153,10 @@ public: // ErrorNotificationLevel and ImportantNotificationLevel are never faded out. void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", std::function callback = std::function(), int timestamp = 0); + // Pushes basic_notification with delay. See push_delayed_notification_data. + void push_delayed_notification(const NotificationType type, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); + // Removes all notifications of type from m_waiting_notifications + void stop_delayed_notifications_of_type(const NotificationType type); // Creates Validate Error notification with a custom text and no fade out. void push_validate_error_notification(const std::string& text); // Creates Slicing Error notification with a custom text and no fade out. @@ -699,10 +705,8 @@ private: // and condition callback is success, notification is regular pushed from update function. // Otherwise another delay interval waiting. Timestamp is 0. // Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push. - // Also note that no control of same notification is done during push_delayed_notification but if waiting notif fails to push, it continues waiting. - void push_delayed_notification(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); - // Removes all notifications of type from m_waiting_notifications - void stop_delayed_notifications_of_type(const NotificationType type); + // Also note that no control of same notification is done during push_delayed_notification_data but if waiting notif fails to push, it continues waiting. + void push_delayed_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); //finds older notification of same type and moves it to the end of queue. returns true if found bool activate_existing(const NotificationManager::PopNotification* notification); // Put the more important notifications to the bottom of the list. diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 9c31f679b..18553b9cf 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -597,6 +597,8 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) { if (field && field->undo_to_sys_bitmap()) h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink()) + ctrl->m_h_gap; + else if (field && !field->undo_to_sys_bitmap() && field->blink()) + draw_blinking_bmp(dc, wxPoint(h_pos, v_pos), field->blink()); // update width for full_width fields if (option_set.front().opt.full_width && field->getWindow()) field->getWindow()->SetSize(ctrl->GetSize().x - h_pos, -1); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3c217a59e..1f5c2b6e7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4032,6 +4032,7 @@ void Plater::priv::on_export_began(wxCommandEvent& evt) { if (show_warning_dialog) warnings_dialog(); + notification_manager->push_delayed_notification(NotificationType::ExportOngoing, [](){return true;}, 1000, 1000); } void Plater::priv::on_slicing_began() { @@ -4164,6 +4165,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) if(wxGetApp().get_mode() == comSimple) { show_action_buttons(false); } + if (exporting_status != ExportingStatus::NOT_EXPORTING && !has_error) { + notification_manager->stop_delayed_notifications_of_type(NotificationType::ExportOngoing); + notification_manager->close_notification_of_type(NotificationType::ExportOngoing); + } // If writing to removable drive was scheduled, show notification with eject button if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { show_action_buttons(false); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index ae52b52a9..3748e2251 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -43,7 +43,7 @@ namespace Slic3r { namespace GUI { -PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) : +PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab, const std::string& highlight_opt_key) : DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { @@ -51,6 +51,8 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) : isOSX = true; #endif build(selected_tab); + if (!highlight_opt_key.empty()) + init_highlighter(highlight_opt_key); } static std::shared_ptrcreate_options_tab(const wxString& title, wxBookCtrlBase* tabs) @@ -450,8 +452,10 @@ void PreferencesDialog::build(size_t selected_tab) activate_options_tab(m_optgroup_gui); // set Field for notify_release to its value to activate the object - boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); - m_optgroup_gui->get_field("notify_release")->set_value(val, false); + if (is_editor) { + boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); + m_optgroup_gui->get_field("notify_release")->set_value(val, false); + } if (is_editor) { create_icon_size_slider(); @@ -755,6 +759,71 @@ void PreferencesDialog::create_settings_text_color_widget() m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); } +void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key) +{ + m_highlighter.set_timer_owner(this, 0); + this->Bind(wxEVT_TIMER, [this](wxTimerEvent&) + { + m_highlighter.blink(); + }); + + std::pair ctrl = { nullptr, nullptr }; + for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui }) { + ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1); + if (ctrl.first && ctrl.second) { + m_highlighter.init(ctrl); + break; + } + } +} + +void PreferencesDialog::PreferencesHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) +{ + m_timer.SetOwner(owner, timerid); +} + +void PreferencesDialog::PreferencesHighlighter::init(std::pair params) +{ + if (m_timer.IsRunning()) + invalidate(); + if (!params.first || !params.second) + return; + + m_timer.Start(300, false); + + m_custom_ctrl = params.first; + m_show_blink_ptr = params.second; + + *m_show_blink_ptr = true; + m_custom_ctrl->Refresh(); +} + +void PreferencesDialog::PreferencesHighlighter::invalidate() +{ + m_timer.Stop(); + + if (m_custom_ctrl && m_show_blink_ptr) { + *m_show_blink_ptr = false; + m_custom_ctrl->Refresh(); + m_show_blink_ptr = nullptr; + m_custom_ctrl = nullptr; + } + + m_blink_counter = 0; +} + +void PreferencesDialog::PreferencesHighlighter::blink() +{ + if (m_custom_ctrl && m_show_blink_ptr) { + *m_show_blink_ptr = !*m_show_blink_ptr; + m_custom_ctrl->Refresh(); + } + else + return; + + if ((++m_blink_counter) == 11) + invalidate(); +} } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 7f708ff9c..01f116760 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -5,6 +5,7 @@ #include "GUI_Utils.hpp" #include +#include #include class wxColourPickerCtrl; @@ -20,6 +21,7 @@ namespace Slic3r { namespace GUI { class ConfigOptionsGroup; +class OG_CustomCtrl; class PreferencesDialog : public DPIDialog { @@ -39,7 +41,7 @@ class PreferencesDialog : public DPIDialog bool m_recreate_GUI{false}; public: - explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0); + explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0, const std::string& highlight_opt_key = std::string()); ~PreferencesDialog() = default; bool settings_layout_changed() const { return m_settings_layout_changed; } @@ -55,6 +57,22 @@ protected: void create_icon_size_slider(); void create_settings_mode_widget(); void create_settings_text_color_widget(); + void init_highlighter(const t_config_option_key& opt_key); + + struct PreferencesHighlighter + { + void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY); + void init(std::pair); + void blink(); + void invalidate(); + + private: + OG_CustomCtrl* m_custom_ctrl{ nullptr }; + bool* m_show_blink_ptr{ nullptr }; + int m_blink_counter{ 0 }; + wxTimer m_timer; + } + m_highlighter; }; } // GUI diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index db6ebad57..106617e76 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Http.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -17,6 +18,7 @@ #include #include #include +#include #include #include @@ -36,12 +38,17 @@ #include #pragma comment(lib, "iphlpapi.lib") #elif __APPLE__ -#import + #import + #include +#else // Linux/BSD + #include #endif namespace Slic3r { namespace GUI { +static const std::string SEND_SYSTEM_INFO_DOMAIN = "prusa3d.com"; +static const std::string SEND_SYSTEM_INFO_URL = "https://files." + SEND_SYSTEM_INFO_DOMAIN + "/wp-json/v1/ps"; // Declaration of a free function defined in OpenGLManager.cpp: @@ -52,8 +59,8 @@ std::string gl_get_string_safe(GLenum param, const std::string& default_value); class SendSystemInfoDialog : public DPIDialog { enum { - MIN_WIDTH = 80, - MIN_HEIGHT = 50 + MIN_WIDTH = 70, + MIN_HEIGHT = 34 }; public: @@ -129,20 +136,36 @@ public: // current version is newer. Only major and minor versions are compared. static bool should_dialog_be_shown() { - return false; - std::string last_sent_version = wxGetApp().app_config->get("version_system_info_sent"); Semver semver_current(SLIC3R_VERSION); Semver semver_last_sent; if (! last_sent_version.empty()) semver_last_sent = Semver(last_sent_version); - if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") - return false; // Don't show in alphas. + // set whether to show in alpha builds, or only betas/rcs/finals: + const bool show_in_alphas = true; - // Show the dialog if current > last, but they differ in more than just patch. - return ((semver_current.maj() > semver_last_sent.maj()) + if (! show_in_alphas && semver_current.prerelease() + && std::string(semver_current.prerelease()).find("alpha") != std::string::npos) + return false; + + // New version means current > last, but they must differ in more than just patch. + bool new_version = ((semver_current.maj() > semver_last_sent.maj()) || (semver_current.maj() == semver_last_sent.maj() && semver_current.min() > semver_last_sent.min() )); + + if (! new_version) + return false; + + // We'll misuse the version check to check internet connection here. + bool is_internet = false; + Http::get(wxGetApp().app_config->version_check_url()) + .size_limit(SLIC3R_VERSION_BODY_MAX) + .timeout_max(2) + .on_complete([&](std::string, unsigned) { + is_internet = true; + }) + .perform_sync(); + return is_internet; } @@ -162,9 +185,10 @@ static std::map get_cpu_info_from_registry() std::map out; int idx = -1; - constexpr DWORD bufsize_ = 200; - DWORD bufsize = bufsize_; + constexpr DWORD bufsize_ = 500; + DWORD bufsize = bufsize_-1; // Ensure a terminating zero. char buf[bufsize_] = ""; + memset(buf, 0, bufsize_); const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\"; std::string reg_path = reg_dir; @@ -186,7 +210,7 @@ static std::map get_cpu_info_from_registry() } ++idx; reg_path = reg_dir + std::to_string(idx) + "\\"; - bufsize = bufsize_; + bufsize = bufsize_-1; } return out; } @@ -194,7 +218,7 @@ static std::map get_cpu_info_from_registry() static std::map parse_lscpu_etc(const std::string& name, char delimiter) { std::map out; - constexpr size_t max_len = 100; + constexpr size_t max_len = 1000; char cline[max_len] = ""; FILE* fp = popen(name.data(), "r"); if (fp != NULL) { @@ -260,10 +284,12 @@ static std::string get_unique_id() char buf[buf_size] = ""; memset(&buf, 0, sizeof(buf)); io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); - CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); - IOObjectRelease(ioRegistryRoot); - CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); - CFRelease(uuidCf); + if (ioRegistryRoot != MACH_PORT_NULL) { + CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); + IOObjectRelease(ioRegistryRoot); + CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); + CFRelease(uuidCf); + } // Now convert the string to std::vector. for (char* c = buf; *c != 0; ++c) unique.emplace_back((unsigned char)(*c)); @@ -318,11 +344,20 @@ static std::string generate_system_info_json() std::string unique_id = get_unique_id(); // Get system language. - std::string sys_language = "Unknown"; - const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); - if (lang_system != wxLANGUAGE_UNKNOWN) - sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); - + std::string sys_language = "Unknown"; // important to init, see the __APPLE__ block. + #ifndef __APPLE__ + // Following apparently does not work on macOS. + const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); + if (lang_system != wxLANGUAGE_UNKNOWN) + sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); + #else // __APPLE__ + CFLocaleRef cflocale = CFLocaleCopyCurrent(); + CFStringRef value = (CFStringRef)CFLocaleGetValue(cflocale, kCFLocaleLanguageCode); + char temp[10] = ""; + CFStringGetCString(value, temp, 10, kCFStringEncodingUTF8); + sys_language = temp; + CFRelease(cflocale); + #endif // Build a property tree with all the information. namespace pt = boost::property_tree; @@ -364,9 +399,13 @@ static std::string generate_system_info_json() data_node.put("SystemLanguage", sys_language); data_node.put("TranslationLanguage: ", wxGetApp().app_config->get("translation_language")); + pt::ptree hw_node; - hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); - hw_node.put("RAM_MB", size_t(Slic3r::total_physical_memory()/1000000)); + { + hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); + size_t num = std::round(Slic3r::total_physical_memory()/107374100.); + hw_node.put("RAM_GiB", std::to_string(num / 10) + "." + std::to_string(num % 10)); + } // Now get some CPU info: pt::ptree cpu_node; @@ -381,31 +420,32 @@ static std::string generate_system_info_json() cpu_node.put("Model", sysctl["machdep.cpu.brand_string"]); cpu_node.put("Vendor", sysctl["machdep.cpu.vendor"]); #else // linux/BSD - std::map lscpu = parse_lscpu_etc("lscpu", ':'); - cpu_node.put("Arch", lscpu["Architecture"]); - cpu_node.put("Cores", lscpu["CPU(s)"]); - cpu_node.put("Model", lscpu["Model name"]); - cpu_node.put("Vendor", lscpu["Vendor ID"]); + std::map lscpu = parse_lscpu_etc("cat /proc/cpuinfo", ':'); + if (auto ncpu_it = lscpu.find("processor"); ncpu_it != lscpu.end()) { + std::string& ncpu = ncpu_it->second; + if (int num=0; std::from_chars(ncpu.data(), ncpu.data() + ncpu.size(), num).ec != std::errc::invalid_argument) + ncpu = std::to_string(num + 1); + } + cpu_node.put("Cores", lscpu["processor"]); + cpu_node.put("Model", lscpu["model name"]); + cpu_node.put("Vendor", lscpu["vendor_id"]); #endif hw_node.add_child("CPU", cpu_node); pt::ptree monitors_node; for (int i=0; i remove_whitespace; + remove_whitespace = [&remove_whitespace](pt::ptree& t) -> void + { + if (t.empty()) // Trim whitespace + boost::algorithm::trim(t.data()); + else + for (auto it = t.begin(); it != t.end(); ++it) + remove_whitespace(it->second); + }; + remove_whitespace(root); + // Serialize the tree into JSON and return it. std::stringstream ss; pt::write_json(ss, root); return ss.str(); - - // FURTHER THINGS TO CONSIDER: - //std::cout << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << std::endl; // Unix - // ? CPU, GPU, UNKNOWN ? - // printers? will they be installed already? } @@ -453,6 +501,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) GUI::DPIDialog(parent, wxID_ANY, _L("Send system info"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { + const int em = GUI::wxGetApp().em_unit(); + // Get current PrusaSliver version info. std::string app_name; { @@ -500,7 +550,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) std::string("") + filename + ""); wxString label3 = _L("Show verbatim data that will be sent"); - auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); + auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(70*em, 34*em), wxHW_SCROLLBAR_NEVER); wxString html = GUI::format_wxstr( "" "
" @@ -514,7 +564,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) + "" + label3 + "
" + "", bgr_clr_str, text_clr_str); html_window->SetPage(html); - html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent &evt) { + html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent&) { ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7)); dlg.ShowModal(); }); @@ -526,7 +576,6 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info")); auto* hsizer = new wxBoxSizer(wxHORIZONTAL); - const int em = GUI::wxGetApp().em_unit(); hsizer->Add(m_btn_ask_later); hsizer->AddSpacer(em); hsizer->Add(m_btn_dont_send); @@ -548,6 +597,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) SetSize(std::max(size.GetWidth(), MIN_WIDTH * em), std::max(size.GetHeight(), MIN_HEIGHT * em)); + CenterOnParent(); + m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&) { if (send_info()) { @@ -592,15 +643,16 @@ bool SendSystemInfoDialog::send_info() } result; // No synchronization needed, UI thread reads only after worker is joined. auto send = [&job_done, &result](const std::string& data) { - const std::string url = "https://files.prusa3d.com/wp-json/v1/ps"; - Http http = Http::post(url); + Http http = Http::post(SEND_SYSTEM_INFO_URL); http.header("Content-Type", "application/json") + .timeout_max(6) // seconds .set_post_body(data) .on_complete([&result](std::string body, unsigned status) { result = { Result::Success, _L("System info sent successfully. Thank you.") }; }) .on_error([&result](std::string body, std::string error, unsigned status) { - result = { Result::Error, GUI::format_wxstr(_L("Sending system info failed! Status: %1%"), status) }; + result = { Result::Error, _L("Sending system info failed!") }; + BOOST_LOG_TRIVIAL(error) << "Sending system info failed! STATUS: " << status; }) .on_progress([&job_done, &result](Http::Progress, bool &cancel) { if (job_done) // UI thread wants us to cancel. @@ -622,8 +674,10 @@ bool SendSystemInfoDialog::send_info() job_done = true; // In case the user closed the dialog, let the other thread know sending_thread.join(); // and wait until it terminates. - InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); - info_dlg.ShowModal(); + if (result.value != Result::Cancelled) { // user knows he cancelled, no need to tell him. + InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); + info_dlg.ShowModal(); + } return result.value == Result::Success; } diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index f1614017f..fc7afbb34 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -104,6 +104,7 @@ struct Http::priv { enum { DEFAULT_TIMEOUT_CONNECT = 10, + DEFAULT_TIMEOUT_MAX = 0, DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024, }; @@ -137,6 +138,7 @@ struct Http::priv static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); void set_timeout_connect(long timeout); + void set_timeout_max(long timeout); void form_add_file(const char *name, const fs::path &path, const char* filename); void set_post_body(const fs::path &path); void set_post_body(const std::string &body); @@ -163,6 +165,7 @@ Http::priv::priv(const std::string &url) } set_timeout_connect(DEFAULT_TIMEOUT_CONNECT); + set_timeout_max(DEFAULT_TIMEOUT_MAX); ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION); ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front()); @@ -253,6 +256,11 @@ void Http::priv::set_timeout_connect(long timeout) ::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); } +void Http::priv::set_timeout_max(long timeout) +{ + ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); +} + void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) { // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows @@ -409,6 +417,13 @@ Http& Http::timeout_connect(long timeout) return *this; } +Http& Http::timeout_max(long timeout) +{ + if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_MAX; } + if (p) { p->set_timeout_max(timeout); } + return *this; +} + Http& Http::size_limit(size_t sizeLimit) { if (p) { p->limit = sizeLimit; } diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 52e48a394..61d84c51e 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -58,6 +58,8 @@ public: // Sets a maximum connection timeout in seconds Http& timeout_connect(long timeout); + // Sets a maximum total request timeout in seconds + Http& timeout_max(long timeout); // Sets a maximum size of the data that can be received. // A value of zero sets the default limit, which is is 5MB. Http& size_limit(size_t sizeLimit); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 13c631c9c..76ecc76d9 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -46,10 +46,6 @@ using Slic3r::GUI::Config::SnapshotDB; namespace Slic3r { -enum { - SLIC3R_VERSION_BODY_MAX = 256, -}; - static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index b7937c574..d0d18a7d8 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -13,6 +13,8 @@ class AppConfig; class PresetBundle; class Semver; +const int SLIC3R_VERSION_BODY_MAX = 256; + class PresetUpdater { public: diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 3cbd21a40..1b2b45eca 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -34,7 +34,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { } } WHEN("offset2_ex") { - ExPolygons result = Slic3r::offset2_ex(square_with_hole, 5.f, -2.f); + ExPolygons result = Slic3r::offset2_ex({ square_with_hole }, 5.f, -2.f); THEN("offset matches") { REQUIRE(result == ExPolygons { { { { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } }, diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index bae6a103e..eae3afeff 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -49,7 +49,7 @@ offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, mit Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: - RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit); + RETVAL = offset2_ex(union_ex(polygons), delta1, delta2, joinType, miterLimit); OUTPUT: RETVAL