diff --git a/resources/icons/PrusaSlicer-gcodeviewer.svg b/resources/icons/PrusaSlicer-gcodeviewer.svg
new file mode 100644
index 000000000..6312beee3
--- /dev/null
+++ b/resources/icons/PrusaSlicer-gcodeviewer.svg
@@ -0,0 +1,73 @@
+
+
+
diff --git a/resources/icons/prusa_slicer_logo.svg b/resources/icons/PrusaSlicer.svg
similarity index 100%
rename from resources/icons/prusa_slicer_logo.svg
rename to resources/icons/PrusaSlicer.svg
diff --git a/resources/icons/colorchange_add_off.png b/resources/icons/colorchange_add_off.png
deleted file mode 100644
index 6ddeccbe0..000000000
Binary files a/resources/icons/colorchange_add_off.png and /dev/null differ
diff --git a/resources/icons/colorchange_add_on.png b/resources/icons/colorchange_add_on.png
deleted file mode 100644
index cc800b81e..000000000
Binary files a/resources/icons/colorchange_add_on.png and /dev/null differ
diff --git a/resources/icons/colorchange_delete_off.png b/resources/icons/colorchange_delete_off.png
deleted file mode 100644
index c16655271..000000000
Binary files a/resources/icons/colorchange_delete_off.png and /dev/null differ
diff --git a/resources/icons/colorchange_delete_on.png b/resources/icons/colorchange_delete_on.png
deleted file mode 100644
index 8f27ce9fe..000000000
Binary files a/resources/icons/colorchange_delete_on.png and /dev/null differ
diff --git a/resources/icons/down_half_circle.png b/resources/icons/down_half_circle.png
deleted file mode 100644
index f86a2932c..000000000
Binary files a/resources/icons/down_half_circle.png and /dev/null differ
diff --git a/resources/icons/left_half_circle.png b/resources/icons/left_half_circle.png
deleted file mode 100644
index 3bdc4c3ee..000000000
Binary files a/resources/icons/left_half_circle.png and /dev/null differ
diff --git a/resources/icons/mirroring_transparent.png b/resources/icons/mirroring_transparent.png
deleted file mode 100644
index 841010fcc..000000000
Binary files a/resources/icons/mirroring_transparent.png and /dev/null differ
diff --git a/resources/icons/mirroring_transparent.svg b/resources/icons/mirroring_transparent.svg
new file mode 100644
index 000000000..c0e831cc3
--- /dev/null
+++ b/resources/icons/mirroring_transparent.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/resources/icons/mode_advanced_.png b/resources/icons/mode_advanced_.png
deleted file mode 100644
index d98d8f709..000000000
Binary files a/resources/icons/mode_advanced_.png and /dev/null differ
diff --git a/resources/icons/mode_advanced_sq.png b/resources/icons/mode_advanced_sq.png
deleted file mode 100644
index 6df2a52fe..000000000
Binary files a/resources/icons/mode_advanced_sq.png and /dev/null differ
diff --git a/resources/icons/mode_expert_.png b/resources/icons/mode_expert_.png
deleted file mode 100644
index 4d78bcccf..000000000
Binary files a/resources/icons/mode_expert_.png and /dev/null differ
diff --git a/resources/icons/mode_expert_sq.png b/resources/icons/mode_expert_sq.png
deleted file mode 100644
index 742ffc088..000000000
Binary files a/resources/icons/mode_expert_sq.png and /dev/null differ
diff --git a/resources/icons/mode_simple_.png b/resources/icons/mode_simple_.png
deleted file mode 100644
index aac2b61b0..000000000
Binary files a/resources/icons/mode_simple_.png and /dev/null differ
diff --git a/resources/icons/mode_simple_sq.png b/resources/icons/mode_simple_sq.png
deleted file mode 100644
index cb8ab7bd4..000000000
Binary files a/resources/icons/mode_simple_sq.png and /dev/null differ
diff --git a/resources/icons/one_layer_lock_off.png b/resources/icons/one_layer_lock_off.png
deleted file mode 100644
index f6e61d058..000000000
Binary files a/resources/icons/one_layer_lock_off.png and /dev/null differ
diff --git a/resources/icons/one_layer_lock_on.png b/resources/icons/one_layer_lock_on.png
deleted file mode 100644
index 011099972..000000000
Binary files a/resources/icons/one_layer_lock_on.png and /dev/null differ
diff --git a/resources/icons/one_layer_unlock_off.png b/resources/icons/one_layer_unlock_off.png
deleted file mode 100644
index 46fabfb05..000000000
Binary files a/resources/icons/one_layer_unlock_off.png and /dev/null differ
diff --git a/resources/icons/one_layer_unlock_on.png b/resources/icons/one_layer_unlock_on.png
deleted file mode 100644
index 265b92610..000000000
Binary files a/resources/icons/one_layer_unlock_on.png and /dev/null differ
diff --git a/resources/icons/pause_add.png b/resources/icons/pause_add.png
deleted file mode 100644
index afe881de8..000000000
Binary files a/resources/icons/pause_add.png and /dev/null differ
diff --git a/resources/icons/right_half_circle.png b/resources/icons/right_half_circle.png
deleted file mode 100644
index ecc8980b3..000000000
Binary files a/resources/icons/right_half_circle.png and /dev/null differ
diff --git a/resources/icons/row.png b/resources/icons/row.png
deleted file mode 100644
index 18a6034fd..000000000
Binary files a/resources/icons/row.png and /dev/null differ
diff --git a/resources/icons/shape_ungroup.png b/resources/icons/shape_ungroup.png
deleted file mode 100644
index 97aaceb23..000000000
Binary files a/resources/icons/shape_ungroup.png and /dev/null differ
diff --git a/resources/icons/table.png b/resources/icons/table.png
deleted file mode 100644
index 3bc0bd32f..000000000
Binary files a/resources/icons/table.png and /dev/null differ
diff --git a/resources/icons/up_half_circle.png b/resources/icons/up_half_circle.png
deleted file mode 100644
index aac6e32c3..000000000
Binary files a/resources/icons/up_half_circle.png and /dev/null differ
diff --git a/resources/icons/variable_layer_height_reset.png b/resources/icons/variable_layer_height_reset.png
deleted file mode 100644
index 6e051fe95..000000000
Binary files a/resources/icons/variable_layer_height_reset.png and /dev/null differ
diff --git a/resources/icons/variable_layer_height_tooltip.png b/resources/icons/variable_layer_height_tooltip.png
deleted file mode 100644
index 182005292..000000000
Binary files a/resources/icons/variable_layer_height_tooltip.png and /dev/null differ
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
new file mode 100644
index 000000000..a1ba85b5c
--- /dev/null
+++ b/resources/shaders/gouraud_mod.fs
@@ -0,0 +1,106 @@
+#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 vec3 GREEN = vec3(0.0, 0.7, 0.0);
+const vec3 YELLOW = vec3(0.5, 0.7, 0.0);
+const vec3 RED = vec3(0.7, 0.0, 0.0);
+const vec3 WHITE = vec3(1.0, 1.0, 1.0);
+const float EPSILON = 0.0001;
+const float BANDS_WIDTH = 10.0;
+
+struct PrintVolumeDetection
+{
+ // 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid
+ int type;
+ // type = 0 (rectangle):
+ // x = min.x, y = min.y, z = max.x, w = max.y
+ // type = 1 (circle):
+ // x = center.x, y = center.y, z = radius
+ vec4 xy_data;
+ // x = min z, y = max z
+ vec2 z_data;
+};
+
+struct SlopeDetection
+{
+ bool actived;
+ float normal_z;
+ mat3 volume_world_normal_matrix;
+};
+
+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;
+#endif // ENABLE_ENVIRONMENT_MAP
+
+varying vec3 clipping_planes_dots;
+
+// x = diffuse, y = specular;
+varying vec2 intensity;
+
+uniform PrintVolumeDetection print_volume;
+
+varying vec4 model_pos;
+varying vec4 world_pos;
+varying float world_normal_z;
+varying vec3 eye_normal;
+
+void main()
+{
+ if (any(lessThan(clipping_planes_dots, ZERO)))
+ discard;
+ vec3 color = uniform_color.rgb;
+ float alpha = uniform_color.a;
+
+ if (slope.actived && world_normal_z < slope.normal_z - EPSILON) {
+ color = vec3(0.7, 0.7, 1.0);
+ alpha = 1.0;
+ }
+
+ // if the fragment is outside the print volume -> use darker color
+ vec3 pv_check_min = ZERO;
+ vec3 pv_check_max = ZERO;
+ if (print_volume.type == 0) {
+ // rectangle
+ pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x);
+ pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y);
+ }
+ else if (print_volume.type == 1) {
+ // circle
+ float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy);
+ pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x);
+ pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y);
+ }
+ color = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_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).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha);
+ else
+#endif
+ 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
new file mode 100644
index 000000000..79d7a63c0
--- /dev/null
+++ b/resources/shaders/gouraud_mod.vs
@@ -0,0 +1,73 @@
+#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 LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SHININESS 5.0
+
+#define INTENSITY_AMBIENT 0.3
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+struct SlopeDetection
+{
+ bool actived;
+ float normal_z;
+ mat3 volume_world_normal_matrix;
+};
+
+uniform mat4 volume_world_matrix;
+uniform SlopeDetection slope;
+
+// 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;
+
+// x = diffuse, y = specular;
+varying vec2 intensity;
+
+varying vec3 clipping_planes_dots;
+
+varying vec4 model_pos;
+varying vec4 world_pos;
+varying float world_normal_z;
+varying vec3 eye_normal;
+
+void main()
+{
+ // 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);
+
+ 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;
+
+ 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
+ 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.
+ clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
+}
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/resources/shapes/M3_hex_nut.png b/resources/shapes/M3_hex_nut.png
new file mode 100644
index 000000000..26dbb634c
Binary files /dev/null and b/resources/shapes/M3_hex_nut.png differ
diff --git a/resources/shapes/M3_hex_nut.stl b/resources/shapes/M3_hex_nut.stl
new file mode 100644
index 000000000..2be831705
Binary files /dev/null and b/resources/shapes/M3_hex_nut.stl differ
diff --git a/resources/shapes/M3x10_screw.png b/resources/shapes/M3x10_screw.png
new file mode 100644
index 000000000..623efd460
Binary files /dev/null and b/resources/shapes/M3x10_screw.png differ
diff --git a/resources/shapes/M3x10_screw.stl b/resources/shapes/M3x10_screw.stl
new file mode 100644
index 000000000..e6d37185e
Binary files /dev/null and b/resources/shapes/M3x10_screw.stl differ
diff --git a/resources/shapes/cone.png b/resources/shapes/cone.png
new file mode 100644
index 000000000..87bc51d46
Binary files /dev/null and b/resources/shapes/cone.png differ
diff --git a/resources/shapes/cone.stl b/resources/shapes/cone.stl
new file mode 100644
index 000000000..f75c6d772
Binary files /dev/null and b/resources/shapes/cone.stl differ
diff --git a/resources/shapes/helper_disk.png b/resources/shapes/helper_disk.png
new file mode 100644
index 000000000..602f33cbf
Binary files /dev/null and b/resources/shapes/helper_disk.png differ
diff --git a/resources/shapes/helper_disk.stl b/resources/shapes/helper_disk.stl
new file mode 100644
index 000000000..94ab0739c
Binary files /dev/null and b/resources/shapes/helper_disk.stl differ
diff --git a/resources/shapes/torus.png b/resources/shapes/torus.png
new file mode 100644
index 000000000..16e8bac5a
Binary files /dev/null and b/resources/shapes/torus.png differ
diff --git a/resources/shapes/torus.stl b/resources/shapes/torus.stl
new file mode 100644
index 000000000..997fd44b5
Binary files /dev/null and b/resources/shapes/torus.stl differ
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/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index 8999387f1..fb12fee34 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -86,9 +86,6 @@ void AppConfig::set_defaults()
if (get("associate_stl").empty())
set("associate_stl", "0");
- if (get("dark_color_mode").empty())
- set("dark_color_mode", "0");
-
if (get("tabs_as_menu").empty())
set("tabs_as_menu", "0");
#endif // _WIN32
@@ -125,6 +122,9 @@ void AppConfig::set_defaults()
if (get("auto_toolbar_size").empty())
set("auto_toolbar_size", "100");
+
+ if (get("notify_release").empty())
+ set("notify_release", "all"); // or "none" or "release"
#if ENABLE_ENVIRONMENT_MAP
if (get("use_environment_map").empty())
@@ -180,6 +180,9 @@ void AppConfig::set_defaults()
#ifdef _WIN32
if (get("use_legacy_3DConnexion").empty())
set("use_legacy_3DConnexion", "0");
+
+ if (get("dark_color_mode").empty())
+ set("dark_color_mode", "0");
#endif // _WIN32
// Remove legacy window positions/sizes
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 9c90535c4..6121fb7fd 100644
--- a/src/libslic3r/GCode/GCodeProcessor.cpp
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -720,6 +720,9 @@ void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor)
void GCodeProcessor::Result::reset() {
moves = std::vector();
bed_shape = Pointfs();
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ max_print_height = 0.0f;
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
settings_ids.reset();
extruders_count = 0;
extruder_colors = std::vector();
@@ -734,6 +737,9 @@ void GCodeProcessor::Result::reset() {
moves.clear();
lines_ends.clear();
bed_shape = Pointfs();
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ max_print_height = 0.0f;
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
settings_ids.reset();
extruders_count = 0;
extruder_colors = std::vector();
@@ -883,6 +889,10 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height");
if (first_layer_height != nullptr)
m_first_layer_height = std::abs(first_layer_height->value);
+
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ m_result.max_print_height = config.max_print_height;
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
}
void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
@@ -1112,6 +1122,12 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height");
if (first_layer_height != nullptr)
m_first_layer_height = std::abs(first_layer_height->value);
+
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ const ConfigOptionFloat* max_print_height = config.option("max_print_height");
+ if (max_print_height != nullptr)
+ m_result.max_print_height = max_print_height->value;
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
}
void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
@@ -1251,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);
@@ -2663,7 +2679,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex())
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]);
// check for seam ending vertex and store the resulting move
- else if ((type != EMoveType::Extrude || m_extrusion_role != erExternalPerimeter) && m_seams_detector.has_first_vertex()) {
+ else if ((type != EMoveType::Extrude || (m_extrusion_role != erExternalPerimeter && m_extrusion_role != erOverhangPerimeter)) && m_seams_detector.has_first_vertex()) {
auto set_end_position = [this](const Vec3f& pos) {
m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z();
};
@@ -2672,6 +2688,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id];
const std::optional first_vertex = m_seams_detector.get_first_vertex();
// the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later
+
if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) {
set_end_position(0.5f * (new_pos + *first_vertex));
store_move_vertex(EMoveType::Seam);
@@ -2681,6 +2698,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
m_seams_detector.activate(false);
}
}
+ else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) {
+ m_seams_detector.activate(true);
+ m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]);
+ }
// store move
store_move_vertex(type);
diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp
index fce888233..e7d602155 100644
--- a/src/libslic3r/GCode/GCodeProcessor.hpp
+++ b/src/libslic3r/GCode/GCodeProcessor.hpp
@@ -351,6 +351,9 @@ namespace Slic3r {
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
std::vector lines_ends;
Pointfs bed_shape;
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ float max_print_height;
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
SettingsIds settings_ids;
size_t extruders_count;
std::vector extruder_colors;
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/Geometry.cpp b/src/libslic3r/Geometry.cpp
index 420ab473f..4cf60dbe2 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -311,6 +311,15 @@ bool directions_parallel(double angle1, double angle2, double max_diff)
return diff < max_diff || fabs(diff - PI) < max_diff;
}
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+bool directions_perpendicular(double angle1, double angle2, double max_diff)
+{
+ double diff = fabs(angle1 - angle2);
+ max_diff += EPSILON;
+ return fabs(diff - 0.5 * PI) < max_diff || fabs(diff - 1.5 * PI) < max_diff;
+}
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+
template
bool contains(const std::vector &vector, const Point &point)
{
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index 505ff3d96..49886d4b8 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -84,6 +84,32 @@ static inline bool is_ccw(const Polygon &poly)
return o == ORIENTATION_CCW;
}
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+// returns true if the given polygons are identical
+static inline bool are_approx(const Polygon& lhs, const Polygon& rhs)
+{
+ if (lhs.points.size() != rhs.points.size())
+ return false;
+
+ size_t rhs_id = 0;
+ while (rhs_id < rhs.points.size()) {
+ if (rhs.points[rhs_id].isApprox(lhs.points.front()))
+ break;
+ ++rhs_id;
+ }
+
+ if (rhs_id == rhs.points.size())
+ return false;
+
+ for (size_t i = 0; i < lhs.points.size(); ++i) {
+ if (!lhs.points[i].isApprox(rhs.points[(i + rhs_id) % lhs.points.size()]))
+ return false;
+ }
+
+ return true;
+}
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+
inline bool ray_ray_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res)
{
double denom = v1(0) * v2(1) - v2(0) * v1(1);
@@ -336,6 +362,9 @@ Polygon convex_hull(Points points);
Polygon convex_hull(const Polygons &polygons);
bool directions_parallel(double angle1, double angle2, double max_diff = 0);
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+bool directions_perpendicular(double angle1, double angle2, double max_diff = 0);
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
template bool contains(const std::vector &vector, const Point &point);
template T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
double rad2deg_dir(double angle);
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.cpp b/src/libslic3r/Line.cpp
index 8a2a2875b..1a96b8b1f 100644
--- a/src/libslic3r/Line.cpp
+++ b/src/libslic3r/Line.cpp
@@ -63,6 +63,13 @@ bool Line::parallel_to(double angle) const
return Slic3r::Geometry::directions_parallel(this->direction(), angle);
}
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+bool Line::perpendicular_to(double angle) const
+{
+ return Slic3r::Geometry::directions_perpendicular(this->direction(), angle);
+}
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+
bool Line::intersection(const Line &l2, Point *intersection) const
{
const Line &l1 = *this;
diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp
index b62775bfe..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,10 +100,15 @@ 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;
bool parallel_to(const Line &line) const { return this->parallel_to(line.direction()); }
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ bool perpendicular_to(double angle) const;
+ bool perpendicular_to(const Line& line) const { return this->perpendicular_to(line.direction()); }
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
double atan2_() const { return atan2(this->b(1) - this->a(1), this->b(0) - this->a(0)); }
double orientation() const;
double direction() const;
diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp
index e60918fab..ab99ea5f6 100644
--- a/src/libslic3r/MTUtils.hpp
+++ b/src/libslic3r/MTUtils.hpp
@@ -47,7 +47,7 @@ private:
public:
// Forwarded constructor
template
- inline CachedObject(Setter fn, Args &&... args)
+ inline CachedObject(Setter &&fn, Args &&... args)
: m_obj(std::forward(args)...), m_valid(false), m_setter(fn)
{}
@@ -55,7 +55,7 @@ public:
// the next retrieval (Setter will be called). The data that is used in
// the setter function should be guarded as well during modification so
// the modification has to take place in fn.
- inline void invalidate(std::function fn)
+ template void invalidate(Fn &&fn)
{
std::lock_guard lck(m_lck);
fn();
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/Model.cpp b/src/libslic3r/Model.cpp
index 8659e6961..b36618dd1 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -26,6 +26,35 @@
namespace Slic3r {
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z)
+{
+ if (!Geometry::intersects(printbed_shape, obj_hull_2d))
+ return ModelInstancePVS_Fully_Outside;
+
+ bool contained_xy = true;
+ for (const Point& p : obj_hull_2d) {
+ if (!printbed_shape.contains(p)) {
+ contained_xy = false;
+ break;
+ }
+ }
+ const bool contained_z = -1e10 < obj_min_z && obj_max_z < print_volume_height;
+ return (contained_xy && contained_z) ? ModelInstancePVS_Inside : ModelInstancePVS_Partly_Outside;
+}
+
+ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box)
+{
+ const Polygon box_hull_2d({
+ { scale_(box.min.x()), scale_(box.min.y()) },
+ { scale_(box.max.x()), scale_(box.min.y()) },
+ { scale_(box.max.x()), scale_(box.max.y()) },
+ { scale_(box.min.x()), scale_(box.max.y()) }
+ });
+ return printbed_collision_state(printbed_shape, print_volume_height, box_hull_2d, box.min.z(), box.max.z());
+}
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+
Model& Model::assign_copy(const Model &rhs)
{
this->copy_id(rhs);
@@ -330,13 +359,23 @@ BoundingBoxf3 Model::bounding_box() const
return bb;
}
-unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume)
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+unsigned int Model::update_print_volume_state(const Polygon& printbed_shape, double print_volume_height)
+{
+ unsigned int num_printable = 0;
+ for (ModelObject* model_object : this->objects)
+ num_printable += model_object->check_instances_print_volume_state(printbed_shape, print_volume_height);
+ return num_printable;
+}
+#else
+unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume)
{
unsigned int num_printable = 0;
for (ModelObject *model_object : this->objects)
num_printable += model_object->check_instances_print_volume_state(print_volume);
return num_printable;
}
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
bool Model::center_instances_around_point(const Vec2d &point)
{
@@ -513,6 +552,22 @@ void Model::convert_from_meters(bool only_small_volumes)
}
}
+static constexpr const double zero_volume = 0.0000000001;
+
+int Model::removed_objects_with_zero_volume()
+{
+ if (objects.size() == 0)
+ return 0;
+
+ int removed = 0;
+ for (int i = int(objects.size()) - 1; i >= 0; i--)
+ if (objects[i]->get_object_stl_stats().volume < zero_volume) {
+ delete_object(size_t(i));
+ removed++;
+ }
+ return removed;
+}
+
void Model::adjust_min_z()
{
if (objects.empty())
@@ -1513,6 +1568,38 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const
return max_z + inst->get_offset(Z);
}
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+unsigned int ModelObject::check_instances_print_volume_state(const Polygon& printbed_shape, double print_volume_height)
+{
+ unsigned int num_printable = 0;
+ enum {
+ INSIDE = 1,
+ OUTSIDE = 2
+ };
+ for (ModelInstance* model_instance : this->instances) {
+ unsigned int inside_outside = 0;
+ for (const ModelVolume* vol : this->volumes)
+ if (vol->is_model_part()) {
+ const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix();
+ const BoundingBoxf3 bb = vol->mesh().transformed_bounding_box(matrix, 0.0);
+ const Polygon volume_hull_2d = its_convex_hull_2d_above(vol->mesh().its, matrix.cast(), 0.0f);
+ ModelInstanceEPrintVolumeState state = printbed_collision_state(printbed_shape, print_volume_height, volume_hull_2d, bb.min.z(), bb.max.z());
+ if (state == ModelInstancePVS_Inside)
+ inside_outside |= INSIDE;
+ else if (state == ModelInstancePVS_Fully_Outside)
+ inside_outside |= OUTSIDE;
+ else
+ inside_outside |= INSIDE | OUTSIDE;
+ }
+ model_instance->print_volume_state =
+ (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstancePVS_Partly_Outside :
+ (inside_outside == INSIDE) ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside;
+ if (inside_outside == INSIDE)
+ ++num_printable;
+ }
+ return num_printable;
+}
+#else
unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
{
unsigned int num_printable = 0;
@@ -1540,6 +1627,7 @@ unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3
}
return num_printable;
}
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
void ModelObject::print_info() const
{
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index efbf84cd4..12b76a879 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -360,7 +360,11 @@ public:
double get_instance_max_z(size_t instance_idx) const;
// Called by Print::validate() from the UI thread.
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ unsigned int check_instances_print_volume_state(const Polygon& printbed_shape, double print_volume_height);
+#else
unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume);
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// Print object statistics to console.
void print_info() const;
@@ -904,6 +908,14 @@ enum ModelInstanceEPrintVolumeState : unsigned char
ModelInstanceNum_BedStates
};
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+// return the state of the given object's volume (extrusion along z of obj_hull_2d from obj_min_z to obj_max_z)
+// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height)
+ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z);
+// return the state of the given box
+// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height)
+ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box);
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// A single instance of a ModelObject.
// Knows the affine transformation of an object.
@@ -1109,8 +1121,12 @@ public:
BoundingBoxf3 bounding_box() const;
// Set the print_volume_state of PrintObject::instances,
// return total number of printable objects.
+#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ unsigned int update_print_volume_state(const Polygon& printbed_shape, double print_volume_height);
+#else
unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume);
- // Returns true if any ModelObject was modified.
+#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
+ // Returns true if any ModelObject was modified.
bool center_instances_around_point(const Vec2d &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;
@@ -1124,6 +1140,7 @@ public:
void convert_from_imperial_units(bool only_small_volumes);
bool looks_like_saved_in_meters() const;
void convert_from_meters(bool only_small_volumes);
+ int removed_objects_with_zero_volume();
// Ensures that the min z of the model is not negative
void adjust_min_z();
diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp
index 9456f5077..b17ee000a 100644
--- a/src/libslic3r/MultiMaterialSegmentation.cpp
+++ b/src/libslic3r/MultiMaterialSegmentation.cpp
@@ -190,8 +190,7 @@ static inline std::vector to_lines(const std::vector;
ulp_cmp_type ulp_cmp;
static constexpr int ULPS = boost::polygon::voronoi_diagram_traits::vertex_equality_predicate_type::ULPS;
- return ulp_cmp(vertex.x(), double(ipt.x()), ULPS) == ulp_cmp_type::EQUAL &&
- ulp_cmp(vertex.y(), double(ipt.y()), ULPS) == ulp_cmp_type::EQUAL;
+ return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL &&
+ ulp_cmp(vertex.y(), ipt.y(), ULPS) == ulp_cmp_type::EQUAL;
}
-static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Point &ipt)
+static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Vec2d &ipt)
{
return vertex_equal_to_point(*vertex, ipt);
}
@@ -509,6 +508,8 @@ static inline Point mk_point(const Voronoi::Internal::point_type &point) { retur
static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; }
+static inline Point mk_point(const Vec2d &point) { return {coord_t(std::round(point.x())), coord_t(std::round(point.y()))}; }
+
static inline Vec2d mk_vec2(const voronoi_diagram::vertex_type *point) { return {point->x(), point->y()}; }
struct MMU_Graph
@@ -528,7 +529,7 @@ struct MMU_Graph
struct Node
{
- Point point;
+ Vec2d point;
std::list arc_idxs;
void remove_edge(const size_t to_idx, MMU_Graph &graph)
@@ -665,48 +666,67 @@ struct MMU_Graph
struct CPoint
{
CPoint() = delete;
- CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
- CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {}
+ CPoint(const Vec2d &point, size_t contour_idx, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
+ CPoint(const Vec2d &point, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(0) {}
+ const Vec2d m_point_double;
const Point m_point;
size_t m_point_idx;
size_t m_contour_idx;
+ [[nodiscard]] const Vec2d &point_double() const { return m_point_double; }
[[nodiscard]] const Point &point() const { return m_point; }
- bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
+ bool operator==(const CPoint &rhs) const { return this->m_point_double == rhs.m_point_double && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
};
struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
typedef ClosestPointInRadiusLookup CPointLookupType;
- CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON));
+ CPointLookupType closest_voronoi_point(coord_t(SCALED_EPSILON));
CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON));
for (const Polygon &polygon : color_poly_tmp)
for (const Point &pt : polygon.points)
- closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
+ closest_contour_point.insert(CPoint(Vec2d(pt.x(), pt.y()), &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) {
vertex.color(-1);
- Point vertex_point = mk_point(vertex);
+ Vec2d vertex_point_double = Vec2d(vertex.x(), vertex.y());
+ Point vertex_point = mk_point(vertex);
- const Point &first_point = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
- const Point &second_point = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
+ const Vec2d &first_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
+ const Vec2d &second_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
- if (vertex_equal_to_point(&vertex, first_point)) {
+ if (vertex_equal_to_point(&vertex, first_point_double)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
- } else if (vertex_equal_to_point(&vertex, second_point)) {
+ } else if (vertex_equal_to_point(&vertex, second_point_double)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
} else if (bbox.contains(vertex_point)) {
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) {
vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx));
- } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(3 * SCALED_EPSILON)) {
- closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count()));
+ } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(SCALED_EPSILON / 10.0)) {
+ closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count()));
vertex.color(this->nodes_count());
- this->nodes.push_back({vertex_point});
+ this->nodes.push_back({vertex_point_double});
} else {
- vertex.color(voronoi_pt->m_point_idx);
+ // Boost Voronoi diagram generator sometimes creates two very closed points instead of one point.
+ // For the example points (146872.99999999997, -146872.99999999997) and (146873, -146873), this example also included in Voronoi generator test cases.
+ std::vector> all_closes_c_points = closest_voronoi_point.find_all(vertex_point);
+ int merge_to_point = -1;
+ for (const std::pair &c_point : all_closes_c_points)
+ if ((vertex_point_double - c_point.first->point_double()).squaredNorm() <= Slic3r::sqr(EPSILON)) {
+ merge_to_point = int(c_point.first->m_point_idx);
+ break;
+ }
+
+ if (merge_to_point != -1) {
+ vertex.color(merge_to_point);
+ } else {
+ closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count()));
+ vertex.color(this->nodes_count());
+ this->nodes.push_back({vertex_point_double});
+ }
}
}
}
@@ -850,7 +870,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectorvertex0()->color()].point;
- Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point;
+ Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point;
+ Vec2d real_v1_double = graph.nodes[edge_it->vertex1()->color()].point;
+ Point real_v0 = Point(coord_t(real_v0_double.x()), coord_t(real_v0_double.y()));
+ Point real_v1 = Point(coord_t(real_v1_double.x()), coord_t(real_v1_double.y()));
if (is_point_closer_to_beginning_of_line(contour_line, intersection)) {
Line first_part(intersection, real_v0);
@@ -999,8 +1021,9 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectorvertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx);
}
} else {
- const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx;
- const Point int_point = graph.nodes[int_point_idx].point;
+ const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx;
+ const Vec2d int_point_double = graph.nodes[int_point_idx].point;
+ const Point int_point = Point(coord_t(int_point_double.x()), coord_t(int_point_double.y()));
const Line first_part(int_point, real_v0);
const Line second_part(int_point, real_v1);
@@ -1039,12 +1062,12 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector &lines)
{
Polygon poly_out;
poly_out.points.reserve(lines.size());
- for (const Line &line : lines)
- poly_out.points.emplace_back(line.a);
+ for (const Linef &line : lines)
+ poly_out.points.emplace_back(mk_point(line.a));
return poly_out;
}
@@ -1056,7 +1079,7 @@ static std::vector> extract_colored_segments(const MM
{
std::vector used_arcs(graph.arcs.size(), false);
// When there is no next arc, then is returned original_arc or edge with is marked as used
- auto get_next = [&graph, &used_arcs](const Line &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & {
+ auto get_next = [&graph, &used_arcs](const Linef &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & {
std::vector> sorted_arcs;
for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
@@ -1064,8 +1087,8 @@ static std::vector> extract_colored_segments(const MM
continue;
assert(original_arc.to_idx == arc.from_idx);
- Vec2d process_line_vec_n = (process_line.a - process_line.b).cast().normalized();
- Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).cast().normalized();
+ Vec2d process_line_vec_n = (process_line.a - process_line.b).normalized();
+ Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).normalized();
double angle = ::acos(std::clamp(neighbour_line_vec_n.dot(process_line_vec_n), -1.0, 1.0));
if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0)
@@ -1098,17 +1121,17 @@ static std::vector> extract_colored_segments(const MM
for (const size_t &arc_idx : node.arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
- if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx])continue;
+ if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx])
+ continue;
-
- Line process_line(node.point, graph.nodes[arc.to_idx].point);
+ Linef process_line(node.point, graph.nodes[arc.to_idx].point);
used_arcs[arc_idx] = true;
- Lines face_lines;
+ std::vector face_lines;
face_lines.emplace_back(process_line);
- Point start_p = process_line.a;
+ Vec2d start_p = process_line.a;
- Line p_vec = process_line;
+ Linef p_vec = process_line;
const MMU_Graph::Arc *p_arc = &arc;
do {
const MMU_Graph::Arc &next = get_next(p_vec, *p_arc);
@@ -1118,7 +1141,7 @@ static std::vector> extract_colored_segments(const MM
break;
used_arcs[next_arc_idx] = true;
- p_vec = Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point);
+ p_vec = Linef(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point);
p_arc = &next;
} while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx]));
@@ -1141,16 +1164,16 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st
used_arcs[start_arc_idx] = true;
const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx];
size_t idx = start_idx;
- double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast().norm();;
+ double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();;
while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) {
bool found = false;
for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) {
if (const MMU_Graph::Arc &arc_n = graph.arcs[arc_idx]; arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !used_arcs[arc_idx] && arc_n.to_idx != idx) {
- Line first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point);
- Line second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point);
+ Linef first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point);
+ Linef second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point);
- Vec2d first_line_vec = (first_line.a - first_line.b).cast();
- Vec2d second_line_vec = (second_line.b - second_line.a).cast();
+ Vec2d first_line_vec = (first_line.a - first_line.b);
+ Vec2d second_line_vec = (second_line.b - second_line.a);
Vec2d first_line_vec_n = first_line_vec.normalized();
Vec2d second_line_vec_n = second_line_vec.normalized();
double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0));
@@ -1163,7 +1186,7 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st
idx = arc->to_idx;
arc = &arc_n;
- line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast().norm();
+ line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();
used_arcs[arc_idx] = true;
found = true;
break;
@@ -1185,7 +1208,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
for (const std::pair &colored_segment : colored_segment_p) {
size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first);
size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]);
- Line seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point);
+ Linef seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point);
if (graph.nodes[first_idx].arc_idxs.size() >= 3) {
std::vector> arc_to_check;
@@ -1214,7 +1237,7 @@ static void cut_segmented_layers(const std::vector
const std::function &throw_on_cancel_callback)
{
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin";
- tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&](const tbb::blocked_range& range) {
+ tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
std::vector> segmented_regions_cuts;
@@ -1366,7 +1389,8 @@ static inline std::vector