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> mmu_segmentation_top_and_bott return out; }; - tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top, + &throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom](const tbb::blocked_range &range) { size_t group_idx = range.begin() / granularity; size_t layer_idx_offset = (group_idx & 1) * num_layers; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { @@ -1376,7 +1400,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { // Clean up thin projections. They are not printable anyways. - top_ex = offset2_ex(top_ex, - stat.small_region_threshold, + stat.small_region_threshold); + top_ex = opening_ex(top_ex, stat.small_region_threshold); if (! top_ex.empty()) { append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); float offset = 0.f; @@ -1384,8 +1408,7 @@ static inline std::vector> mmu_segmentation_top_and_bott for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) { offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = offset2_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), - - stat.small_region_threshold, + stat.small_region_threshold); + ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); if (last.empty()) break; append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); @@ -1395,7 +1418,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { // Clean up thin projections. They are not printable anyways. - bottom_ex = offset2_ex(bottom_ex, - stat.small_region_threshold, + stat.small_region_threshold); + bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); if (! bottom_ex.empty()) { append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); float offset = 0.f; @@ -1403,8 +1426,7 @@ static inline std::vector> mmu_segmentation_top_and_bott for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) { offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = offset2_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), - - stat.small_region_threshold, + stat.small_region_threshold); + ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); if (last.empty()) break; append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); @@ -1417,7 +1439,7 @@ static inline std::vector> mmu_segmentation_top_and_bott std::vector> triangles_by_color_merged(num_extruders); triangles_by_color_merged.assign(num_extruders, std::vector(num_layers)); - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &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(); for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { @@ -1446,7 +1468,7 @@ static std::vector>> merge_segmented_la std::vector>> segmented_regions_merged(segmented_regions.size()); BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging 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, &top_and_bottom_layers, &segmented_regions_merged, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { for (const std::pair &colored_expoly : segmented_regions[layer_idx]) { throw_on_cancel_callback(); @@ -1501,7 +1523,7 @@ static void export_graph_to_svg(const std::string &path, const MMU_Graph &graph, for (const MMU_Graph::Node &node : graph.nodes) for (const size_t &arc_idx : node.arc_idxs) { const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - Line arc_line(node.point, graph.nodes[arc.to_idx].point); + Line arc_line(mk_point(node.point), mk_point(graph.nodes[arc.to_idx].point)); if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color >= 0 && arc.color < int(colors.size())) svg.draw(arc_line, colors[arc.color], stroke_width); else @@ -1526,6 +1548,20 @@ void export_processed_input_expolygons_to_svg(const std::string &path, const Lay } #endif // MMU_SEGMENTATION_DEBUG_INPUT +// Check if all ColoredLine representing a single layer uses the same color. +static bool has_layer_only_one_color(const std::vector> &colored_polygons) +{ + assert(!colored_polygons.empty()); + assert(!colored_polygons.front().empty()); + int first_line_color = colored_polygons.front().front().color; + for (const std::vector &colored_polygon : colored_polygons) + for (const ColoredLine &colored_line : colored_polygon) + if (first_line_color != colored_line.color) + return false; + + return true; +} + std::vector>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { std::vector>> segmented_regions(print_object.layers().size()); @@ -1539,7 +1575,7 @@ std::vector>> multi_material_segmentati // Merge all regions and remove small holes BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, layers.size()), [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, layers.size()), [&layers, &input_expolygons, &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(); ExPolygons ex_polygons; @@ -1649,16 +1685,16 @@ std::vector>> multi_material_segmentati edge_grids[layer_idx].visit_cells_intersecting_line(line_start, line_end, visitor); } } - }); + }); // end of parallel_for } - }); + }); // end of parallel_for } BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - end"; BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - painted layers count: " << std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector &pl) { return !pl.empty(); }); BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, print_object.layers().size()), [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, print_object.layers().size()), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &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(); auto comp = [&edge_grids, layer_idx](const PaintedLine &first, const PaintedLine &second) { @@ -1677,20 +1713,28 @@ std::vector>> multi_material_segmentati if (!painted_lines_single.empty()) { std::vector> color_poly = colorize_polygons(edge_grids[layer_idx].contours(), painted_lines_single); - MMU_Graph graph = build_graph(layer_idx, color_poly); - remove_multiple_edges_in_vertices(graph, color_poly); - graph.remove_nodes_with_one_arc(); + assert(!color_poly.empty()); + assert(!color_poly.front().empty()); + if (has_layer_only_one_color(color_poly)) { + // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. + for (const ExPolygon &ex_polygon : input_expolygons[layer_idx]) + segmented_regions[layer_idx].emplace_back(ex_polygon, size_t(color_poly.front().front().color)); + } else { + MMU_Graph graph = build_graph(layer_idx, color_poly); + remove_multiple_edges_in_vertices(graph, color_poly); + graph.remove_nodes_with_one_arc(); #ifdef MMU_SEGMENTATION_DEBUG_GRAPH - { - static int iRun = 0; - export_graph_to_svg(debug_out_path("mm-graph-final-%d-%d.svg", layer_idx, iRun++), graph, input_expolygons[layer_idx]); - } + { + static int iRun = 0; + export_graph_to_svg(debug_out_path("mm-graph-final-%d-%d.svg", layer_idx, iRun++), graph, input_expolygons[layer_idx]); + } #endif // MMU_SEGMENTATION_DEBUG_GRAPH - std::vector> segmentation = extract_colored_segments(graph); - for (std::pair ®ion : segmentation) - segmented_regions[layer_idx].emplace_back(std::move(region)); + std::vector> segmentation = extract_colored_segments(graph); + for (std::pair ®ion : segmentation) + segmented_regions[layer_idx].emplace_back(std::move(region)); + } #ifdef MMU_SEGMENTATION_DEBUG_REGIONS { diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 3190845bd..55d3c6aa5 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -347,10 +347,10 @@ void PerimeterGenerator::process() // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); - ExPolygons expp = offset2_ex( + ExPolygons expp = opening_ex( // medial axis requires non-overlapping geometry diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), - - float(min_width / 2.), float(min_width / 2.)); + float(min_width / 2.)); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygon &ex : expp) ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); @@ -495,7 +495,7 @@ void PerimeterGenerator::process() double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( //FIXME offset2 would be enough and cheaper. - offset2_ex(gaps, - float(min / 2.), float(min / 2.)), + opening_ex(gaps, float(min / 2.)), offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); ThickPolylines polylines; for (const ExPolygon &ex : gaps_ex) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b33e8bda2..03a3d1e57 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1139,7 +1139,7 @@ void Print::_make_wipe_tower() // Insert the new support layer. double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z); //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. - it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); + it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height); ++ it_layer; } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 0056aee33..ac986216e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -300,8 +300,8 @@ public: size_t support_layer_count() const { return m_support_layers.size(); } void clear_support_layers(); SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } - SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); - SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); + SupportLayer* add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z); + SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2917a9a19..b34a6cb58 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1853,7 +1853,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters " "(and thus any ooze will be probably invisible)."); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("ooze_prevention", coBool); def->label = L("Enable"); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f49eddf3e..fd98feff7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -461,15 +461,15 @@ void PrintObject::clear_support_layers() m_support_layers.clear(); } -SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z) +SupportLayer* PrintObject::add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z) { - m_support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1)); + m_support_layers.emplace_back(new SupportLayer(id, interface_id, this, height, print_z, -1)); return m_support_layers.back(); } -SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) +SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z) { - return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); + return m_support_layers.insert(pos, new SupportLayer(id, interface_id, this, height, print_z, slice_z)); } // Called by Print::apply(). @@ -774,7 +774,7 @@ void PrintObject::detect_surfaces_type() ExPolygons upper_slices = interface_shells ? diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); - surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop); + surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection @@ -792,15 +792,15 @@ void PrintObject::detect_surfaces_type() to_polygons(lower_layer->get_region(region_id)->slices.surfaces) : to_polygons(lower_layer->slices); surfaces_append(bottom, - offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset), + opening_ex(diff(layerm->slices.surfaces, lower_slices, true), offset), surface_type_bottom_other); #else // Any surface lying on the void is a true bottom bridge (an overhang) surfaces_append( bottom, - offset2_ex( + opening_ex( diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), - -offset, offset), + offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces // lying on other slices not belonging to this region @@ -809,12 +809,12 @@ void PrintObject::detect_surfaces_type() // on something else, excluding those lying on our own region surfaces_append( bottom, - offset2_ex( + opening_ex( diff_ex( intersection(layerm->slices.surfaces, lower_layer->lslices), // supported lower_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes), - -offset, offset), + offset), stBottom); } #endif @@ -1088,7 +1088,7 @@ void PrintObject::discover_vertical_shells() // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. - polygons_append(cache.holes, offset(offset_ex(layer.lslices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing)); + polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); @@ -1325,7 +1325,7 @@ void PrintObject::discover_vertical_shells() #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); + shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else @@ -1337,7 +1337,7 @@ void PrintObject::discover_vertical_shells() // get a triangle in $too_narrow; if we grow it below then the shell // would have a different shape from the external surface and we'd still // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true); + Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -1453,7 +1453,7 @@ void PrintObject::bridge_over_infill() // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. { float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); + to_bridge_pp = opening(to_bridge_pp, min_width); } if (to_bridge_pp.empty()) continue; @@ -1744,7 +1744,7 @@ void PrintObject::clip_fill_surfaces() for (const LayerRegion *layerm : layer->m_regions) pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); // Append such thick perimeters to the areas that need support - polygons_append(overhangs, offset2(perimeters, -pw, +pw)); + polygons_append(overhangs, opening(perimeters, pw)); } // Find new internal infill. polygons_append(overhangs, std::move(upper_internal)); @@ -1884,7 +1884,7 @@ void PrintObject::discover_horizontal_shells() float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, jtMiter, 5)); + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5)); // Trim the regularized region by the original region. if (! too_narrow.empty()) new_internal_solid = solid = diff(new_internal_solid, too_narrow); @@ -1903,7 +1903,7 @@ void PrintObject::discover_horizontal_shells() // have the same angle, so the next shell would be grown even more and so on. Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -1915,7 +1915,7 @@ void PrintObject::discover_horizontal_shells() polygons_append(internal, to_polygons(surface.expolygon)); polygons_append(new_internal_solid, intersection( - offset(too_narrow, +margin), + expand(too_narrow, +margin), // Discard bridges as they are grown for anchoring and we can't // remove such anchors. (This may happen when a bridge is being // anchored onto a wall where little space remains after the bridge diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 7db0de626..e2844a624 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -393,7 +393,7 @@ static std::vector> slices_to_regions( } } if (merged) - expolygons = offset2_ex(expolygons, float(scale_(EPSILON)), -float(scale_(EPSILON))); + expolygons = closing_ex(expolygons, float(scale_(EPSILON))); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); i = j; } @@ -648,7 +648,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance ByRegion &src = by_region[region_id]; if (src.needs_merge) // Multiple regions were merged into one. - src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON))); + src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON))); layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); } } diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index bbc6b03fa..c32da0431 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -186,8 +186,8 @@ static std::vector make_layers( // Produce 2 bands around the island, a safe band for dangling overhangs // and an unsafe band for sloped overhangs. // These masks include the original island - auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); - auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); + auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); + auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare); // Absolutely hopeless overhangs are those outside the unsafe band top.overhangs = diff_ex(*top.polygon, overh_mask); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 004f7d555..1bc548914 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -138,14 +138,14 @@ Transform3d SLAPrint::sla_trafo(const ModelObject &model_object) const offset(1) = 0.; rotation(2) = 0.; - offset(Z) *= corr(Z); + offset.z() *= corr.z(); auto trafo = Transform3d::Identity(); trafo.translate(offset); trafo.scale(corr); - trafo.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); - trafo.rotate(Eigen::AngleAxisd(rotation(1), Vec3d::UnitY())); - trafo.rotate(Eigen::AngleAxisd(rotation(0), Vec3d::UnitX())); + trafo.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ())); + trafo.rotate(Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY())); + trafo.rotate(Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); trafo.scale(model_instance.get_scaling_factor()); trafo.scale(model_instance.get_mirror()); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 2099cbf73..a668a385b 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -367,6 +367,29 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object // Object is printed with the same extruder as the support. m_support_params.can_merge_support_regions = true; } + + + m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); + m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); + m_support_params.interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); + m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing); + m_support_params.support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); + m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing); + if (m_object_config->support_material_interface_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + m_support_params.interface_spacing = m_support_params.support_spacing; + m_support_params.interface_density = m_support_params.support_density; + } + + SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; + m_support_params.with_sheath = m_object_config->support_material_with_sheath; + m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (m_support_params.support_density > 0.95 ? ipRectilinear : ipSupportBase); + m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); + m_support_params.contact_fill_pattern = + (m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || + m_object_config->support_material_interface_pattern == smipConcentric ? + ipConcentric : + (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); } // Using the std::deque as an allocator. @@ -397,6 +420,11 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr dst.insert(dst.end(), src.begin(), src.end()); } +// Support layer that is covered by some form of dense interface. +static constexpr const std::initializer_list support_types_interface { + PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface +}; + void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -411,6 +439,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; + // Per object layer projection of the object below the layer into print bed. std::vector buildplate_covered = this->buildplate_covered(object); // Determine the top contact surfaces of the support, defined as: @@ -545,6 +574,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Sort the layers lexicographically by a raising print_z and a decreasing height. std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); int layer_id = 0; + int layer_id_interface = 0; assert(object.support_layers().empty()); for (size_t i = 0; i < layers_sorted.size();) { // Find the last layer with roughly the same print_z, find the minimum layer height of all. @@ -556,17 +586,43 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); coordf_t height_min = layers_sorted[i]->height; bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; for (size_t u = i; u < j; ++u) { MyLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) - empty = false; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == sltTopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } layer.print_z = zavg; height_min = std::min(height_min, layer.height); } if (! empty) { // Here the upper_layer and lower_layer pointers are left to null at the support layers, // as they are never used. These pointers are candidates for removal. - object.add_support_layer(layer_id ++, height_min, zavg); + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; } i = j; } @@ -882,7 +938,14 @@ public: // Merge the support polygons by applying morphological closing and inwards smoothing. auto closing_distance = scaled(m_support_material_closing_radius); auto smoothing_distance = scaled(m_extrusion_width); - return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance); +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z), + { { { diff_ex(expand(*m_support_polygons, closing_distance), closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "closed", "blue", 0.5f } }, + { { union_ex(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance)) }, { "regularized", "red", "black", "", scaled(0.1f), 0.5f } }, + { { union_ex(*m_support_polygons) }, { "src", "green", 0.5f } }, + }); +#endif /* SLIC3R_DEBUG */ + return smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance); } assert(false); return Polygons(); @@ -1241,7 +1304,7 @@ namespace SupportMaterialInternal { const PrintConfig &print_config, const Layer &lower_layer, const Polygons &lower_layer_polygons, - LayerRegion *layerm, + const LayerRegion &layerm, float fw, Polygons &contact_polygons) { @@ -1249,21 +1312,21 @@ namespace SupportMaterialInternal { Polygons bridges; { // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. - Polygons lower_grown_slices = offset(lower_layer_polygons, + Polygons lower_grown_slices = expand(lower_layer_polygons, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region().config().perimeter_extruder-1))), + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. //FIXME split_at_first_point() could split a bridge mid-way #if 0 - Polylines overhang_perimeters = layerm->perimeters.as_polylines(); + Polylines overhang_perimeters = layerm.perimeters.as_polylines(); // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() for (Polyline &polyline : overhang_perimeters) polyline.points[0].x += 1; // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); #else - Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); + Polylines overhang_perimeters = diff_pl(layerm.perimeters.as_polylines(), lower_grown_slices); #endif // only consider straight overhangs @@ -1272,7 +1335,7 @@ namespace SupportMaterialInternal { // since we're dealing with bridges, we can't assume width is larger than spacing, // so we take the largest value and also apply safety offset to be ensure no gaps // are left in between - Flow perimeter_bridge_flow = layerm->bridging_flow(frPerimeter); + Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter); float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())); for (Polyline &polyline : overhang_perimeters) if (polyline.is_straight()) { @@ -1293,8 +1356,8 @@ namespace SupportMaterialInternal { bridges = union_(bridges); } // remove the entire bridges and only support the unsupported edges - //FIXME the brided regions are already collected as layerm->bridged. Use it? - for (const Surface &surface : layerm->fill_surfaces.surfaces) + //FIXME the brided regions are already collected as layerm.bridged. Use it? + for (const Surface &surface : layerm.fill_surfaces.surfaces) if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? @@ -1302,14 +1365,14 @@ namespace SupportMaterialInternal { //FIXME add supports at regular intervals to support long bridges! bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Remove bridged areas from the supported areas. contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); #ifdef SLIC3R_DEBUG static int iRun = 0; SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { { union_ex(offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ @@ -1325,6 +1388,7 @@ std::vector PrintObjectSupportMaterial::buildplate_covered(const Print if (buildplate_only) { BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - start"; buildplate_covered.assign(object.layers().size(), Polygons()); + //FIXME prefix sum algorithm, parallelize it! Parallelization will also likely be more numerically stable. for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) { const Layer &lower_layer = *object.layers()[layer_id-1]; // Merge the new slices with the preceding slices. @@ -1368,6 +1432,8 @@ struct SlicesMarginCache Polygons all_polygons; }; +// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset +// no_interface_offset: minimum of external perimeter widths static inline std::tuple detect_overhangs( const Layer &layer, const size_t layer_id, @@ -1410,9 +1476,9 @@ static inline std::tuple detect_overhangs( overhang_polygons = to_polygons(layer.lslices); #endif // Expand for better stability. - contact_polygons = offset(overhang_polygons, scaled(object_config.raft_expansion.value)); + contact_polygons = expand(overhang_polygons, scaled(object_config.raft_expansion.value)); } - else + else if (! layer.regions().empty()) { // Generate overhang / contact_polygons for non-raft layers. const Layer &lower_layer = *layer.lower_layer; @@ -1426,6 +1492,7 @@ static inline std::tuple detect_overhangs( slices_margin.offset = slices_margin_offset; slices_margin.polygons = (slices_margin_offset == 0.f) ? lower_layer_polygons : + // What is the purpose of no_interface_offset? Likely to not trim the contact layer by lower layer regions that are too thin to extrude? offset2(lower_layer.lslices, -no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) { if (has_enforcer) @@ -1437,14 +1504,14 @@ static inline std::tuple detect_overhangs( } }; - float fw = 0; + no_interface_offset = std::accumulate(layer.regions().begin(), layer.regions().end(), FLT_MAX, + [](float acc, const LayerRegion *layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); + float lower_layer_offset = 0; - float no_interface_offset = 0; for (LayerRegion *layerm : layer.regions()) { // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. - fw = float(layerm->flow(frExternalPerimeter).scaled_width()); - no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); + float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); lower_layer_offset = (layer_id < (size_t)object_config.support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. @@ -1465,37 +1532,40 @@ static inline std::tuple detect_overhangs( // This step is done before the contact surface is calculated by growing the overhang region. diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); } - } else { - if (support_auto) { - // Get the regions needing a suport, collapse very tiny spots. - //FIXME cache the lower layer offset if this layer has multiple regions. -#if 1 - //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 - diff_polygons = offset2( - diff(layerm_polygons, - offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to - // no support at all for not so steep overhangs. - - 0.1f * fw, 0.1f * fw); + } else if (support_auto) { + // Get the regions needing a suport, collapse very tiny spots. + //FIXME cache the lower layer offset if this layer has multiple regions. +#if 0 + //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 + diff_polygons = opening( + diff(layerm_polygons, + // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they + // are not supporting this layer. + // However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports. + // For example, see GH issue #3094 + opening(lower_layer_polygons, 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This opening is targeted to reduce very thin regions to support, but it may lead to + // no support at all for not so steep overhangs. + 0.1f * fw); #else - diff_polygons = - diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + diff_polygons = + diff(layerm_polygons, + expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #endif - if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); - } - if (! diff_polygons.empty()) { - // Offset the support regions back to a full overhang, restrict them to the full overhang. - // This is done to increase size of the supporting columns below, as they are calculated by - // propagating these contact surfaces downwards. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); - } + if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); } + if (! diff_polygons.empty()) { + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } + //FIXME add user defined filtering here based on minimal area or minimum radius or whatever. } if (diff_polygons.empty()) @@ -1508,7 +1578,7 @@ static inline std::tuple detect_overhangs( // Subtracting them as they are may leave unwanted narrow // residues of diff_polygons that would then be supported. diff_polygons = diff(diff_polygons, - offset(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); + expand(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); } #ifdef SLIC3R_DEBUG @@ -1523,8 +1593,9 @@ static inline std::tuple detect_overhangs( #endif /* SLIC3R_DEBUG */ if (object_config.dont_support_bridges) + //FIXME Expensive, potentially not precise enough. Misses gap fill extrusions, which bridge. SupportMaterialInternal::remove_bridges_from_contacts( - print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); + print_config, lower_layer, lower_layer_polygons, *layerm, fw, diff_polygons); if (diff_polygons.empty()) continue; @@ -1579,7 +1650,7 @@ static inline std::tuple detect_overhangs( #endif // SLIC3R_DEBUG enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, @@ -1596,6 +1667,8 @@ static inline std::tuple detect_overhangs( return std::make_tuple(std::move(overhang_polygons), std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); } +// Allocate one, possibly two support contact layers. +// For "thick" overhangs, one support layer will be generated to support normal extrusions, the other to support the "thick" extrusions. static inline std::pair new_contact_layer( const PrintConfig &print_config, const PrintObjectConfig &object_config, @@ -1630,15 +1703,18 @@ static inline std::pair 1 ? slicing_params.raft_interface_top_z + support_layer_height_min + EPSILON : slicing_params.first_print_layer_height - EPSILON; - if (print_z < min_print_z) { + if (print_z < slicing_params.first_print_layer_height - EPSILON) { // This contact layer is below the first layer height, therefore not printable. Don't support this surface. return std::pair(nullptr, nullptr); - } else if (print_z < slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - print_z = slicing_params.first_print_layer_height; - bottom_z = 0; - height = slicing_params.first_print_layer_height; + } + const bool has_raft = slicing_params.raft_layers() > 1; + const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height; + if (print_z < min_print_z + support_layer_height_min) { + // Align the layer with the 1st layer height or the raft contact layer. + // With raft active, any contact layer below the raft_contact_top_z will be brought to raft_contact_top_z to extend the raft area. + print_z = min_print_z; + bottom_z = has_raft ? slicing_params.raft_interface_top_z : 0; + height = has_raft ? slicing_params.contact_raft_layer_height : min_print_z; } else { // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and // its height will be set adaptively later on. @@ -1654,9 +1730,9 @@ static inline std::pair= min_print_z) { // Not below the first layer height means this layer is printable. - if (print_z < slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - bridging_print_z = slicing_params.first_print_layer_height; + if (print_z < min_print_z + support_layer_height_min) { + // Align the layer with the 1st layer height or the raft contact layer. + bridging_print_z = min_print_z; } if (bridging_print_z < print_z - EPSILON) { // Allocate the new layer. @@ -1708,7 +1784,8 @@ static inline void fill_contact_layer( auto lower_layer_polygons_for_dense_interface = [&lower_layer_polygons_for_dense_interface_cache, &lower_layer_polygons, no_interface_offset]() -> const Polygons& { if (lower_layer_polygons_for_dense_interface_cache.empty()) lower_layer_polygons_for_dense_interface_cache = - offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); + //FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height. + opening(lower_layer_polygons, no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); return lower_layer_polygons_for_dense_interface_cache; }; @@ -1721,21 +1798,15 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG )); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - if (layer_id == 0 || slicing_params.soluble_interface) { - // if (no_interface_offset == 0.f) { - new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true -#ifdef SLIC3R_DEBUG - , "top_contact_polygons2", iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - ); - } else { + bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; + if (reduce_interfaces) { // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); if (! dense_interface_polygons.empty()) { dense_interface_polygons = diff( // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), + expand(dense_interface_polygons, no_interface_offset * 0.1f), slices_margin.polygons); // Support islands, to be stretched into a grid. //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, @@ -1746,7 +1817,7 @@ static inline void fill_contact_layer( SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.polygons, grid_params); new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false #ifdef SLIC3R_DEBUG - , "top_contact_polygons3", iRun, layer_id, layer.print_z + , "top_contact_polygons2", iRun, layer_id, layer.print_z #endif // SLIC3R_DEBUG ); #ifdef SLIC3R_DEBUG @@ -1765,45 +1836,59 @@ static inline void fill_contact_layer( { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ } + } else { + new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true +#ifdef SLIC3R_DEBUG + , "top_contact_polygons3", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); } if (! enforcer_polygons.empty() && ! slices_margin.all_polygons.empty() && layer_id > 0) { // Support enforcers used together with support enforcers. The support enforcers need to be handled separately from the rest of the support. - { - SupportGridPattern support_grid_pattern(&enforcer_polygons, &slices_margin.all_polygons, grid_params); - // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - new_layer.enforcer_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true - #ifdef SLIC3R_DEBUG - , "top_contact_polygons4", iRun, layer_id, layer.print_z - #endif // SLIC3R_DEBUG - )); - } - // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. - Polygons dense_interface_polygons = diff(enforcer_polygons, lower_layer_polygons_for_dense_interface()); - if (! dense_interface_polygons.empty()) { - dense_interface_polygons = - diff( - // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), - slices_margin.all_polygons); - // Support islands, to be stretched into a grid. - //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, - // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. - // See for example GH #4874. - Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.enforcer_polygons); - SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.all_polygons, grid_params); - // Extend the polygons to extrude with the contact polygons of support enforcers. - bool needs_union = ! new_layer.polygons.empty(); - append(new_layer.polygons, support_grid_pattern.extract_support(grid_params.expansion_to_slice, false + SupportGridPattern support_grid_pattern(&enforcer_polygons, &slices_margin.all_polygons, grid_params); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.enforcer_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true #ifdef SLIC3R_DEBUG - , "top_contact_polygons5", iRun, layer_id, layer.print_z + , "top_contact_polygons4", iRun, layer_id, layer.print_z #endif // SLIC3R_DEBUG - )); - if (needs_union) - new_layer.polygons = union_(new_layer.polygons); + )); + Polygons new_polygons; + bool needs_union = ! new_layer.polygons.empty(); + if (reduce_interfaces) { + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. + Polygons dense_interface_polygons = diff(enforcer_polygons, lower_layer_polygons_for_dense_interface()); + if (! dense_interface_polygons.empty()) { + dense_interface_polygons = + diff( + // Regularize the contour. + expand(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin.all_polygons); + // Support islands, to be stretched into a grid. + //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, + // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. + // See for example GH #4874. + Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.enforcer_polygons); + SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.all_polygons, grid_params); + // Extend the polygons to extrude with the contact polygons of support enforcers. + new_polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false + #ifdef SLIC3R_DEBUG + , "top_contact_polygons5", iRun, layer_id, layer.print_z + #endif // SLIC3R_DEBUG + ); + } + } else { + new_polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true + #ifdef SLIC3R_DEBUG + , "top_contact_polygons6", iRun, layer_id, layer.print_z + #endif // SLIC3R_DEBUG + ); } + append(new_layer.polygons, std::move(new_polygons)); + if (needs_union) + new_layer.polygons = union_(new_layer.polygons); } #ifdef SLIC3R_DEBUG @@ -1917,9 +2002,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ ); // Now apply the contact areas to the layer where they need to be made. - if (! contact_polygons.empty()) { + if (! contact_polygons.empty() || ! overhang_polygons.empty()) { + // Allocate the two empty layers. auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); if (new_layer) { + // Fill the non-bridging layer with polygons. fill_contact_layer(*new_layer, layer_id, m_slicing_params, *m_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons, m_support_params.support_material_flow, no_interface_offset @@ -1927,6 +2014,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ , iRun, layer #endif // SLIC3R_DEBUG ); + // Insert new layer even if there is no interface generated: Likely the support angle is not steep enough to require dense interface, + // however generating a sparse support will be useful for the object stability. + // if (! new_layer->polygons.empty()) contact_out[layer_id * 2] = new_layer; if (bridging_layer != nullptr) { bridging_layer->polygons = new_layer->polygons; @@ -1944,6 +2034,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); + // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), + // the top contact layer is merged into the bottom contact layer. merge_contact_layers(m_slicing_params, m_support_params.support_layer_height_min, contact_out); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; @@ -2014,8 +2106,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( layer_new.idx_object_layer_below = layer_id; layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. - //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. - layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); + layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! slicing_params.soluble_interface) { // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, @@ -2054,7 +2145,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? - touching = offset(touching, float(SCALED_EPSILON)); + touching = expand(touching, float(SCALED_EPSILON)); for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers()[layer_id_above]; if (layer_above.print_z > layer_new.print_z - EPSILON) @@ -2222,7 +2313,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta #endif // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. // Use a slight positive offset to overlap the touching regions. - polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON))); + polygons_append(polygons_new, expand(*top_contact.overhang_polygons, float(SCALED_EPSILON))); polygons_append(overhangs_projection, union_(polygons_new)); polygons_append(enforcers_projection, enforcers_new); } @@ -2451,14 +2542,16 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. // Intermediate layers are always printed with a normal etrusion flow (non-bridging). size_t idx_layer_object = 0; - for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) { + size_t idx_extreme_first = 0; + if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) { + // This is a raft contact layer, its height has been decided in this->top_contact_layers(). + // Ignore this layer when calculating the intermediate support layers. + assert(extremes.front()->layer_type == sltTopContact); + ++ idx_extreme_first; + } + for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) { MyLayer *extr2 = extremes[idx_extreme]; coordf_t extr2z = extr2->extreme_z(); - if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) { - // This is a raft contact layer, its height has been decided in this->top_contact_layers(). - assert(extr2->layer_type == sltTopContact); - continue; - } if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). assert(extr2->layer_type == sltTopContact); @@ -2475,7 +2568,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int } assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); - MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1]; + MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; // Fuse a support layer firmly to the raft top interface (not to the raft contacts). coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); assert(extr2z >= extr1z); @@ -2840,7 +2933,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf if (brim_inner) { Polygons holes = ex.holes; polygons_reverse(holes); - holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1))); + holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); polygons_reverse(holes); polygons_append(brim, std::move(holes)); } else @@ -2872,11 +2965,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf Polygons interface_polygons; if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) - polygons_append(interface_polygons, offset(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Output vector. MyLayersPtr raft_layers; @@ -2903,7 +2996,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.height = m_slicing_params.first_print_layer_height; new_layer.bottom_z = 0.; - new_layer.polygons = inflate_factor_1st_layer > 0 ? offset(base, inflate_factor_1st_layer) : base; + new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; } // Insert the base layers. for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { @@ -2937,7 +3030,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; for (int i = 0; i < nsteps; ++ i) - raft = diff(offset(raft, step), trimming); + raft = diff(expand(raft, step), trimming); } else raft = diff(raft, trimming); if (contacts != nullptr) @@ -2981,6 +3074,7 @@ std::pairsupport_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && // Base extruder: Either "print with active extruder" not soluble. (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); + bool snug_supports = m_object_config->support_material_style.value == smsSnug; int num_interface_layers_top = m_object_config->support_material_interface_layers; int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; if (num_interface_layers_bottom < 0) @@ -2999,31 +3093,48 @@ std::pair(m_object_config->support_material_closing_radius.value); tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, &layer_storage_mutex](MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) { + auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* { assert(! bottom.empty() || ! top.empty()); - MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); - layer_new.print_z = intermediate_layer.print_z; - layer_new.bottom_z = intermediate_layer.bottom_z; - layer_new.height = intermediate_layer.height; - layer_new.bridging = intermediate_layer.bridging; // Merge top into bottom, unite them with a safety offset. append(bottom, std::move(top)); - layer_new.polygons = intersection(union_safety_offset(std::move(bottom)), intermediate_layer.polygons); - // Subtract the interface from the base regions. - intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); - if (subtract) - // Trim the base interface layer with the interface layer. - layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); - //FIXME filter layer_new.polygons islands by a minimum area? -// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; - return &layer_new; + // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). + bottom = intersection( + snug_supports ? + smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + union_safety_offset(std::move(bottom)), + intermediate_layer.polygons); + if (! bottom.empty()) { + //FIXME Remove non-printable tiny islands, let them be printed using the base support. + //bottom = opening(std::move(bottom), minimum_island_radius); + if (! bottom.empty()) { + MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); + layer_new.polygons = std::move(bottom); + layer_new.print_z = intermediate_layer.print_z; + layer_new.bottom_z = intermediate_layer.bottom_z; + layer_new.height = intermediate_layer.height; + layer_new.bridging = intermediate_layer.bridging; + // Subtract the interface from the base regions. + intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); + if (subtract) + // Trim the base interface layer with the interface layer. + layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); + //FIXME filter layer_new.polygons islands by a minimum area? + // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; + return &layer_new; + } + } + return nullptr; }; tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, - &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { + snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below // this intermediate layer. // Index of the first top contact layer intersecting the current intermediate layer. @@ -3055,7 +3166,10 @@ std::pair top_z) break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, + // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. + // For grid supports, merging of support regions will be performed by the projection into grid. + snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); } } if (num_interface_layers_bottom > 0) { @@ -3164,7 +3278,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( return; if (! with_sheath) { - fill_expolygons_generate_paths(dst, offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)), filler, density, role, flow); + fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); return; } @@ -3176,7 +3290,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. double clip_length = spacing * 0.15; - for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) { + for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { // Don't reorder the skirt and its infills. std::unique_ptr eec; if (no_sort) { @@ -3429,10 +3543,10 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const // make more loops Polygons loop_polygons = loops0; for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - offset2( + polygons_append(loop_polygons, + opening( loops0, - - i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), + i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); // Clip such loops to the side oriented towards the object. // Collect split points, so they will be recognized after the clipping. @@ -3444,7 +3558,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const map_split_points[it->first_point()] = -1; loop_lines.push_back(it->split_at_first_point()); } - loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. // Try to connect them. for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { @@ -3784,27 +3898,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; - float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); - float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); - coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); - coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing); - coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); - coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing); - if (m_object_config->support_material_interface_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - interface_spacing = support_spacing; - interface_density = support_density; - } - - // Prepare fillers. - SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; - bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density < 1.05 ? ipRectilinear : ipSupportBase); - std::vector angles; - angles.push_back(base_angle); - - if (support_pattern == smpRectilinearGrid) - angles.push_back(interface_angle); + std::vector angles { m_support_params.base_angle }; + if (m_object_config->support_material_pattern == smpRectilinearGrid) + angles.push_back(m_support_params.interface_angle); BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); @@ -3816,16 +3912,16 @@ void PrintObjectSupportMaterial::generate_toolpaths( float raft_angle_interface = 0.f; if (m_slicing_params.base_raft_layers > 1) { // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = interface_angle; - raft_angle_base = base_angle; - raft_angle_interface = interface_angle; + raft_angle_1st_layer = m_support_params.interface_angle; + raft_angle_base = m_support_params.base_angle; + raft_angle_interface = m_support_params.interface_angle; } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { // 1st layer, interface & contact layers available. - raft_angle_1st_layer = base_angle; + raft_angle_1st_layer = m_support_params.base_angle; if (this->has_support()) // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. raft_angle_1st_layer += 0.7854f; - raft_angle_interface = interface_angle; + raft_angle_interface = m_support_params.interface_angle; } else if (m_slicing_params.interface_raft_layers == 1) { // Only the contact raft layer is non-empty, which will be printed as the 1st layer. assert(m_slicing_params.base_raft_layers == 0); @@ -3842,7 +3938,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [this, &support_layers, &raft_layers, - infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath] + &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] (const tbb::blocked_range& range) { for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { @@ -3851,8 +3947,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(support_layer.support_fills.entities.empty()); MyLayer &raft_layer = *raft_layers[support_layer_id]; - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(ipRectilinear)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); @@ -3868,17 +3964,17 @@ void PrintObjectSupportMaterial::generate_toolpaths( Fill * filler = filler_support.get(); filler->angle = raft_angle_base; filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); fill_expolygons_with_sheath_generate_paths( // Destination support_layer.support_fills.entities, // Regions to fill to_infill_polygons, // Filler and its parameters - filler, float(support_density), + filler, float(m_support_params.support_density), // Extrusion parameters erSupportMaterial, flow, - with_sheath, false); + m_support_params.with_sheath, false); } } @@ -3897,7 +3993,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler->spacing = m_support_params.support_material_flow.spacing(); assert(! raft_layer.bridging); flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - density = float(interface_density); + density = float(m_support_params.interface_density); } else continue; filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); @@ -3938,15 +4034,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( }; std::vector layer_caches(support_layers.size()); - - const auto fill_type_interface = - (m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_material_interface_pattern == smipConcentric ? - ipConcentric : ipRectilinear; - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - infill_pattern, &bbox_object, support_density, fill_type_interface, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath] + &bbox_object, &angles, link_max_length_factor] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. size_t idx_layer_bottom_contact = size_t(-1); @@ -3955,14 +4045,14 @@ void PrintObjectSupportMaterial::generate_toolpaths( size_t idx_layer_interface = size_t(-1); size_t idx_layer_base_interface = size_t(-1); const auto fill_type_first_layer = ipRectilinear; - auto filler_interface = std::unique_ptr(Fill::new_from_type(fill_type_interface)); + auto filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && fill_type_interface != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); // Pointer to the 1st layer interface filler. auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear)); - auto filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : Fill::new_from_type(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); if (filler_first_layer_ptr) filler_first_layer_ptr->set_bounding_box(bbox_object); @@ -3973,6 +4063,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; + float interface_angle_delta = m_object_config->support_material_style.value == smsSnug ? + (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : + 0; // Find polygons with the same print_z. MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; @@ -4052,8 +4145,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. - interface_angle; - double density = interface_as_base ? support_density : interface_density; + m_support_params.interface_angle + interface_angle_delta; + double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density; filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( @@ -4074,9 +4167,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) assert(! base_interface_layer.layer->bridging); Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = interface_angle; + filler->angle = m_support_params.interface_angle + interface_angle_delta; filler->spacing = m_support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density)); fill_expolygons_generate_paths( // Destination base_interface_layer.extrusions, @@ -4084,7 +4177,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Regions to fill union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), // Filler and its parameters - filler, float(interface_density), + filler, float(m_support_params.interface_density), // Extrusion parameters erSupportMaterial, interface_flow); } @@ -4098,9 +4191,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(! base_layer.layer->bridging); auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); - float density = float(support_density); - bool sheath = with_sheath; + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); + float density = float(m_support_params.support_density); + bool sheath = m_support_params.with_sheath; bool no_sort = false; if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index a1bd81297..65604ef72 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -132,6 +132,18 @@ public: // coordf_t support_layer_height_max; coordf_t gap_xy; + + float base_angle; + float interface_angle; + coordf_t interface_spacing; + coordf_t interface_density; + coordf_t support_spacing; + coordf_t support_density; + + InfillPattern base_fill_pattern; + InfillPattern interface_fill_pattern; + InfillPattern contact_fill_pattern; + bool with_sheath; }; // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 350f6aedc..b22448a88 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,4 +73,17 @@ #define ENABLE_FIX_SUPERSLICER_GCODE_IMPORT (1 && ENABLE_2_4_0_ALPHA3) +//==================== +// 2.4.0.alpha4 techs +//==================== +#define ENABLE_2_4_0_ALPHA4 1 + +// Enable rendering modifiers and similar objects always as transparent +#define ENABLE_MODIFIERS_ALWAYS_TRANSPARENT (1 && ENABLE_2_4_0_ALPHA4) + +// Enable the fix for the detection of the out of bed state for sinking objects +// and detection of out of bed using the bed perimeter +#define ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS (1 && ENABLE_2_4_0_ALPHA4) + + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 106da4a78..4e7bd073a 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -208,7 +208,7 @@ void name_tbb_thread_pool_threads_set_locale() nthreads = 1; #endif - std::atomic nthreads_running(0); + size_t nthreads_running(0); std::condition_variable cv; std::mutex cv_m; auto master_thread_id = std::this_thread::get_id(); @@ -216,13 +216,13 @@ void name_tbb_thread_pool_threads_set_locale() tbb::blocked_range(0, nthreads, 1), [&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range &range) { assert(range.begin() + 1 == range.end()); - if (nthreads_running.fetch_add(1) + 1 == nthreads) { + if (std::unique_lock lk(cv_m); ++nthreads_running == nthreads) { + lk.unlock(); // All threads are spinning. // Wake them up. cv.notify_all(); } else { // Wait for the last thread to wake the others. - std::unique_lock lk(cv_m); cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;}); } auto thread_id = std::this_thread::get_id(); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index bb3c9fc5c..09cb89372 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -435,6 +435,31 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c return bbox; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafo, double world_min_z) const +{ + BoundingBoxf3 bbox; + const Transform3f ftrafo = trafo.cast(); + for (const stl_triangle_vertex_indices& tri : its.indices) { + const Vec3f pts[3] = { ftrafo * its.vertices[tri(0)], ftrafo * its.vertices[tri(1)], ftrafo * its.vertices[tri(2)] }; + int iprev = 2; + for (int iedge = 0; iedge < 3; ++iedge) { + const Vec3f& p1 = pts[iprev]; + const Vec3f& p2 = pts[iedge]; + if ((p1.z() < world_min_z && p2.z() > world_min_z) || (p2.z() < world_min_z && p1.z() > world_min_z)) { + // Edge crosses the z plane. Calculate intersection point with the plane. + const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); + bbox.merge(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z).cast()); + } + if (p2.z() >= world_min_z) + bbox.merge(p2.cast()); + iprev = iedge; + } + } + return bbox; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + TriangleMesh TriangleMesh::convex_hull_3d() const { // The qhull call: diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 5223631c0..ec6401982 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -125,6 +125,10 @@ public: BoundingBoxf3 bounding_box() const; // Returns the bbox of this TriangleMesh transformed by the given transformation BoundingBoxf3 transformed_bounding_box(const Transform3d &trafo) const; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // Variant returning the bbox of the part of this TriangleMesh above the given world_min_z + BoundingBoxf3 transformed_bounding_box(const Transform3d& trafo, double world_min_z) const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // Return the size of the mesh in coordinates. Vec3d size() const { return m_stats.size.cast(); } /// Return the center of the related bounding box. diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index d3c9a49b5..0f3d0f5b6 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1617,7 +1617,7 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c /* The following line is commented out because it can generate wrong polygons, see for example issue #661 */ - //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); + //ExPolygons ex_slices = closing(p_slices, safety_offset); #ifdef SLIC3R_TRIANGLEMESH_DEBUG size_t holes_count = 0; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 987ef1c0a..f65292f03 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -127,7 +127,8 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, float radius, CursorType cursor_type, EnforcerBlockerType new_state, - const Transform3d& trafo, bool triangle_splitting) + const Transform3d& trafo, const Transform3d& trafo_no_translate, + bool triangle_splitting, float highlight_by_angle_deg) { assert(facet_start < m_orig_size_indices); @@ -143,6 +144,9 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, m_old_cursor_radius_sqr = m_cursor.radius_sqr; } + const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); + Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); + // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check; facets_to_check.reserve(16); @@ -153,14 +157,14 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // Head of the bread-first facets_to_check FIFO. int facet_idx = 0; while (facet_idx < int(facets_to_check.size())) { - int facet = facets_to_check[facet_idx]; - if (! visited[facet]) { + int facet = facets_to_check[facet_idx]; + const Vec3f &facet_normal = m_face_normals[m_triangles[facet].source_triangle]; + if (!visited[facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) { if (select_triangle(facet, new_state, triangle_splitting)) { - // add neighboring facets to list to be proccessed later - for (int neighbor_idx : m_neighbors[facet]) { - if (neighbor_idx >=0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) + // add neighboring facets to list to be processed later + for (int neighbor_idx : m_neighbors[facet]) + if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) facets_to_check.push_back(neighbor_idx); - } } } visited[facet] = true; @@ -168,7 +172,10 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, } } -void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle, bool force_reselection) +void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, + const Transform3d& trafo_no_translate, + float seed_fill_angle, float highlight_by_angle_deg, + bool force_reselection) { assert(facet_start < m_orig_size_indices); @@ -182,14 +189,17 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st std::queue facet_queue; facet_queue.push(facet_start); - const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); + Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. while (!facet_queue.empty()) { int current_facet = facet_queue.front(); facet_queue.pop(); - if (!visited[current_facet]) { + const Vec3f &facet_normal = m_face_normals[m_triangles[current_facet].source_triangle]; + if (!visited[current_facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) { if (m_triangles[current_facet].is_split()) { for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) { assert(split_triangle_idx < int(m_triangles[current_facet].children.size())); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 05a78e2ee..b3c468a6e 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -45,12 +45,16 @@ public: CursorType type, // current type of cursor EnforcerBlockerType new_state, // enforcer or blocker? const Transform3d &trafo, // matrix to get from mesh to world - bool triangle_splitting); // If triangles will be split base on the cursor or not + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + bool triangle_splitting, // If triangles will be split base on the cursor or not + float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - void seed_fill_select_triangles(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - float seed_fill_angle, // the maximal angle between two facets to be painted by the same color - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void seed_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + float seed_fill_angle, // the maximal angle between two facets to be painted by the same color + float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle void bucket_fill_select_triangles(const Vec3f &hit, // point where to start int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to diff --git a/src/miniz/CMakeLists.txt b/src/miniz/CMakeLists.txt index a664f7460..04d562b76 100644 --- a/src/miniz/CMakeLists.txt +++ b/src/miniz/CMakeLists.txt @@ -3,29 +3,15 @@ project(miniz) add_library(miniz INTERFACE) -if(NOT SLIC3R_STATIC OR CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_package(miniz 2.1 QUIET) -endif() - -if(miniz_FOUND) - - message(STATUS "Using system miniz...") - target_link_libraries(miniz INTERFACE miniz::miniz) - -else() - - add_library(miniz_static STATIC - miniz.c - miniz.h - ) - - if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU") - target_compile_definitions(miniz_static PRIVATE _GNU_SOURCE) - endif() - - target_link_libraries(miniz INTERFACE miniz_static) - target_include_directories(miniz INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) - - message(STATUS "Miniz NOT found in system, using bundled version...") +add_library(miniz_static STATIC + miniz.c + miniz.h +) +if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU") + target_compile_definitions(miniz_static PRIVATE _GNU_SOURCE) endif() + +target_link_libraries(miniz INTERFACE miniz_static) +target_include_directories(miniz INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 5b7218c87..18c017da1 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -7,11 +7,10 @@ #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Tesselate.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI_App.hpp" -#include "libslic3r/PresetBundle.hpp" #include "GLCanvas3D.hpp" -#include "3DScene.hpp" #include @@ -154,7 +153,11 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c std::string model; std::string texture; if (force_as_custom) +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + type = EType::Custom; +#else type = Custom; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS else { auto [new_type, system_model, system_texture] = detect_type(shape); type = new_type; @@ -174,7 +177,12 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c model_filename.clear(); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EShapeType shape_type = detect_shape_type(shape); + if (m_shape == shape && m_type == type && m_shape_type == shape_type && m_texture_filename == texture_filename && m_model_filename == model_filename) +#else if (m_shape == shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // No change, no need to update the UI. return false; @@ -182,6 +190,9 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c m_texture_filename = texture_filename; m_model_filename = model_filename; m_type = type; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_shape_type = shape_type; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS calc_bounding_boxes(); @@ -229,6 +240,84 @@ void Bed3D::render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_fact render_internal(canvas, bottom, scale_factor, false, false, true); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool Bed3D::is_rectangle(const Pointfs& shape, Vec2d* min, Vec2d* max) +{ + const Lines lines = Polygon::new_scale(shape).lines(); + bool ret = lines.size() == 4 && lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3]) && lines[0].perpendicular_to(lines[1]); + if (ret) { + if (min != nullptr) { + *min = shape.front(); + for (const Vec2d& pt : shape) { + min->x() = std::min(min->x(), pt.x()); + min->y() = std::min(min->y(), pt.y()); + } + } + if (max != nullptr) { + *max = shape.front(); + for (const Vec2d& pt : shape) { + max->x() = std::max(max->x(), pt.x()); + max->y() = std::max(max->y(), pt.y()); + } + } + } + return ret; +} + +bool Bed3D::is_circle(const Pointfs& shape, Vec2d* center, double* radius) +{ + if (shape.size() < 3) + return false; + + // Analyze the array of points. + // Do they reside on a circle ? + const Vec2d box_center = BoundingBoxf(shape).center(); + std::vector vertex_distances; + double avg_dist = 0.0; + for (const Vec2d& pt : shape) { + double distance = (pt - box_center).norm(); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + double tolerance = avg_dist * 0.01; + + bool defined_value = true; + for (double el : vertex_distances) { + if (fabs(el - avg_dist) > tolerance) + defined_value = false; + break; + } + + if (center != nullptr) + *center = box_center; + + if (radius != nullptr) + *radius = avg_dist; + + return defined_value; +} + +bool Bed3D::is_convex(const Pointfs& shape) +{ + return Polygon::new_scale(shape).convex_points().size() == shape.size(); +} + +Bed3D::EShapeType Bed3D::detect_shape_type(const Pointfs& shape) +{ + if (shape.size() < 3) + return EShapeType::Invalid; + else if (is_rectangle(shape)) + return EShapeType::Rectangle; + else if (is_circle(shape)) + return EShapeType::Circle; + else + return EShapeType::Custom; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture, bool picking) { @@ -244,9 +333,15 @@ void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, switch (m_type) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + case EType::System: { render_system(canvas, bottom, show_texture); break; } + default: + case EType::Custom: { render_custom(canvas, bottom, show_texture, picking); break; } +#else case System: { render_system(canvas, bottom, show_texture); break; } default: case Custom: { render_custom(canvas, bottom, show_texture, picking); break; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } glsafe(::glDisable(GL_DEPTH_TEST)); @@ -320,7 +415,11 @@ std::tuple Bed3D::detect_type(const Poin std::string model_filename = PresetUtils::system_printer_bed_model(*curr); std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr); if (!model_filename.empty() && !texture_filename.empty()) +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return { EType::System, model_filename, texture_filename }; +#else return { System, model_filename, texture_filename }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } } @@ -328,7 +427,11 @@ std::tuple Bed3D::detect_type(const Poin } } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return { EType::Custom, "", "" }; +#else return { Custom, "", "" }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void Bed3D::render_axes() const diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index a2a643519..07b9f1758 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -62,15 +62,36 @@ class Bed3D }; public: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + enum class EType : unsigned char + { + System, + Custom + }; + + enum class EShapeType : unsigned char + { + Rectangle, + Circle, + Custom, + Invalid + }; +#else enum EType : unsigned char { System, Custom, Num_Types }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS private: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EType m_type{ EType::Custom }; + EShapeType m_shape_type{ EShapeType::Invalid }; +#else EType m_type{ Custom }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS Pointfs m_shape; std::string m_texture_filename; std::string m_model_filename; @@ -94,16 +115,18 @@ public: ~Bed3D() { reset(); } EType get_type() const { return m_type; } - +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EShapeType get_shape_type() const { return m_shape_type; } + bool is_custom() const { return m_type == EType::Custom; } +#else bool is_custom() const { return m_type == Custom; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS const Pointfs& get_shape() const { return m_shape; } // Return true if the bed shape changed, so the calee will update the UI. bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); - const BoundingBoxf3& get_bounding_box(bool extended) const { - return extended ? m_extended_bounding_box : m_bounding_box; - } + const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } bool contains(const Point& point) const; Point point_projection(const Point& point) const; @@ -113,6 +136,13 @@ public: void render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + static bool is_rectangle(const Pointfs& shape, Vec2d* min = nullptr, Vec2d* max = nullptr); + static bool is_circle(const Pointfs& shape, Vec2d* center = nullptr, double* radius = nullptr); + static bool is_convex(const Pointfs& shape); + static EShapeType detect_shape_type(const Pointfs& shape); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + private: void calc_bounding_boxes() const; void calc_triangles(const ExPolygon& poly); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index fd2e529d3..26a64fdb7 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -10,6 +10,10 @@ #include "GLShader.hpp" #include "GUI_App.hpp" #include "Plater.hpp" +#include "BitmapCache.hpp" +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include "3DBed.hpp" +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" @@ -17,7 +21,6 @@ #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Slicing.hpp" -#include "slic3r/GUI/BitmapCache.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/AppConfig.hpp" @@ -37,6 +40,12 @@ #include +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include +#include +#include +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + #ifdef HAS_GLSAFE void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name) { @@ -261,6 +270,12 @@ void GLIndexedVertexArray::render( const std::pair& tverts_range, const std::pair& qverts_range) const { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // this method has been called before calling finalize() ? + if (this->vertices_and_normals_interleaved_VBO_id == 0 && !this->vertices_and_normals_interleaved.empty()) + return; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + assert(this->vertices_and_normals_interleaved_VBO_id != 0); assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); @@ -319,18 +334,7 @@ void GLVolume::SinkingContours::update() MeshSlicingParams slicing_params; slicing_params.trafo = m_parent.world_matrix(); Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); - for (Polygon& polygon : polygons) { - if (polygon.is_clockwise()) - polygon.reverse(); - Polygons outer_polys = offset(polygon, float(scale_(HalfWidth))); - if (outer_polys.empty()) - // no outer contour, skip - continue; - - ExPolygon expoly(std::move(outer_polys.front())); - expoly.holes = offset(polygon, -float(scale_(HalfWidth))); - polygons_reverse(expoly.holes); - + for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { GUI::GLModel::InitializationData::Entity entity; entity.type = GUI::GLModel::PrimitiveType::Triangles; const std::vector triangulation = triangulate_expolygon_3d(expoly); @@ -463,9 +467,15 @@ std::array color_from_model_volume(const ModelVolume& model_volume) color[2] = 0.2f; } else if (model_volume.is_modifier()) { +#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 0.2f; +#else color[0] = 0.2f; color[1] = 1.0f; color[2] = 0.2f; +#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT } else if (model_volume.is_support_blocker()) { color[0] = 1.0f; @@ -522,6 +532,23 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d & bounding_box().transformed(trafo); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +BoundingBoxf3 GLVolume::transformed_non_sinking_bounding_box(const Transform3d& trafo) const +{ + return GUI::wxGetApp().plater()->model().objects[object_idx()]->volumes[volume_idx()]->mesh().transformed_bounding_box(trafo, 0.0); +} + +const BoundingBoxf3& GLVolume::transformed_non_sinking_bounding_box() const +{ + if (!m_transformed_non_sinking_bounding_box.has_value()) { + std::optional* trans_box = const_cast*>(&m_transformed_non_sinking_bounding_box); + const Transform3d& trafo = world_matrix(); + *trans_box = transformed_non_sinking_bounding_box(trafo); + } + return *m_transformed_non_sinking_bounding_box; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; @@ -596,6 +623,106 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +void GLVolume::calc_convex_hull_3d() +{ + if (this->indexed_vertex_array.vertices_and_normals_interleaved.empty()) + return; + + TriangleMesh mesh; + for (size_t i = 0; i < this->indexed_vertex_array.vertices_and_normals_interleaved.size(); i += 6) { + const size_t v_id = 3 + i; + mesh.its.vertices.push_back({ this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 0], + this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 1], + this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 2] + }); + } + + const std::vector& vertices = mesh.its.vertices; + + // The qhull call: + orgQhull::Qhull qhull; + qhull.disableOutputStream(); // we want qhull to be quiet + std::vector src_vertices; + try + { +#if REALfloat + qhull.runQhull("", 3, (int)vertices.size(), (const realT*)(vertices.front().data()), "Qt"); +#else + src_vertices.reserve(vertices.size() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex& v : vertices) + for (int i = 0; i < 3; ++i) + src_vertices.emplace_back(v(i)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); +#endif + } + catch (...) + { + std::cout << "GLVolume::calc_convex_hull_3d() - Unable to create convex hull" << std::endl; + return ; + } + + // Let's collect results: + std::vector dst_vertices; + std::vector dst_facets; + // Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices). + std::vector map_dst_vertices; +#ifndef NDEBUG + Vec3f centroid = Vec3f::Zero(); + for (const auto& pt : vertices) + centroid += pt; + centroid /= float(vertices.size()); +#endif // NDEBUG + for (const orgQhull::QhullFacet& facet : qhull.facetList()) { + // Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID. + Vec3i indices; + int cnt = 0; + for (const orgQhull::QhullVertex vertex : facet.vertices()) { + const int id = vertex.id(); + assert(id >= 0); + if (id >= int(map_dst_vertices.size())) + map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1); + if (int i = map_dst_vertices[id]; i == -1) { + // Allocate a new vertex. + i = int(dst_vertices.size()); + map_dst_vertices[id] = i; + orgQhull::QhullPoint pt(vertex.point()); + dst_vertices.emplace_back(pt[0], pt[1], pt[2]); + indices[cnt] = i; + } + else + // Reuse existing vertex. + indices[cnt] = i; + + if (cnt++ == 3) + break; + } + assert(cnt == 3); + if (cnt == 3) { + // QHull sorts vertices of a face lexicographically by their IDs, not by face normals. + // Calculate face normal based on the order of vertices. + const Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]); + auto* n2 = facet.getBaseT()->normal; + const auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2]; +#ifndef NDEBUG + const Vec3f n3 = (dst_vertices[indices(0)] - centroid); + const auto d3 = n.dot(n3); + assert((d < 0.f) == (d3 < 0.f)); +#endif // NDEBUG + // Get the face normal from QHull. + if (d < 0.f) + // Fix face orientation. + std::swap(indices[1], indices[2]); + dst_facets.emplace_back(indices); + } + } + + TriangleMesh out_mesh{ std::move(dst_vertices), std::move(dst_facets) }; + this->set_convex_hull(out_mesh); +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -753,7 +880,10 @@ int GLVolumeCollection::load_wipe_tower_preview( volumes.emplace_back(new GLVolume(color)); GLVolume& v = *volumes.back(); - v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.load_mesh(mesh); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + v.set_convex_hull(mesh.convex_hull_3d()); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); @@ -835,7 +965,15 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_CULL_FACE)); for (GLVolumeWithIdAndZ& volume : to_render) { +#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT + if (type == ERenderType::Transparent) + volume.first->force_transparent = true; +#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT volume.first->set_render_color(); +#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT + if (type == ERenderType::Transparent) + volume.first->force_transparent = false; +#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT // render sinking contours of non-hovered volumes if (m_show_sinking_contours) @@ -852,10 +990,17 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("uniform_color", volume.first->render_color); shader->set_uniform("z_range", m_z_range, 2); shader->set_uniform("clipping_plane", m_clipping_plane, 4); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + shader->set_uniform("print_volume.type", static_cast(m_print_volume.type)); + shader->set_uniform("print_volume.xy_data", m_print_volume.data); + shader->set_uniform("print_volume.z_data", m_print_volume.zs); + shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); +#else shader->set_uniform("print_box.min", m_print_box_min, 3); shader->set_uniform("print_box.max", m_print_box_max, 3); shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled); shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); shader->set_uniform("slope.volume_world_normal_matrix", static_cast(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast())); shader->set_uniform("slope.normal_z", m_slope.normal_z); @@ -904,7 +1049,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_BLEND)); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state, bool as_toolpaths) const +#else bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS { if (config == nullptr) return false; @@ -913,22 +1062,95 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (opt == nullptr) return false; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); + const float bed_height = config->opt_float("max_print_height"); + const BoundingBox bed_box_2D = get_extents(bed_poly); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), -1e10 }, + { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), bed_height }); + + auto check_against_rectangular_bed = [&print_volume](GLVolume& volume, ModelInstanceEPrintVolumeState& state) { + const BoundingBoxf3* const bb = volume.is_sinking() ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box(); + volume.is_outside = !print_volume.contains(*bb); + if (volume.printable) { + if (state == ModelInstancePVS_Inside && volume.is_outside) + state = ModelInstancePVS_Fully_Outside; + if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && print_volume.intersects(*bb)) + state = ModelInstancePVS_Partly_Outside; + } + }; + + auto check_against_circular_bed = [](GLVolume& volume, ModelInstanceEPrintVolumeState& state, const Vec2d& center, double radius) { + const TriangleMesh* mesh = volume.is_sinking() ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull(); + const Polygon volume_hull_2d = its_convex_hull_2d_above(mesh->its, volume.world_matrix().cast(), 0.0f); + size_t outside_count = 0; + const double sq_radius = sqr(radius); + for (const Point& p : volume_hull_2d.points) { + if (sq_radius < (unscale(p) - center).squaredNorm()) + ++outside_count; + } + + volume.is_outside = outside_count > 0; + if (volume.printable) { + if (state == ModelInstancePVS_Inside && volume.is_outside) + state = ModelInstancePVS_Fully_Outside; + if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && outside_count < volume_hull_2d.size()) + state = ModelInstancePVS_Partly_Outside; + } + }; + + auto check_against_convex_bed = [&bed_poly, bed_height](GLVolume& volume, ModelInstanceEPrintVolumeState& state) { + const TriangleMesh* mesh = volume.is_sinking() ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull(); + const Polygon volume_hull_2d = its_convex_hull_2d_above(mesh->its, volume.world_matrix().cast(), 0.0f); + const BoundingBoxf3* const bb = volume.is_sinking() ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box(); + ModelInstanceEPrintVolumeState volume_state = printbed_collision_state(bed_poly, bed_height, volume_hull_2d, bb->min.z(), bb->max.z()); + bool contained = (volume_state == ModelInstancePVS_Inside); + bool intersects = (volume_state == ModelInstancePVS_Partly_Outside); + + volume.is_outside = !contained; + if (volume.printable) { + if (state == ModelInstancePVS_Inside && volume.is_outside) + state = ModelInstancePVS_Fully_Outside; + + if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && intersects) + state = ModelInstancePVS_Partly_Outside; + } + }; +#else const BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), 0.0 }, - { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), - config->opt_float("max_print_height") }); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), 0.0 }, + { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), config->opt_float("max_print_height") }); // Allow the objects to protrude below the print bed - print_volume.min(2) = -1e10; - print_volume.min(0) -= BedEpsilon; - print_volume.min(1) -= BedEpsilon; - print_volume.max(0) += BedEpsilon; - print_volume.max(1) += BedEpsilon; - - ModelInstanceEPrintVolumeState state = ModelInstancePVS_Inside; + print_volume.min.z() = -1e10; + print_volume.min.x() -= BedEpsilon; + print_volume.min.y() -= BedEpsilon; + print_volume.max.x() += BedEpsilon; + print_volume.max.y() += BedEpsilon; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside; bool contained_min_one = false; for (GLVolume* volume : this->volumes) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (as_toolpaths && !volume->is_extrusion_path) + continue; + else if (!as_toolpaths && (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0)))) + continue; + + if (GUI::Bed3D::is_rectangle(opt->values)) + check_against_rectangular_bed(*volume, overall_state); + else { + Vec2d center; + double radius; + if (GUI::Bed3D::is_circle(opt->values, ¢er, &radius)) + check_against_circular_bed(*volume, overall_state, center, radius); + else if (GUI::Bed3D::is_convex(opt->values)) + check_against_convex_bed(*volume, overall_state); + } + + contained_min_one |= !volume->is_outside; +#else if (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0))) continue; @@ -941,15 +1163,16 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M contained_min_one |= contained; - if (state == ModelInstancePVS_Inside && volume->is_outside) - state = ModelInstancePVS_Fully_Outside; + if (overall_state == ModelInstancePVS_Inside && volume->is_outside) + overall_state = ModelInstancePVS_Fully_Outside; - if (state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb)) - state = ModelInstancePVS_Partly_Outside; + if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb)) + overall_state = ModelInstancePVS_Partly_Outside; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } if (out_state != nullptr) - *out_state = state; + *out_state = overall_state; return contained_min_one; } @@ -1005,35 +1228,28 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con std::vector colors(colors_count); unsigned char rgb[3]; - for (unsigned int i = 0; i < colors_count; ++i) - { + for (unsigned int i = 0; i < colors_count; ++i) { const std::string& txt_color = config->opt_string("extruder_colour", i); if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) - { colors[i].set(txt_color, rgb); - } - else - { + else { const std::string& txt_color = config->opt_string("filament_colour", i); if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) colors[i].set(txt_color, rgb); } } - for (GLVolume* volume : volumes) - { - if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) + for (GLVolume* volume : volumes) { + if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) continue; int extruder_id = volume->extruder_id - 1; - if ((extruder_id < 0) || ((int)colors.size() <= extruder_id)) + if (extruder_id < 0 || (int)colors.size() <= extruder_id) extruder_id = 0; const Color& color = colors[extruder_id]; - if (!color.text.empty()) - { - for (int i = 0; i < 3; ++i) - { + if (!color.text.empty()) { + for (int i = 0; i < 3; ++i) { volume->color[i] = (float)color.rgb[i] * inv_255; } } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 78b9a96d9..14597f22a 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -41,7 +41,6 @@ enum ModelInstanceEPrintVolumeState : unsigned char; // Return appropriate color based on the ModelVolume. std::array color_from_model_volume(const ModelVolume& model_volume); - // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. class GLIndexedVertexArray { @@ -279,6 +278,10 @@ private: std::shared_ptr m_convex_hull; // Bounding box of this volume, in unscaled coordinates. std::optional m_transformed_convex_hull_bounding_box; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // Bounding box of the non sinking part of this volume, in unscaled coordinates. + std::optional m_transformed_non_sinking_bounding_box; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS class SinkingContours { @@ -469,6 +472,12 @@ public: BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; // caching variant const BoundingBoxf3& transformed_convex_hull_bounding_box() const; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // non-caching variant + BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const; + // caching variant + const BoundingBoxf3& transformed_non_sinking_bounding_box() const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // convex hull const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } @@ -481,7 +490,15 @@ public: void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void set_bounding_boxes_as_dirty() { + m_transformed_bounding_box.reset(); + m_transformed_convex_hull_bounding_box.reset(); + m_transformed_non_sinking_bounding_box.reset(); + } +#else void set_bounding_boxes_as_dirty() { m_transformed_bounding_box.reset(); m_transformed_convex_hull_bounding_box.reset(); } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS bool is_sla_support() const; bool is_sla_pad() const; @@ -498,6 +515,12 @@ public: // Return an estimate of the memory held by GPU vertex buffers. size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // calculates the 3D convex hull from indexed_vertex_array.vertices_and_normals_interleaved + // must be called before calling indexed_vertex_array.finalize_geometry(); + void calc_convex_hull_3d(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS }; typedef std::vector GLVolumePtrs; @@ -514,10 +537,30 @@ public: All }; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + struct PrintVolume + { + // see: Bed3D::EShapeType + int type{ 0 }; + // data contains: + // Rectangle: + // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y + // Circle: + // [0] = center.x, [1] = center.y, [3] = radius + std::array data; + // [0] = min z, [1] = max z + std::array zs; + }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + private: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + PrintVolume m_print_volume; +#else // min and max vertex of the print box volume float m_print_box_min[3]; float m_print_box_max[3]; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // z range for clipping in shaders float m_z_range[2]; @@ -589,10 +632,14 @@ public: bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } +#else void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) { m_print_box_min[0] = min_x; m_print_box_min[1] = min_y; m_print_box_min[2] = min_z; m_print_box_max[0] = max_x; m_print_box_max[1] = max_y; m_print_box_max[2] = max_z; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; } @@ -607,7 +654,11 @@ public: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state, bool as_toolpaths = false) const; +#else bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index a6b99a08b..05f301186 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -220,7 +220,7 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo - m_logo_bitmap = ScalableBitmap(this, wxGetApp().is_editor() ? "PrusaSlicer_192px.png" : "PrusaSlicer-gcodeviewer_192px.png", 192); + m_logo_bitmap = ScalableBitmap(this, wxGetApp().logo_name(), 192); m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 40d9ee3b2..0b8e31e13 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -22,9 +22,25 @@ namespace GUI { BedShape::BedShape(const ConfigOptionPoints& points) { - auto polygon = Polygon::new_scale(points.values); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (points.size() < 3) { + m_type = Bed3D::EShapeType::Invalid; + return; + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // is this a rectangle ? +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Vec2d min; + Vec2d max; + if (Bed3D::is_rectangle(points.values, &min, &max)) { + m_type = Bed3D::EShapeType::Rectangle; + m_rectSize = max - min; + m_rectOrigin = -min; + return; + } +#else + Polygon polygon = Polygon::new_scale(points.values); if (points.size() == 4) { auto lines = polygon.lines(); if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { @@ -48,8 +64,21 @@ BedShape::BedShape(const ConfigOptionPoints& points) return; } } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // is this a circle ? +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Vec2d center; + double radius; + if (Bed3D::is_circle(points.values, ¢er, &radius)) { + m_type = Bed3D::EShapeType::Circle; + m_diameter = 2.0 * radius; + return; + } + + // This is a custom bed shape, use the polygon provided. + m_type = Bed3D::EShapeType::Custom; +#else { // Analyze the array of points.Do they reside on a circle ? auto center = polygon.bounding_box().center(); @@ -79,11 +108,12 @@ BedShape::BedShape(const ConfigOptionPoints& points) } } - if (points.size() < 3) + if (points.size() < 3) return; // This is a custom bed shape, use the polygon provided. m_type = Type::Custom; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } static std::string get_option_label(BedShape::Parameter param) @@ -134,31 +164,56 @@ void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter para } } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +wxString BedShape::get_name(Bed3D::EShapeType type) +{ + switch (type) { + case Bed3D::EShapeType::Rectangle: { return _L("Rectangular"); } + case Bed3D::EShapeType::Circle: { return _L("Circular"); } + case Bed3D::EShapeType::Custom: { return _L("Custom"); } + case Bed3D::EShapeType::Invalid: + default: return _L("Invalid"); + } +} +#else wxString BedShape::get_name(Type type) { switch (type) { - case Type::Rectangular : return _L("Rectangular"); - case Type::Circular : return _L("Circular"); - case Type::Custom : return _L("Custom"); - case Type::Invalid : - default : return _L("Invalid"); + case Type::Rectangular: return _L("Rectangular"); + case Type::Circular: return _L("Circular"); + case Type::Custom: return _L("Custom"); + case Type::Invalid: + default: return _L("Invalid"); } } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS size_t BedShape::get_type() { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return static_cast(m_type == Bed3D::EShapeType::Invalid ? Bed3D::EShapeType::Rectangle : m_type); +#else return static_cast(m_type == Type::Invalid ? Type::Rectangular : m_type); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } wxString BedShape::get_full_name_with_params() { wxString out = _L("Shape") + ": " + get_name(m_type); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_type == Bed3D::EShapeType::Rectangle) { +#else if (m_type == Type::Rectangular) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(m_rectSize).serialize() + "]"; out += "\n" + _(get_option_label(Parameter::RectOrigin))+ ": [" + ConfigOptionPoint(m_rectOrigin).serialize() + "]"; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (m_type == Bed3D::EShapeType::Circle) +#else else if (m_type == Type::Circular) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(m_diameter) + "]"; return out; @@ -166,11 +221,19 @@ wxString BedShape::get_full_name_with_params() void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_type == Bed3D::EShapeType::Rectangle || m_type == Bed3D::EShapeType::Invalid) { +#else if (m_type == Type::Rectangular || m_type == Type::Invalid) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS optgroup->set_value("rect_size" , new ConfigOptionPoints{ m_rectSize }); optgroup->set_value("rect_origin" , new ConfigOptionPoints{ m_rectOrigin }); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (m_type == Bed3D::EShapeType::Circle) +#else else if (m_type == Type::Circular) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS optgroup->set_value("diameter", double_to_string(m_diameter)); } @@ -222,7 +285,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value; m_custom_model = custom_model.value.empty() ? NONE : custom_model.value; - auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); + auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _L("Shape")); sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); wxGetApp().UpdateDarkUI(sbsizer->GetStaticBox()); @@ -232,16 +295,28 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf sbsizer->Add(m_shape_options_book); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + auto optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Rectangle)); +#else auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); activate_options_page(optgroup); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Circle)); +#else optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); activate_options_page(optgroup); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Custom)); +#else optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS Line line{ "", "" }; line.full_width = 1; @@ -265,10 +340,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf wxPanel* texture_panel = init_texture_panel(); wxPanel* model_panel = init_model_panel(); - Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) - { - update_shape(); - })); + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { update_shape(); })); // right pane with preview canvas m_canvas = new Bed_2D(this); @@ -295,7 +367,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { wxPanel* panel = new wxPanel(m_shape_options_book); - ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Settings"))); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Settings")); optgroup->label_width = 10; optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -319,7 +391,7 @@ wxPanel* BedShapePanel::init_texture_panel() { wxPanel* panel = new wxPanel(this); wxGetApp().UpdateDarkUI(panel, true); - ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Texture"))); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Texture")); optgroup->label_width = 10; optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -329,7 +401,7 @@ wxPanel* BedShapePanel::init_texture_panel() Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxButton* load_btn = new wxButton(parent, wxID_ANY, _L("Load...")); wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); load_sizer->Add(load_btn, 1, wxEXPAND); @@ -338,7 +410,7 @@ wxPanel* BedShapePanel::init_texture_panel() wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); filename_sizer->Add(filename_lbl, 1, wxEXPAND); - wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _L("Remove")); wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); remove_sizer->Add(remove_btn, 1, wxEXPAND); @@ -347,31 +419,23 @@ wxPanel* BedShapePanel::init_texture_panel() sizer->Add(load_sizer, 1, wxEXPAND); sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2); - load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { - load_texture(); - })); - - remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_texture(); })); + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { m_custom_texture = NONE; update_shape(); })); - filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string())); wxStaticText* lbl = dynamic_cast(e.GetEventObject()); - if (lbl != nullptr) - { + if (lbl != nullptr) { bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture); lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED)); wxString tooltip_text = ""; - if (m_custom_texture != NONE) - { + if (m_custom_texture != NONE) { if (!exists) - tooltip_text += _(L("Not found:")) + " "; + tooltip_text += _L("Not found:") + " "; tooltip_text += _(m_custom_texture); } @@ -382,10 +446,7 @@ wxPanel* BedShapePanel::init_texture_panel() } })); - remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { - e.Enable(m_custom_texture != NONE); - })); + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.Enable(m_custom_texture != NONE); })); return sizer; }; @@ -401,7 +462,7 @@ wxPanel* BedShapePanel::init_model_panel() { wxPanel* panel = new wxPanel(this); wxGetApp().UpdateDarkUI(panel, true); - ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Model"))); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Model")); optgroup->label_width = 10; optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -411,7 +472,7 @@ wxPanel* BedShapePanel::init_model_panel() Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxButton* load_btn = new wxButton(parent, wxID_ANY, _L("Load...")); wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); load_sizer->Add(load_btn, 1, wxEXPAND); @@ -419,7 +480,7 @@ wxPanel* BedShapePanel::init_model_panel() wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); filename_sizer->Add(filename_lbl, 1, wxEXPAND); - wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _L("Remove")); wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); remove_sizer->Add(remove_btn, 1, wxEXPAND); @@ -428,31 +489,24 @@ wxPanel* BedShapePanel::init_model_panel() sizer->Add(load_sizer, 1, wxEXPAND); sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2); - load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { - load_model(); - })); + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_model(); })); - remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { m_custom_model = NONE; update_shape(); })); - filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.SetText(_(boost::filesystem::path(m_custom_model).filename().string())); wxStaticText* lbl = dynamic_cast(e.GetEventObject()); - if (lbl != nullptr) - { + if (lbl != nullptr) { bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model); lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED)); wxString tooltip_text = ""; - if (m_custom_model != NONE) - { + if (m_custom_model != NONE) { if (!exists) - tooltip_text += _(L("Not found:")) + " "; + tooltip_text += _L("Not found:") + " "; tooltip_text += _(m_custom_model); } @@ -463,10 +517,7 @@ wxPanel* BedShapePanel::init_model_panel() } })); - remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { - e.Enable(m_custom_model != NONE); - })); + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.Enable(m_custom_model != NONE); })); return sizer; }; @@ -511,10 +562,18 @@ void BedShapePanel::update_shape() auto page_idx = m_shape_options_book->GetSelection(); auto opt_group = m_optgroups[page_idx]; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Bed3D::EShapeType page_type = static_cast(page_idx); +#else BedShape::Type page_type = static_cast(page_idx); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (page_type == BedShape::Type::Rectangular) { - Vec2d rect_size(Vec2d::Zero()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (page_type == Bed3D::EShapeType::Rectangle) { +#else + if (page_type == BedShape::Type::Rectangular) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Vec2d rect_size(Vec2d::Zero()); Vec2d rect_origin(Vec2d::Zero()); try { rect_size = boost::any_cast(opt_group->get_value("rect_size")); } @@ -544,8 +603,12 @@ void BedShapePanel::update_shape() Vec2d(x1, y1), Vec2d(x0, y1) }; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (page_type == Bed3D::EShapeType::Circle) { +#else else if (page_type == BedShape::Type::Circular) { - double diameter; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + double diameter; try { diameter = boost::any_cast(opt_group->get_value("diameter")); } catch (const std::exception & /* e */) { return; } @@ -560,7 +623,11 @@ void BedShapePanel::update_shape() } m_shape = points; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (page_type == Bed3D::EShapeType::Custom) +#else else if (page_type == BedShape::Type::Custom) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_shape = m_loaded_shape; update_preview(); @@ -569,14 +636,13 @@ void BedShapePanel::update_shape() // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + wxFileDialog dialog(this, _L("Choose an STL file to import bed shape from:"), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) return; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(file_name, ".stl")) - { - show_error(this, _(L("Invalid file format."))); + if (!boost::algorithm::iends_with(file_name, ".stl")) { + show_error(this, _L("Invalid file format.")); return; } @@ -587,7 +653,7 @@ void BedShapePanel::load_stl() model = Model::read_from_file(file_name); } catch (std::exception &) { - show_error(this, _(L("Error! Invalid model"))); + show_error(this, _L("Error! Invalid model")); return; } @@ -595,11 +661,11 @@ void BedShapePanel::load_stl() auto expolygons = mesh.horizontal_projection(); if (expolygons.size() == 0) { - show_error(this, _(L("The selected file contains no geometry."))); + show_error(this, _L("The selected file contains no geometry.")); return; } if (expolygons.size() > 1) { - show_error(this, _(L("The selected file contains several disjoint areas. This is not supported."))); + show_error(this, _L("The selected file contains several disjoint areas. This is not supported.")); return; } @@ -614,7 +680,7 @@ void BedShapePanel::load_stl() void BedShapePanel::load_texture() { - wxFileDialog dialog(this, _(L("Choose a file to import bed texture from (PNG/SVG):")), "", "", + wxFileDialog dialog(this, _L("Choose a file to import bed texture from (PNG/SVG):"), "", "", file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) @@ -623,9 +689,8 @@ void BedShapePanel::load_texture() m_custom_texture = NONE; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) - { - show_error(this, _(L("Invalid file format."))); + if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) { + show_error(this, _L("Invalid file format.")); return; } @@ -637,7 +702,7 @@ void BedShapePanel::load_texture() void BedShapePanel::load_model() { - wxFileDialog dialog(this, _(L("Choose an STL file to import bed model from:")), "", "", + wxFileDialog dialog(this, _L("Choose an STL file to import bed model from:"), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) @@ -646,9 +711,8 @@ void BedShapePanel::load_model() m_custom_model = NONE; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(file_name, ".stl")) - { - show_error(this, _(L("Invalid file format."))); + if (!boost::algorithm::iends_with(file_name, ".stl")) { + show_error(this, _L("Invalid file format.")); return; } diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 370129f2e..af84ffb95 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -5,6 +5,9 @@ #include "GUI_Utils.hpp" #include "2DBed.hpp" +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include "3DBed.hpp" +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #include "I18N.hpp" #include @@ -19,12 +22,14 @@ using ConfigOptionsGroupShp = std::shared_ptr; struct BedShape { +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS enum class Type { Rectangular = 0, Circular, Custom, Invalid }; +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS enum class Parameter { RectSize, @@ -34,10 +39,18 @@ struct BedShape BedShape(const ConfigOptionPoints& points); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool is_custom() { return m_type == Bed3D::EShapeType::Custom; } +#else bool is_custom() { return m_type == Type::Custom; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + static wxString get_name(Bed3D::EShapeType type); +#else static wxString get_name(Type type); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // convert Type to size_t size_t get_type(); @@ -46,7 +59,11 @@ struct BedShape void apply_optgroup_values(ConfigOptionsGroupShp optgroup); private: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Bed3D::EShapeType m_type{ Bed3D::EShapeType::Invalid }; +#else Type m_type {Type::Invalid}; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS Vec2d m_rectSize {200, 200}; Vec2d m_rectOrigin {0, 0}; double m_diameter {0}; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 77679a1ad..d0b29165c 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -38,6 +38,14 @@ using GUI::format_wxstr; namespace DoubleSlider { +constexpr double min_delta_area = scale_(scale_(25)); // equal to 25 mm2 +constexpr double miscalculation = scale_(scale_(1)); // equal to 1 mm2 + +bool equivalent_areas(const double& bottom_area, const double& top_area) +{ + return fabs(bottom_area - top_area) <= miscalculation; +} + wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); static std::string gcode(Type type) @@ -2034,6 +2042,32 @@ void Control::show_cog_icon_context_menu() GUI::wxGetApp().plater()->PopupMenu(&menu); } +bool check_color_change(PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, std::function break_condition) +{ + double prev_area = area(object->get_layer(frst_layer_id)->lslices); + + bool detected = false; + for (size_t i = frst_layer_id+1; i < layers_cnt; i++) { + Layer* layer = object->get_layer(i); + double cur_area = area(layer->lslices); + + // check for overhangs + if (check_overhangs && cur_area > prev_area && !equivalent_areas(prev_area, cur_area)) + break; + + // Check percent of the area decrease. + // This value have to be more than min_delta_area and more then 10% + if ((prev_area - cur_area > min_delta_area) && (cur_area / prev_area < 0.9)) { + detected = true; + if (break_condition(layer)) + break; + } + + prev_area = cur_area; + } + return detected; +} + void Control::auto_color_change() { if (!m_ticks.empty()) { @@ -2049,45 +2083,33 @@ void Control::auto_color_change() int extruder = 2; const Print& print = GUI::wxGetApp().plater()->fff_print(); - double delta_area = scale_(scale_(25)); // equal to 25 mm2 - for (auto object : print.objects()) { if (object->layer_count() == 0) continue; - double prev_area = area(object->get_layer(0)->lslices); - for (size_t i = 1; i < object->layers().size(); i++) { - Layer* layer = object->get_layer(i); - double cur_area = area(layer->lslices); - - if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) - break; - - if (prev_area - cur_area > delta_area) { - // Check percent of the area decrease. - // Ignore it, if this value is less than 10% - if (cur_area / prev_area > 0.9) - continue; - int tick = get_tick_from_value(layer->print_z); - if (tick >= 0 && !m_ticks.has_tick(tick)) { - if (m_mode == SingleExtruder) { - m_ticks.set_default_colors(true); - m_ticks.add_tick(tick, ColorChange, 1, layer->print_z); - } - else { - m_ticks.add_tick(tick, ToolChange, extruder, layer->print_z); - if (++extruder > extruders_cnt) + check_color_change(object, 1, object->layers().size(), false, [this, extruders_cnt](Layer* layer) + { + int tick = get_tick_from_value(layer->print_z); + if (tick >= 0 && !m_ticks.has_tick(tick)) { + if (m_mode == SingleExtruder) { + m_ticks.set_default_colors(true); + m_ticks.add_tick(tick, ColorChange, 1, layer->print_z); + } + else { + int extruder = 2; + if (!m_ticks.empty()) { + auto it = m_ticks.ticks.end(); + it--; + extruder = it->extruder + 1; + if (extruder > extruders_cnt) extruder = 1; } + m_ticks.add_tick(tick, ToolChange, extruder, layer->print_z); } - - // allow max 3 auto color changes - if (m_ticks.ticks.size() == 3) - break; } - - prev_area = cur_area; - } + // allow max 3 auto color changes + return m_ticks.ticks.size() > 2; + }); } if (m_ticks.empty()) diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 0f663f663..8f88de472 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -17,6 +17,8 @@ class wxMenu; namespace Slic3r { using namespace CustomGCode; +class PrintObject; +class Layer; namespace DoubleSlider { @@ -25,6 +27,15 @@ namespace DoubleSlider { */ constexpr double epsilon() { return 0.0011; } +// return true when areas are mostly equivalent +bool equivalent_areas(const double& bottom_area, const double& top_area); + +// return true if color change was detected +bool check_color_change(PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, + // what to do with detected color change + // and return true when detection have to be desturbed + std::function break_condition); + // custom message the slider sends to its parent to notify a tick-change: wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index fd2fe9cbc..00fa066c9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -6,10 +6,11 @@ #include "libslic3r/Model.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/PresetBundle.hpp" + #include "GUI_App.hpp" #include "MainFrame.hpp" #include "Plater.hpp" -#include "libslic3r/PresetBundle.hpp" #include "Camera.hpp" #include "I18N.hpp" #include "GUI_Utils.hpp" @@ -19,6 +20,10 @@ #include "GLToolbar.hpp" #include "GUI_Preview.hpp" #include "GUI_ObjectManipulation.hpp" +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include "3DBed.hpp" +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + #include #include @@ -674,6 +679,10 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& if (wxGetApp().is_gcode_viewer()) m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_max_print_height = gcode_result.max_print_height; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + load_toolpaths(gcode_result); if (m_layers.empty()) @@ -819,6 +828,9 @@ void GCodeViewer::reset() m_paths_bounding_box = BoundingBoxf3(); m_max_bounding_box = BoundingBoxf3(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_max_print_height = 0.0f; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_tool_colors = std::vector(); m_extruders_count = 0; m_extruder_ids = std::vector(); @@ -835,6 +847,9 @@ void GCodeViewer::reset() #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); #endif // ENABLE_GCODE_VIEWER_STATISTICS +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_contained_in_bed = true; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GCodeViewer::render() @@ -1554,7 +1569,49 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // set approximate max bounding box (take in account also the tool marker) m_max_bounding_box = m_paths_bounding_box; - m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); + m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); + +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (wxGetApp().is_editor()) { + const Bed3D::EShapeType bed_type = wxGetApp().plater()->get_bed().get_shape_type(); + if (bed_type == Bed3D::EShapeType::Rectangle) { + BoundingBoxf3 print_volume = wxGetApp().plater()->get_bed().get_bounding_box(false); + print_volume.min.z() = -1e10; + print_volume.max.z() = m_max_print_height; + print_volume.min -= Vec3f(BedEpsilon, BedEpsilon, 0.0f).cast(); + print_volume.max += Vec3f(BedEpsilon, BedEpsilon, 0.0f).cast(); + m_contained_in_bed = print_volume.contains(m_paths_bounding_box); + } + else if (bed_type == Bed3D::EShapeType::Circle) { + Vec2d center; + double radius; + Bed3D::is_circle(wxGetApp().plater()->get_bed().get_shape(), ¢er, &radius); + const double sq_radius = sqr(radius); + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) { + if (sq_radius < (Vec2d(move.position.x(), move.position.y()) - center).squaredNorm()) { + m_contained_in_bed = false; + break; + } + } + } + } + else if (bed_type == Bed3D::EShapeType::Custom) { + const Pointfs& shape = wxGetApp().plater()->get_bed().get_shape(); + if (Bed3D::is_convex(shape)) { + const Polygon poly = Polygon::new_scale(shape); + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) { + if (!poly.contains(Point::new_scale(Vec2d(move.position.x(), move.position.y())))) { + m_contained_in_bed = false; + break; + } + } + } + } + } + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_FIX_SEAMS_SYNCH m_sequential_view.gcode_ids.clear(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 1b5a53f9d..66fcba2bc 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -780,6 +780,9 @@ private: BoundingBoxf3 m_paths_bounding_box; // bounding box of toolpaths + marker tools BoundingBoxf3 m_max_bounding_box; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + float m_max_print_height{ 0.0f }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS std::vector m_tool_colors; Layers m_layers; std::array m_layers_z_range; @@ -804,6 +807,10 @@ private: std::vector m_custom_gcode_per_print_z; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool m_contained_in_bed{ true }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + public: GCodeViewer(); ~GCodeViewer() { reset(); } @@ -832,6 +839,10 @@ public: const SequentialView& get_sequential_view() const { return m_sequential_view; } void update_sequential_view_current(unsigned int first, unsigned int last); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool is_contained_in_bed() const { return m_contained_in_bed; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EViewType get_view_type() const { return m_view_type; } void set_view_type(EViewType type) { if (type == EViewType::Count) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7325c3e03..d6a8cbe04 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1108,10 +1108,18 @@ void GLCanvas3D::reset_volumes() _set_warning_notification(EWarning::ObjectOutside, false); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(bool as_toolpaths) const +#else ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS { ModelInstanceEPrintVolumeState state; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_volumes.check_outside_state(m_config, &state, as_toolpaths); +#else m_volumes.check_outside_state(m_config, &state); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS return state; } @@ -1286,12 +1294,14 @@ bool GLCanvas3D::is_reload_delayed() const void GLCanvas3D::enable_layers_editing(bool enable) { m_layers_editing.set_enabled(enable); +#if !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); for (unsigned int idx : idxs) { GLVolume* v = m_volumes.volumes[idx]; if (v->is_modifier) v->force_transparent = enable; } +#endif // !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT set_as_dirty(); } @@ -1826,8 +1836,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(volume_idx_wipe_tower_old == -1); volume_idx_wipe_tower_old = (int)volume_id; } - if (!m_reload_delayed) - { + if (!m_reload_delayed) { deleted_volumes.emplace_back(volume, volume_id); delete volume; } @@ -2121,8 +2130,10 @@ void GLCanvas3D::load_sla_preview() // Release OpenGL data before generating new data. reset_volumes(); _load_sla_shells(); +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); m_volumes.set_print_box(float(bed_bb.min.x()) - BedEpsilon, float(bed_bb.min.y()) - BedEpsilon, 0.0f, float(bed_bb.max.x()) + BedEpsilon, float(bed_bb.max.y()) + BedEpsilon, (float)m_config->opt_float("max_print_height")); +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS _update_sla_shells_outside_state(); _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); } @@ -3215,6 +3226,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (evt.LeftUp()) + m_selection.stop_dragging(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_layers_editing.state != LayersEditing::Unknown) { m_layers_editing.state = LayersEditing::Unknown; _stop_timer(); @@ -4995,6 +5011,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() _update_volumes_hover_state(); } +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) { // tolerance to avoid false detection at bed edges @@ -5011,6 +5028,7 @@ static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) } return ret; } +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void GLCanvas3D::_render_background() const { @@ -5021,11 +5039,16 @@ void GLCanvas3D::_render_background() const if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); - else { + else +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); +#else + { const BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); use_error_color &= (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) ? !test_volume.contains(paths_volume) : false; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } glsafe(::glPushMatrix()); @@ -5101,13 +5124,55 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) if (m_picking_enabled) { // Update the layer editing selection to the first object selected, update the current object maximum Z. m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (m_config != nullptr) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Bed3D::EShapeType type = wxGetApp().plater()->get_bed().get_shape_type(); + switch (type) + { + case Bed3D::EShapeType::Circle: { + Vec2d center; + double radius; + if (Bed3D::is_circle(wxGetApp().plater()->get_bed().get_shape(), ¢er, &radius)) { + m_volumes.set_print_volume({ static_cast(type), + { float(center.x()), float(center.y()), float(radius) + BedEpsilon, 0.0f }, + { 0.0f, float(m_config->opt_float("max_print_height")) } }); + } + break; + } + case Bed3D::EShapeType::Rectangle: { + const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); + m_volumes.set_print_volume({ static_cast(type), + { float(bed_bb.min.x()) - BedEpsilon, float(bed_bb.min.y()) - BedEpsilon, float(bed_bb.max.x()) + BedEpsilon, float(bed_bb.max.y()) + BedEpsilon }, + { 0.0f, float(m_config->opt_float("max_print_height")) } }); + break; + } + default: + case Bed3D::EShapeType::Custom: { + m_volumes.set_print_volume({ static_cast(type), + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }); + } + } +#else const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); - m_volumes.set_print_box((float)bed_bb.min(0) - BedEpsilon, (float)bed_bb.min(1) - BedEpsilon, 0.0f, (float)bed_bb.max(0) + BedEpsilon, (float)bed_bb.max(1) + BedEpsilon, (float)m_config->opt_float("max_print_height")); + m_volumes.set_print_box((float)bed_bb.min.x() - BedEpsilon, (float)bed_bb.min.y() - BedEpsilon, 0.0f, (float)bed_bb.max.x() + BedEpsilon, (float)bed_bb.max.y() + BedEpsilon, (float)m_config->opt_float("max_print_height")); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_requires_check_outside_state) { + m_volumes.check_outside_state(m_config, nullptr); + m_requires_check_outside_state = false; + } +#else m_volumes.check_outside_state(m_config, nullptr); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (m_use_clipping_planes) m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); @@ -5117,7 +5182,11 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_mod"); +#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (shader != nullptr) { shader->start_using(); @@ -5762,7 +5831,7 @@ void GLCanvas3D::_load_print_toolpaths() total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); } size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); - if ((skirt_height == 0) && print->has_brim()) + if (skirt_height == 0 && print->has_brim()) skirt_height = 1; // Get first skirt_height layers. @@ -5795,6 +5864,9 @@ void GLCanvas3D::_load_print_toolpaths() reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); } } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + volume->calc_convex_hull_3d(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS volume->indexed_vertex_array.finalize_geometry(m_initialized); } @@ -6085,8 +6157,16 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; + v->calc_convex_hull_3d(); + v->indexed_vertex_array.finalize_geometry(m_initialized); + } +#else for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } @@ -6094,7 +6174,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) { const Print *print = this->fff_print(); - if ((print == nullptr) || print->wipe_tower_data().tool_changes.empty()) + if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) return; if (!print->is_step_done(psWipeTower)) @@ -6242,8 +6322,16 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; + v->calc_convex_hull_3d(); + v->indexed_vertex_array.finalize_geometry(m_initialized); + } +#else for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } @@ -6305,18 +6393,26 @@ void GLCanvas3D::_load_sla_shells() void GLCanvas3D::_update_toolpath_volumes_outside_state() { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + check_volumes_outside_state(true); +#else BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); for (GLVolume* volume : m_volumes.volumes) { volume->is_outside = (test_volume.radius() > 0.0 && volume->is_extrusion_path) ? !test_volume.contains(volume->bounding_box()) : false; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GLCanvas3D::_update_sla_shells_outside_state() { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + check_volumes_outside_state(); +#else BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); for (GLVolume* volume : m_volumes.volumes) { volume->is_outside = (test_volume.radius() > 0.0 && volume->shader_outside_printer_detection_enabled) ? !test_volume.contains(volume->transformed_convex_hull_bounding_box()) : false; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) @@ -6327,12 +6423,18 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) show = _is_any_volume_outside(); else { if (wxGetApp().is_editor()) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (current_printer_technology() != ptSLA) + show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); +#else BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) show = !test_volume.contains(paths_volume); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } } + _set_warning_notification(warning, show); } @@ -6377,7 +6479,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) "Resolve the current problem to continue slicing."); error = ErrorType::PLATER_ERROR; break; -} + } auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); switch (error) { @@ -6407,7 +6509,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) bool GLCanvas3D::_is_any_volume_outside() const { for (const GLVolume* volume : m_volumes.volumes) { - if ((volume != nullptr) && volume->is_outside) + if (volume != nullptr && volume->is_outside) return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 86ec5634f..a18f1ec07 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -456,7 +456,7 @@ private: GLGizmosManager m_gizmos; GLToolbar m_main_toolbar; GLToolbar m_undoredo_toolbar; - ClippingPlane m_clipping_planes[2]; + std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; SlaCap m_sla_caps[2]; @@ -475,6 +475,9 @@ private: const DynamicPrintConfig* m_config; Model* m_model; BackgroundSlicingProcess *m_process; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool m_requires_check_outside_state{ false }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS std::array m_old_size{ 0, 0 }; @@ -611,11 +614,18 @@ public: void post_event(wxEvent &&event); void set_as_dirty(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void requires_check_outside_state() { m_requires_check_outside_state = true; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS unsigned int get_volumes_count() const; const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + ModelInstanceEPrintVolumeState check_volumes_outside_state(bool as_toolpaths = false) const; +#else ModelInstanceEPrintVolumeState check_volumes_outside_state() const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_SEAMS_USING_MODELS void init_gcode_viewer() { m_gcode_viewer.init(); } @@ -651,6 +661,9 @@ public: void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); } void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; } + bool get_use_clipping_planes() const { return m_use_clipping_planes; } + const std::array &get_clipping_planes() const { return m_clipping_planes; }; + void set_color_by(const std::string& value); void refresh_camera_scene_box(); diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 5dd478b57..0214652b2 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -61,25 +61,28 @@ std::pair GLShadersManager::init() // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor - // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. - // Because of this, objects had darker colors inside the multi-material gizmo. - // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. - if (platform_flavor() == PlatformFlavor::OSXOnArm) - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // When setting this technology to default rename the following from "gouraud_mod" to "gouraud" + valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" } +#else + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_ENVIRONMENT_MAP - , "ENABLE_ENVIRONMENT_MAP"sv -#endif - }); - else - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } -#if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } -#endif + , { "ENABLE_ENVIRONMENT_MAP"sv } +#endif // ENABLE_ENVIRONMENT_MAP ); // used to render variable layers heights in 3d editor valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); // used to render highlight contour around selected triangles inside the multi-material gizmo valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" }); + // Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader. + // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. + // Because of this, objects had darker colors inside the multi-material gizmo. + // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. + if (platform_flavor() == PlatformFlavor::OSXOnArm) + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv}); + else + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}); return { valid, error }; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e787f97cb..716eefb07 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -193,7 +193,7 @@ public: // load bitmap for logo BitmapCache bmp_cache; int logo_size = lround(width * 0.25); - wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().is_editor() ? "prusa_slicer_logo" : "add_gcode", logo_size, logo_size); + wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); wxCoord margin = int(m_scale * 20); @@ -677,16 +677,18 @@ void GUI_App::post_init() if (this->preset_updater) { this->check_updates(false); CallAfter([this] { - this->config_wizard_startup(); + bool cw_showed = this->config_wizard_startup(); this->preset_updater->slic3r_update_notify(); this->preset_updater->sync(preset_bundle); + if (! cw_showed) { + // The CallAfter is needed as well, without it, GL extensions did not show. + // Also, we only want to show this when the wizard does not, so the new user + // sees something else than "we want something" on the first start. + show_send_system_info_dialog_if_needed(); + } }); } - // 'Send system info' dialog. Again, a CallAfter is needed on mac. - // Without it, GL extensions did not show. - CallAfter([] { show_send_system_info_dialog_if_needed(); }); - #ifdef _WIN32 // Sets window property to mainframe so other instances can indentify it. OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); @@ -866,8 +868,11 @@ bool GUI_App::on_init_inner() wxInitAllImageHandlers(); #ifdef _MSW_DARK_MODE - if (app_config->get("dark_color_mode") == "1") + if (bool dark_mode = app_config->get("dark_color_mode") == "1") { NppDarkMode::InitDarkMode(); + if (dark_mode != NppDarkMode::IsDarkMode()) + NppDarkMode::SetDarkMode(dark_mode); + } #endif SplashScreen* scrn = nullptr; if (app_config->get("show_splash_screen") == "1") { @@ -884,7 +889,7 @@ bool GUI_App::on_init_inner() } // create splash screen with updated bmp - scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("prusa_slicer_logo", nullptr, 400), + scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); #ifndef __linux__ wxYield(); @@ -916,6 +921,23 @@ bool GUI_App::on_init_inner() } } }); + Bind(EVT_SLIC3R_ALPHA_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + app_config->save(); + if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") { + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAlphaAvailable); + } + } + }); + Bind(EVT_SLIC3R_BETA_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + app_config->save(); + if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") { + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::NewAlphaAvailable); + this->plater_->get_notification_manager()->push_notification(NotificationType::NewBetaAvailable); + } + } + }); } else { #ifdef __WXMSW__ @@ -2001,14 +2023,14 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } -void GUI_App::open_preferences(size_t open_on_tab) +void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) { bool app_layout_changed = false; { // the dialog needs to be destroyed before the call to recreate_GUI() // or sometimes the application crashes into wxDialogBase() destructor // so we put it into an inner scope - PreferencesDialog dlg(mainframe, open_on_tab); + PreferencesDialog dlg(mainframe, open_on_tab, highlight_option); dlg.ShowModal(); app_layout_changed = dlg.settings_layout_changed(); #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 7d6e11f54..1e4bfc522 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -167,6 +167,7 @@ public: bool is_editor() const { return m_app_mode == EAppMode::Editor; } bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } bool is_recreating_gui() const { return m_is_recreating_gui; } + std::string logo_name() const { return is_editor() ? "PrusaSlicer" : "PrusaSlicer-gcodeviewer"; } // To be called after the GUI is fully built up. // Process command line parameters cached in this->init_params, @@ -259,7 +260,7 @@ public: wxString current_language_code_safe() const; bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } - void open_preferences(size_t open_on_tab = 0); + void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); virtual bool OnExceptionInMainLoop() override; // Calls wxLaunchDefaultBrowser if user confirms in dialog. diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d2a6b6e9a..83f9d1ec1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1405,9 +1405,11 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false item = m_objects_model->GetItemById(obj_idx); std::vector volumes; + // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common + /* if (type == ModelVolumeType::MODEL_PART) load_part(*(*m_objects)[obj_idx], volumes, type, from_galery); - else + else*/ load_modifier(*(*m_objects)[obj_idx], volumes, type, from_galery); if (volumes.empty()) @@ -1430,8 +1432,8 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false selection_changed(); } - -void ObjectList::load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery/* = false*/) +/* +void ObjectList::load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery/* = false* /) { if (type != ModelVolumeType::MODEL_PART) return; @@ -1489,11 +1491,12 @@ void ObjectList::load_part(ModelObject& model_object, std::vector& } } } - +*/ void ObjectList::load_modifier(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery) { - if (type == ModelVolumeType::MODEL_PART) - return; + // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common + //if (type == ModelVolumeType::MODEL_PART) + // return; wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); @@ -4123,8 +4126,7 @@ void ObjectList::fix_through_netfabb() Plater::TakeSnapshot snapshot(plater, _L("Fix through NetFabb")); // Open a progress dialog. - wxProgressDialog progress_dlg(_L("Fixing through NetFabb"), "", 100, - nullptr, // ! parent of the wxProgressDialog should be nullptr to avoid flickering during the model fixing + wxProgressDialog progress_dlg(_L("Fixing through NetFabb"), "", 100, plater, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); int model_idx{ 0 }; if (vol_idxs.empty()) { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index cc619fc45..535bfa7a7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -249,7 +249,8 @@ public: bool is_instance_or_object_selected(); void load_subobject(ModelVolumeType type, bool from_galery = false); - void load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); + // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common + //void load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); void load_modifier(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_shape_object(const std::string &type_name); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index b8c053f88..09492db09 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -547,8 +547,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection) } else { m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); - m_new_scale = volume->get_instance_scaling_factor() * 100.; + m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); + m_new_scale = volume->get_instance_scaling_factor() * 100.; } m_new_enabled = true; @@ -569,7 +569,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; - m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size())); + m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); m_new_enabled = true; } else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { @@ -635,7 +635,6 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } - if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); @@ -658,8 +657,14 @@ void ObjectManipulation::update_if_dirty() else m_og->disable(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (!selection.is_dragging()) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS update_reset_buttons_visibility(); update_mirror_buttons_visibility(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_dirty = false; } @@ -861,7 +866,7 @@ void ObjectManipulation::change_scale_value(int axis, double value) Vec3d scale = m_cache.scale; scale(axis) = value; - this->do_scale(axis, scale); + this->do_scale(axis, 0.01 * scale); m_cache.scale = scale; m_cache.scale_rounded(axis) = DBL_MAX; @@ -880,14 +885,21 @@ void ObjectManipulation::change_size_value(int axis, double value) const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d ref_size = m_cache.size; - if (selection.is_single_volume() || selection.is_single_modifier()) - ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); + if (selection.is_single_volume() || selection.is_single_modifier()) { + const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const Vec3d local_size = size.cwiseQuotient(v->get_instance_scaling_factor()); + const Vec3d local_ref_size = v->bounding_box().size().cwiseProduct(v->get_volume_scaling_factor()); + const Vec3d local_change = local_size.cwiseQuotient(local_ref_size); + + size = local_change.cwiseProduct(v->get_volume_scaling_factor()); + ref_size = Vec3d::Ones(); + } else if (selection.is_single_full_instance()) ref_size = m_world_coordinates ? selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); - this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); + this->do_scale(axis, size.cwiseQuotient(ref_size)); m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; @@ -910,7 +922,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const scaling_factor = scale(axis) * Vec3d::Ones(); selection.start_dragging(); - selection.scale(scaling_factor * 0.01, transformation_type); + selection.scale(scaling_factor, transformation_type); wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 57840dda2..8ca2a39c5 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -13,6 +13,7 @@ #include "DoubleSlider.hpp" #include "Plater.hpp" #include "MainFrame.hpp" +#include "format.hpp" #include #include @@ -687,7 +688,6 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (m_layers_slider->IsNewPrint()) { const Print& print = wxGetApp().plater()->fff_print(); - double delta_area = scale_(scale_(25)); // equal to 25 mm2 //bool is_possible_auto_color_change = false; for (auto object : print.objects()) { @@ -708,7 +708,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee int i, min_solid_height = int(0.25 * num_layers); for (i = 1; i <= min_solid_height; ++ i) { double cur_area = area(object->get_layer(i)->lslices); - if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) { + if (!DoubleSlider::equivalent_areas(bottom_area, cur_area)) { // but due to the elephant foot compensation, the first layer may be slightly smaller than the others if (i == 1 && fabs(cur_area - bottom_area) / bottom_area < 0.1) { // So, let process this case and use second layer as a bottom @@ -721,33 +721,23 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (i < min_solid_height) continue; - // bottom layer have to be a biggest, so control relation between bottom layer and object size - double prev_area = area(object->get_layer(i)->lslices); - for ( i++; i < num_layers; i++) { - double cur_area = area(object->get_layer(i)->lslices); - if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) - break; - prev_area = cur_area; - } - if (i < num_layers) - continue; - - double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); - if( bottom_area - top_area > delta_area) { - NotificationManager *notif_mngr = wxGetApp().plater()->get_notification_manager(); + if (DoubleSlider::check_color_change(object, i, num_layers, true, [this, object](Layer*) { + NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); notif_mngr->push_notification( NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, - _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", - _u8L("Apply auto color change to print"), + _u8L("NOTE:") + "\n" + + format(_u8L("Sliced object \"%1%\" looks like a logo or a sign"), object->model_object()->name) + "\n", + _u8L("Apply automatic color change"), [this](wxEvtHandler*) { m_layers_slider->auto_color_change(); return true; }); notif_mngr->apply_in_preview(); - + return true; + }) ) + // first object with color chnages is found break; - } } } diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index 5429e2658..f5e3083a7 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -66,7 +66,7 @@ bool GalleryDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& f GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) : - DPIDialog(parent, wxID_ANY, _L("Shapes Gallery"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + DPIDialog(parent, wxID_ANY, _L("Shape Gallery"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { #ifndef _WIN32 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -75,10 +75,12 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Select shape from the gallery") + ":"); - m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(55 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()), + m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()), wxLC_ICON | wxSIMPLE_BORDER); - m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); - m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); + m_list_ctrl->Bind(wxEVT_LIST_KEY_DOWN, &GalleryDialog::key_down, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &GalleryDialog::show_context_menu, this); m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) { m_selected_items.clear(); select(event); @@ -111,19 +113,11 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) this->Bind(wxEVT_BUTTON, method, this, ID); }; - auto enable_del_fn = [this]() { - if (m_selected_items.empty()) - return false; - for (const Item& item : m_selected_items) - if (item.is_system) - return false; - return true; - }; - - add_btn(0, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes); - add_btn(1, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, enable_del_fn); - add_btn(2, ID_BTN_REPLACE_CUSTOM_PNG, _L("Replace PNG"), _L("Replace PNG for custom shape. You can't raplace PNG for system shape"),&GalleryDialog::replace_custom_png, [this]() { return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); }); - buttons->InsertStretchSpacer(3, 2* BORDER_W); + size_t btn_pos = 0; + add_btn(btn_pos++, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes); + add_btn(btn_pos++, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, [this](){ return can_delete(); }); + //add_btn(btn_pos++, ID_BTN_REPLACE_CUSTOM_PNG, _L("Change thumbnail"), _L("Replace PNG for custom shape. You can't raplace thimbnail for system shape"), &GalleryDialog::change_thumbnail, [this](){ return can_change_thumbnail(); }); + buttons->InsertStretchSpacer(btn_pos, 2* BORDER_W); load_label_icon_list(); @@ -146,13 +140,28 @@ GalleryDialog::~GalleryDialog() { } +bool GalleryDialog::can_delete() +{ + if (m_selected_items.empty()) + return false; + for (const Item& item : m_selected_items) + if (item.is_system) + return false; + return true; +} + +bool GalleryDialog::can_change_thumbnail() +{ + return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); +} + void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); msw_buttons_rescale(this, em, { ID_BTN_ADD_CUSTOM_SHAPE, ID_BTN_DEL_CUSTOM_SHAPE, ID_BTN_REPLACE_CUSTOM_PNG, wxID_OK, wxID_CLOSE }); - wxSize size = wxSize(55 * em, 35 * em); + wxSize size = wxSize(50 * em, 35 * em); m_list_ctrl->SetMinSize(size); m_list_ctrl->SetSize(size); @@ -405,7 +414,7 @@ void GalleryDialog::add_custom_shapes(wxEvent& event) load_files(input_files); } -void GalleryDialog::del_custom_shapes(wxEvent& event) +void GalleryDialog::del_custom_shapes() { auto custom_dir = get_dir(false); @@ -438,7 +447,7 @@ static void show_warning(const wxString& title, const std::string& error_file_ty dialog.ShowModal(); } -void GalleryDialog::replace_custom_png(wxEvent& event) +void GalleryDialog::change_thumbnail() { if (m_selected_items.size() != 1 || m_selected_items[0].is_system) return; @@ -461,8 +470,11 @@ void GalleryDialog::replace_custom_png(wxEvent& event) } try { + fs::path png_path = fs::path(get_dir(false) / m_selected_items[0].name); + png_path.replace_extension("png"); + fs::path current = fs::path(into_u8(input_files.Item(0))); - fs::copy_file(current, get_dir(false) / (m_selected_items[0].name + ".png"), fs::copy_option::overwrite_if_exists); + fs::copy_file(current, png_path, fs::copy_option::overwrite_if_exists); } catch (fs::filesystem_error const& e) { std::cerr << e.what() << '\n'; @@ -491,6 +503,23 @@ void GalleryDialog::deselect(wxListEvent& event) m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; })); } +void GalleryDialog::show_context_menu(wxListEvent& event) +{ + wxMenu* menu = new wxMenu(); + if (can_delete()) + append_menu_item(menu, wxID_ANY, _L("Delete"), "", [this](wxCommandEvent&) { del_custom_shapes(); }); + if (can_change_thumbnail()) + append_menu_item(menu, wxID_ANY, _L("Change thumbnail"), "", [this](wxCommandEvent&) { change_thumbnail(); }); + + this->PopupMenu(menu); +} + +void GalleryDialog::key_down(wxListEvent& event) +{ + if (can_delete() && (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK)) + del_custom_shapes(); +} + void GalleryDialog::update() { m_selected_items.clear(); @@ -535,12 +564,12 @@ bool GalleryDialog::load_files(const wxArrayString& input_files) if (!fs::exists(dest_dir / current.filename())) fs::copy_file(current, dest_dir / current.filename()); else { - std::string filename = current.filename().string(); + std::string filename = current.stem().string(); int file_idx = 0; for (auto& dir_entry : fs::directory_iterator(dest_dir)) if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) { - std::string name = dir_entry.path().filename().string(); + std::string name = dir_entry.path().stem().string(); if (filename == name) { if (file_idx == 0) file_idx++; diff --git a/src/slic3r/GUI/GalleryDialog.hpp b/src/slic3r/GUI/GalleryDialog.hpp index 8cf3f0096..2aa04ffa2 100644 --- a/src/slic3r/GUI/GalleryDialog.hpp +++ b/src/slic3r/GUI/GalleryDialog.hpp @@ -33,10 +33,17 @@ class GalleryDialog : public DPIDialog void load_label_icon_list(); void add_custom_shapes(wxEvent& event); - void del_custom_shapes(wxEvent& event); - void replace_custom_png(wxEvent& event); + void del_custom_shapes(); + void del_custom_shapes(wxEvent& event) { del_custom_shapes(); } + void change_thumbnail(); + void change_thumbnail(wxEvent& event) { change_thumbnail(); } void select(wxListEvent& event); void deselect(wxListEvent& event); + void show_context_menu(wxListEvent& event); + void key_down(wxListEvent& event); + + bool can_delete(); + bool can_change_thumbnail(); void update(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 437106fed..16856a9e3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/format.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -20,7 +21,7 @@ namespace Slic3r::GUI { void GLGizmoFdmSupports::on_shutdown() { - m_angle_threshold_deg = 0.f; + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); m_parent.toggle_model_objects_visibility(true); } @@ -51,10 +52,20 @@ bool GLGizmoFdmSupports::on_init() m_desc["remove_all"] = _L("Remove all selection"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); - m_desc["highlight_by_angle"] = _L("Highlight by angle"); + m_desc["pointer"] = _L("Triangles"); + m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); m_desc["enforce_button"] = _L("Enforce"); m_desc["cancel"] = _L("Cancel"); + m_desc["tool_type"] = _L("Tool type") + ": "; + m_desc["tool_brush"] = _L("Brush"); + m_desc["tool_smart_fill"] = _L("Smart fill"); + + m_desc["smart_fill_angle"] = _L("Smart fill angle"); + + m_desc["split_triangles"] = _L("Split triangles"); + m_desc["on_overhangs_only"] = _L("On overhangs only"); + return true; } @@ -82,42 +93,54 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(17.0f); + const float approx_height = m_imgui->scaled(23.f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) - + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x - + m_imgui->scaled(2.5f); - const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x - + m_imgui->scaled(2.5f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); + const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); + + const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x; const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x; const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f); const float minimal_slider_width = m_imgui->scaled(4.f); + const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); + const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); + float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t + "_caption")).x); - total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); } - caption_max += m_imgui->scaled(1.f); - total_text_max += m_imgui->scaled(1.f); + total_text_max += caption_max + m_imgui->scaled(1.f); + caption_max += m_imgui->scaled(1.f); - float window_width = minimal_slider_width + std::max(autoset_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); + float window_width = minimal_slider_width + sliders_left_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); - window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2); + window_width = std::max(window_width, split_triangles_checkbox_width); + window_width = std::max(window_width, on_overhangs_only_checkbox_width); + window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); + window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { @@ -129,114 +152,148 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l for (const auto &t : std::array{"enforce", "block", "remove"}) draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); - m_imgui->text(""); ImGui::Separator(); + float position_before_text_y = ImGui::GetCursorPos().y; ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["highlight_by_angle"] + ":"); + m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); ImGui::AlignTextToFramePadding(); + float position_after_text_y = ImGui::GetCursorPos().y; std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo," "placed after the number with no whitespace in between."); - ImGui::SameLine(autoset_slider_left); - ImGui::PushItemWidth(window_width - autoset_slider_left); - if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, format_str.data())) { - m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); + + float slider_height = m_imgui->get_slider_float_height(); + // Makes slider to be aligned to bottom of the multi-line text. + float slider_start_position = std::max(position_before_text_y, position_after_text_y - slider_height); + ImGui::SetCursorPosY(slider_start_position); + + if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) { + m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); if (! m_parent.is_using_slope()) { m_parent.use_slope(true); m_parent.set_as_dirty(); } } - m_imgui->disabled_begin(m_angle_threshold_deg == 0.f); + // Restores the cursor position to be below the multi-line text. + ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " + "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]), max_tooltip_width); + + m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); ImGui::NewLine(); ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { - select_facets_by_angle(m_angle_threshold_deg, false); - m_angle_threshold_deg = 0.f; + select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } ImGui::SameLine(window_width - buttons_width); if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { - m_angle_threshold_deg = 0.f; + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } m_imgui->disabled_end(); + ImGui::Separator(); - if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), - UndoRedo::SnapshotType::GizmoAction); - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) { - ++idx; - m_triangle_selectors[idx]->reset(); - m_triangle_selectors[idx]->request_update_render_data(); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["tool_type"]); + + float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; + ImGui::SameLine(tool_type_offset); + ImGui::PushItemWidth(tool_type_radio_brush); + if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) + m_tool_type = ToolType::BRUSH; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); + + ImGui::SameLine(tool_type_offset + tool_type_radio_brush); + ImGui::PushItemWidth(tool_type_radio_smart_fill); + if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) + m_tool_type = ToolType::SMART_FILL; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + + m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); + + ImGui::Separator(); + + if (m_tool_type == ToolType::BRUSH) { + m_imgui->text(m_desc.at("cursor_type")); + ImGui::NewLine(); + + float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(cursor_type_offset); + ImGui::PushItemWidth(cursor_type_radio_sphere); + if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + m_cursor_type = TriangleSelector::CursorType::SPHERE; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); + ImGui::PushItemWidth(cursor_type_radio_circle); + + if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + m_cursor_type = TriangleSelector::CursorType::CIRCLE; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); + ImGui::PushItemWidth(cursor_type_radio_pointer); + + if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) + m_cursor_type = TriangleSelector::CursorType::POINTER; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); + + m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("cursor_size")); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); + m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); + + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); + + m_imgui->disabled_end(); + } else { + assert(m_tool_type == ToolType::SMART_FILL); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["smart_fill_angle"] + ":"); + + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); } - } - update_model_object(); - m_parent.set_as_dirty(); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); } - - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(cursor_slider_left); - ImGui::PushItemWidth(window_width - cursor_slider_left); - m_imgui->slider_float(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_type")); - ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f)); - ImGui::PushItemWidth(cursor_type_radio_width1); - - bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE; - if (m_imgui->radio_button(m_desc["sphere"], sphere_sel)) - sphere_sel = true; - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f)); - ImGui::PushItemWidth(cursor_type_radio_width2); - - if (m_imgui->radio_button(m_desc["circle"], ! sphere_sel)) - sphere_sel = false; - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - m_cursor_type = sphere_sel - ? TriangleSelector::CursorType::SPHERE - : TriangleSelector::CursorType::CIRCLE; - - - - ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); @@ -250,19 +307,31 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } } - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); auto clp_dist = float(m_c->object_clipper()->get_position()); - if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f")) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); + + ImGui::Separator(); + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + m_triangle_selectors[idx]->request_update_render_data(); + } + + update_model_object(); + m_parent.set_as_dirty(); } + m_imgui->end(); } @@ -291,7 +360,7 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) // Now calculate dot product of vert_direction and facets' normals. int idx = 0; const indexed_triangle_set &its = mv->mesh().its; - for (stl_triangle_vertex_indices face : its.indices) { + for (const stl_triangle_vertex_indices &face : its.indices) { if (its_face_normal(its, face).dot(down) > dot_limit) { m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); m_triangle_selectors.back()->request_update_render_data(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 0fb03140a..4929714a2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -35,7 +35,6 @@ private: PainterGizmoType get_painter_type() const override; void select_facets_by_angle(float threshold, bool block); - float m_angle_threshold_deg = 0.f; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 17630e5c6..d45a2e613 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -508,20 +508,23 @@ RENDER_AGAIN: m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float settings_sliders_left = - std::max({m_imgui->calc_text_size(m_desc.at("offset")).x, - m_imgui->calc_text_size(m_desc.at("quality")).x, - m_imgui->calc_text_size(m_desc.at("closing_distance")).x, - m_imgui->calc_text_size(m_desc.at("hole_diameter")).x, - m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) - + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f); + + const float settings_sliders_left = + std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x, + m_imgui->calc_text_size(m_desc.at("quality")).x, + m_imgui->calc_text_size(m_desc.at("closing_distance")).x, + m_imgui->calc_text_size(m_desc.at("hole_diameter")).x, + m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left); - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); + const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x; + float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); - window_width = std::max(window_width, m_imgui->calc_text_size(m_desc.at("preview")).x); + window_width = std::max(window_width, button_preview_width); if (m_imgui->button(m_desc["preview"])) hollow_mesh(); @@ -544,16 +547,12 @@ RENDER_AGAIN: float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("offset")); - ImGui::SameLine(settings_sliders_left); + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); ImGui::PushItemWidth(window_width - settings_sliders_left); - m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[0].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm"); + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width); + bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider @@ -561,15 +560,11 @@ RENDER_AGAIN: if (current_mode >= quality_mode) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("quality")); - ImGui::SameLine(settings_sliders_left); - m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[1].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f"); + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width); + slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); @@ -578,15 +573,11 @@ RENDER_AGAIN: if (current_mode >= closing_d_mode) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("closing_distance")); - ImGui::SameLine(settings_sliders_left); - m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[2].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width); + slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); @@ -626,11 +617,11 @@ RENDER_AGAIN: m_new_hole_radius = diameter_upper_cap / 2.f; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("hole_diameter")); - ImGui::SameLine(diameter_slider_left); + ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); ImGui::PushItemWidth(window_width - diameter_slider_left); float diam = 2.f * m_new_hole_radius; - m_imgui->slider_float("", &diam, 1.f, 15.f, "%.1f mm", 1.f, false); + m_imgui->slider_float("##hole_diameter", &diam, 1.f, 15.f, "%.1f mm", 1.f, false); // Let's clamp the value (which could have been entered by keyboard) to a larger range // than the slider. This allows entering off-scale values and still protects against //complete non-sense. @@ -642,8 +633,8 @@ RENDER_AGAIN: ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["hole_depth"]); - ImGui::SameLine(diameter_slider_left); - m_imgui->slider_float(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); + ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); + m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); // Same as above: m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f); @@ -709,10 +700,10 @@ RENDER_AGAIN: } } - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGui::PushItemWidth(window_width - settings_sliders_left); float clp_dist = m_c->object_clipper()->get_position(); - if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f")) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); // make sure supports are shown/hidden as appropriate @@ -744,7 +735,7 @@ RENDER_AGAIN: if (force_refresh) m_parent.set_as_dirty(); - + if (config_changed) m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index d7824357f..ee15dab88 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -42,7 +42,6 @@ void GLGizmoMmuSegmentation::on_shutdown() std::string GLGizmoMmuSegmentation::on_get_name() const { - // FIXME Lukas H.: Discuss and change shortcut return _u8L("Multimaterial painting"); } @@ -107,7 +106,6 @@ void GLGizmoMmuSegmentation::init_extruders_data() bool GLGizmoMmuSegmentation::on_init() { - // FIXME Lukas H.: Discuss and change shortcut m_shortcut_key = WXK_CONTROL_N; m_desc["reset_direction"] = _L("Reset direction"); @@ -123,7 +121,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["remove_all"] = _L("Remove all painted areas"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Pointer"); + m_desc["pointer"] = _L("Triangles"); m_desc["tool_type"] = _L("Tool type"); m_desc["tool_brush"] = _L("Brush"); @@ -131,6 +129,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["tool_bucket_fill"] = _L("Bucket fill"); m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["split_triangles"] = _L("Split triangles"); init_extruders_data(); @@ -144,7 +143,7 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_triangles(selection, false); + render_triangles(selection); m_c->object_clipper()->render_cut(); m_c->instances_hider()->render_cut(); @@ -175,6 +174,43 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) } } +void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const +{ + ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data(); + auto *shader = wxGetApp().get_shader("mm_gouraud"); + if (!shader) + return; + shader->start_using(); + shader->set_uniform("clipping_plane", clp_data.clp_dataf); + shader->set_uniform("z_range", clp_data.z_range); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix(); + + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); + + shader->set_uniform("volume_world_matrix", trafo_matrix); + m_triangle_selectors[mesh_id]->render(m_imgui); + + glsafe(::glPopMatrix()); + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + } +} + static void render_extruders_combo(const std::string &label, const std::vector &extruders, const std::vector> &extruders_colors, @@ -236,7 +272,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (!m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(25.0f); + const float approx_height = m_imgui->scaled(22.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -263,19 +299,22 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + float caption_max = 0.f; - float total_text_max = 0.; + float total_text_max = 0.f; for (const auto &t : std::array{"first_color", "second_color", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t + "_caption")).x); - total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); } - caption_max += m_imgui->scaled(1.f); - total_text_max += m_imgui->scaled(1.f); + total_text_max += caption_max + m_imgui->scaled(1.f); + caption_max += m_imgui->scaled(1.f); float sliders_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); float window_width = minimal_slider_width + sliders_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); @@ -289,7 +328,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott for (const auto &t : std::array{"first_color", "second_color", "remove"}) draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); - m_imgui->text(""); ImGui::Separator(); ImGui::AlignTextToFramePadding(); @@ -321,64 +359,47 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::Separator(); m_imgui->text(m_desc.at("tool_type")); - - float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(2.f)) / 2.f; - ImGui::NewLine(); - ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f)); + float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(tool_type_offset); ImGui::PushItemWidth(tool_type_radio_brush); - if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH)) { - m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH; + if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) { + m_tool_type = ToolType::BRUSH; for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); - ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f)); + ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); - if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SMART_FILL)) { - m_tool_type = GLGizmoMmuSegmentation::ToolType::SMART_FILL; + if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) { + m_tool_type = ToolType::SMART_FILL; for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); - ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill + m_imgui->scaled(0.f)); + ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_bucket_fill); - if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) { - m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL; + if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) { + m_tool_type = ToolType::BUCKET_FILL; for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets that have the same color."), max_tooltip_width); ImGui::Separator(); @@ -386,47 +407,32 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_imgui->text(m_desc.at("cursor_type")); ImGui::NewLine(); - float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(2.f)) / 2.f; - ImGui::SameLine(cursor_type_offset + m_imgui->scaled(0.f)); + float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(cursor_type_offset); ImGui::PushItemWidth(cursor_type_radio_sphere); if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); - ImGui::SameLine(cursor_type_offset +cursor_type_radio_sphere + m_imgui->scaled(0.f)); + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); - ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle + m_imgui->scaled(0.f)); + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_pointer); if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) m_cursor_type = TriangleSelector::CursorType::POINTER; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); @@ -434,24 +440,14 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); - m_imgui->slider_float(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); - m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); m_imgui->disabled_end(); @@ -469,13 +465,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott triangle_selector->request_update_render_data(); } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); ImGui::Separator(); } @@ -492,15 +483,11 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); auto clp_dist = float(m_c->object_clipper()->get_position()); - if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f")) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { @@ -596,11 +583,6 @@ std::array GLGizmoMmuSegmentation::get_cursor_sphere_right_button_colo return {color[0], color[1], color[2], 0.25f}; } -static std::array get_seed_fill_color(const std::array &base_color) -{ - return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f}; -} - void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) { if (m_update_render_data) @@ -609,29 +591,27 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) auto *shader = wxGetApp().get_current_shader(); if (!shader) return; - assert(shader->get_name() == "gouraud"); - ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);}); - shader->set_uniform("compute_triangle_normals_in_fs", true); + assert(shader->get_name() == "mm_gouraud"); for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx) if (m_gizmo_scene.has_VBOs(color_idx)) { if (color_idx > m_colors.size()) // Seed fill VBO - shader->set_uniform("uniform_color", get_seed_fill_color(color_idx == (m_colors.size() + 1) ? m_default_volume_color : m_colors[color_idx - (m_colors.size() + 1) - 1])); + shader->set_uniform("uniform_color", TriangleSelectorGUI::get_seed_fill_color(color_idx == (m_colors.size() + 1) ? m_default_volume_color : m_colors[color_idx - (m_colors.size() + 1) - 1])); else // Normal VBO shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color : m_colors[color_idx - 1]); m_gizmo_scene.render(color_idx); } - if (m_gizmo_scene.has_contour_VBO()) { - ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); + if (m_paint_contour.has_VBO()) { + ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); }); shader->stop_using(); auto *contour_shader = wxGetApp().get_shader("mm_contour"); contour_shader->start_using(); glsafe(::glDepthFunc(GL_LEQUAL)); - m_gizmo_scene.render_contour(); + m_paint_contour.render(); glsafe(::glDepthFunc(GL_LESS)); contour_shader->stop_using(); @@ -670,23 +650,24 @@ void TriangleSelectorMmGui::update_render_data() m_gizmo_scene.finalize_triangle_indices(); + m_paint_contour.release_geometry(); std::vector contour_edges = this->get_seed_fill_contour(); - m_gizmo_scene.contour_vertices.reserve(contour_edges.size() * 6); + m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); for (const Vec2i &edge : contour_edges) { - m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); - m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); - m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); - m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); - m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); - m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); } - m_gizmo_scene.contour_indices.assign(m_gizmo_scene.contour_vertices.size() / 3, 0); - std::iota(m_gizmo_scene.contour_indices.begin(), m_gizmo_scene.contour_indices.end(), 0); - m_gizmo_scene.contour_indices_size = m_gizmo_scene.contour_indices.size(); + m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); + std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); + m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); - m_gizmo_scene.finalize_contour(); + m_paint_contour.finalize_geometry(); } wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const @@ -710,14 +691,6 @@ void GLMmSegmentationGizmo3DScene::release_geometry() { glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id)); triangle_indices_VBO_id = 0; } - if (this->contour_vertices_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->contour_vertices_VBO_id)); - this->contour_vertices_VBO_id = 0; - } - if (this->contour_indices_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->contour_indices_VBO_id)); - this->contour_indices_VBO_id = 0; - } this->clear(); } @@ -745,29 +718,6 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } -void GLMmSegmentationGizmo3DScene::render_contour() const -{ - assert(this->contour_vertices_VBO_id != 0); - assert(this->contour_indices_VBO_id != 0); - - glsafe(::glLineWidth(4.0f)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_vertices_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - if (this->contour_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices_VBO_id)); - glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -} - void GLMmSegmentationGizmo3DScene::finalize_vertices() { assert(this->vertices_VBO_id == 0); @@ -795,26 +745,4 @@ void GLMmSegmentationGizmo3DScene::finalize_triangle_indices() } } -void GLMmSegmentationGizmo3DScene::finalize_contour() -{ - assert(this->contour_vertices_VBO_id == 0); - assert(this->contour_indices_VBO_id == 0); - - if (!this->contour_vertices.empty()) { - glsafe(::glGenBuffers(1, &this->contour_vertices_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_vertices_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->contour_vertices.clear(); - } - - if (!this->contour_indices.empty()) { - glsafe(::glGenBuffers(1, &this->contour_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_indices_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->contour_indices.clear(); - } -} - } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 851a5ac4f..0991527f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -25,8 +25,6 @@ public: return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0; } - [[nodiscard]] inline bool has_contour_VBO() const { return this->contour_indices_VBO_id != 0; } - // Release the geometry data, release OpenGL VBOs. void release_geometry(); // Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects @@ -35,9 +33,6 @@ public: // Finalize the initialization of the indices, upload the indices to OpenGL VBO objects // and possibly releasing it if it has been loaded into the VBOs. void finalize_triangle_indices(); - // Finalize the initialization of the contour geometry and the indices, upload both to OpenGL VBO objects - // and possibly releasing it if it has been loaded into the VBOs. - void finalize_contour(); void clear() { @@ -47,34 +42,21 @@ public: for (size_t &triangle_indices_size : this->triangle_indices_sizes) triangle_indices_size = 0; - - this->contour_vertices.clear(); - this->contour_indices.clear(); - this->contour_indices_size = 0; } void render(size_t triangle_indices_idx) const; - void render_contour() const; - std::vector vertices; std::vector> triangle_indices; - std::vector contour_vertices; - std::vector contour_indices; - // When the triangle indices are loaded into the graphics card as Vertex Buffer Objects, // the above mentioned std::vectors are cleared and the following variables keep their original length. std::vector triangle_indices_sizes; - size_t contour_indices_size{0}; // IDs of the Vertex Array Objects, into which the geometry has been loaded. // Zero if the VBOs are not sent to GPU yet. unsigned int vertices_VBO_id{0}; std::vector triangle_indices_VBO_ids; - - unsigned int contour_vertices_VBO_id{0}; - unsigned int contour_indices_VBO_id{0}; }; class TriangleSelectorMmGui : public TriangleSelectorGUI { @@ -107,6 +89,8 @@ public: void set_painter_gizmo_data(const Selection& selection) override; + void render_triangles(const Selection& selection) const override; + // TriangleSelector::serialization/deserialization has a limit to store 19 different states. // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. // When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 42bdd0843..8cfb3fbca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -43,42 +43,48 @@ void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) } } - - -void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool use_polygon_offset_fill) const +GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const { - const ModelObject* mo = m_c->selection_info()->model_object(); - - ScopeGuard offset_fill_guard([&use_polygon_offset_fill]() { - if (use_polygon_offset_fill) - glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); - }); - if (use_polygon_offset_fill) { - glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); - glsafe(::glPolygonOffset(-5.0, -5.0)); - } - + ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; // Take care of the clipping plane. The normal of the clipping plane is // saved with opposite sign than we need to pass to OpenGL (FIXME) - bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; - float clp_dataf[4] = {0.f, 0.f, 1.f, FLT_MAX}; - if (clipping_plane_active) { - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - for (size_t i=0; i<3; ++i) - clp_dataf[i] = -1.f * float(clp->get_data()[i]); - clp_dataf[3] = float(clp->get_data()[3]); + if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + for (size_t i = 0; i < 3; ++i) + clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); + clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); } - auto *shader = wxGetApp().get_shader("gouraud"); + // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) + if (m_c->get_canvas()->get_use_clipping_planes()) { + const std::array &clps = m_c->get_canvas()->get_clipping_planes(); + clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; + } + + return clp_data_out; +} + +void GLGizmoPainterBase::render_triangles(const Selection& selection) const +{ +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + auto* shader = wxGetApp().get_shader("gouraud_mod"); +#else + auto* shader = wxGetApp().get_shader("gouraud"); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (! shader) return; shader->start_using(); shader->set_uniform("slope.actived", false); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + shader->set_uniform("print_volume.type", 0); +#else shader->set_uniform("print_box.actived", false); - shader->set_uniform("clipping_plane", clp_dataf, 4); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); - int mesh_id = -1; + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; for (const ModelVolume* mv : mo->volumes) { if (! mv->is_model_part()) continue; @@ -100,7 +106,11 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool // to the shader input variable print_box.volume_world_matrix before // rendering the painted triangles. When this matrix is not set, the // wrong transformation matrix is used for "Clipping of view". +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + shader->set_uniform("volume_world_matrix", trafo_matrix); +#else shader->set_uniform("print_box.volume_world_matrix", trafo_matrix); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_triangle_selectors[mesh_id]->render(m_imgui); @@ -253,7 +263,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); m_parent.set_as_dirty(); if (m_rr.mesh_id != -1) { - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } @@ -284,11 +299,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); } - const Camera &camera = wxGetApp().plater()->get_camera(); - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d &instance_trafo = mi->get_transformation().get_matrix(); + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); // List of mouse positions that will be used as seeds for painting. std::vector mouse_positions{mouse_position}; @@ -314,10 +330,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Precalculate transformations of individual meshes. std::vector trafo_matrices; - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) + std::vector trafo_matrices_not_translate; + for (const ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); - } + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); + } // Now "click" into all the prepared points and spill paint around them. for (const Vec2d& mp : mouse_positions) { @@ -339,7 +357,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return dragging_while_painting; } - const Transform3d& trafo_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); @@ -348,7 +367,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state); if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true); else if (m_tool_type == ToolType::BUCKET_FILL) @@ -357,7 +377,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_seed_fill_last_mesh_id = -1; } else if (m_tool_type == ToolType::BRUSH) m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type, - new_state, trafo_matrix, m_triangle_splitting_enabled); + new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_last_mouse_click = mouse_position; @@ -370,17 +391,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_triangle_selectors.empty()) return false; - const Camera & camera = wxGetApp().plater()->get_camera(); - const Selection & selection = m_parent.get_selection(); - const ModelObject * mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d & instance_trafo = mi->get_transformation().get_matrix(); + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); // Precalculate transformations of individual meshes. std::vector trafo_matrices; + std::vector trafo_matrices_not_translate; for (const ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) + if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); + } // Now "click" into all the prepared points and spill paint around them. update_raycast_cache(mouse_position, camera, trafo_matrices); @@ -405,9 +430,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if(m_rr.mesh_id != m_seed_fill_last_mesh_id) seed_fill_unselect_all(); + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; + assert(m_rr.mesh_id < int(m_triangle_selectors.size())); if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false); else if (m_tool_type == ToolType::BUCKET_FILL) @@ -541,7 +569,10 @@ void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) m_schedule_update = true; } - +std::array TriangleSelectorGUI::get_seed_fill_color(const std::array &base_color) +{ + return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f}; +} void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { @@ -556,8 +587,13 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) auto* shader = wxGetApp().get_current_shader(); if (! shader) return; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + assert(shader->get_name() == "gouraud_mod"); +#else assert(shader->get_name() == "gouraud"); - +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);}); + shader->set_uniform("offset_depth_buffer", true); for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), std::make_pair(&m_iva_blockers, blockers_color)}) { if (iva.first->has_VBOs()) { @@ -566,6 +602,29 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) } } + for (auto &iva : m_iva_seed_fills) + if (iva.has_VBOs()) { + size_t color_idx = &iva - &m_iva_seed_fills.front(); + const std::array &color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + color_idx == 2 ? blockers_color : + GLVolume::NEUTRAL_COLOR); + shader->set_uniform("uniform_color", color); + iva.render(); + } + + if (m_paint_contour.has_VBO()) { + ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); + shader->stop_using(); + + auto *contour_shader = wxGetApp().get_shader("mm_contour"); + contour_shader->start_using(); + + glsafe(::glDepthFunc(GL_LEQUAL)); + m_paint_contour.render(); + glsafe(::glDepthFunc(GL_LESS)); + + contour_shader->stop_using(); + } #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) @@ -575,26 +634,33 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) #endif } - - void TriangleSelectorGUI::update_render_data() { - int enf_cnt = 0; - int blc_cnt = 0; + int enf_cnt = 0; + int blc_cnt = 0; + std::vector seed_fill_cnt(m_iva_seed_fills.size(), 0); for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) iva->release_geometry(); + for (auto &iva : m_iva_seed_fills) + iva.release_geometry(); + for (const Triangle &tr : m_triangles) { - if (!tr.valid() || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE) + if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) continue; - GLIndexedVertexArray &iva = tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : m_iva_blockers; - int & cnt = tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt; + int tr_state = int(tr.get_state()); + GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : + m_iva_blockers; + int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : + blc_cnt; const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v; const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v; const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v; - //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort + //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort // or the current implementation may be more cache friendly. const Vec3f n = (v1 - v0).cross(v2 - v1).normalized(); iva.push_geometry(v0, n); @@ -606,9 +672,87 @@ void TriangleSelectorGUI::update_render_data() for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) iva->finalize_geometry(true); + + for (auto &iva : m_iva_seed_fills) + iva.finalize_geometry(true); + + m_paint_contour.release_geometry(); + std::vector contour_edges = this->get_seed_fill_contour(); + m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); + for (const Vec2i &edge : contour_edges) { + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); + + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); + } + + m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); + std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); + m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); + + m_paint_contour.finalize_geometry(); } +void GLPaintContour::render() const +{ + assert(this->m_contour_VBO_id != 0); + assert(this->m_contour_EBO_id != 0); + glsafe(::glLineWidth(4.0f)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + if (this->contour_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLPaintContour::finalize_geometry() +{ + assert(this->m_contour_VBO_id == 0); + assert(this->m_contour_EBO_id == 0); + + if (!this->contour_vertices.empty()) { + glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + this->contour_vertices.clear(); + } + + if (!this->contour_indices.empty()) { + glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + this->contour_indices.clear(); + } +} + +void GLPaintContour::release_geometry() +{ + if (this->m_contour_VBO_id) { + glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); + this->m_contour_VBO_id = 0; + } + if (this->m_contour_EBO_id) { + glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); + this->m_contour_EBO_id = 0; + } + this->clear(); +} #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 8d37f2404..3093b0bec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -10,6 +10,7 @@ #include "libslic3r/Model.hpp" #include +#include @@ -26,6 +27,41 @@ enum class PainterGizmoType { MMU_SEGMENTATION }; +class GLPaintContour +{ +public: + GLPaintContour() = default; + + void render() const; + + inline bool has_VBO() const { return this->m_contour_EBO_id != 0; } + + // Release the geometry data, release OpenGL VBOs. + void release_geometry(); + + // Finalize the initialization of the contour geometry and the indices, upload both to OpenGL VBO objects + // and possibly releasing it if it has been loaded into the VBOs. + void finalize_geometry(); + + void clear() + { + this->contour_vertices.clear(); + this->contour_indices.clear(); + this->contour_indices_size = 0; + } + + std::vector contour_vertices; + std::vector contour_indices; + + // When the triangle indices are loaded into the graphics card as Vertex Buffer Objects, + // the above mentioned std::vectors are cleared and the following variables keep their original length. + size_t contour_indices_size{0}; + + // IDs of the Vertex Array Objects, into which the geometry has been loaded. + // Zero if the VBOs are not sent to GPU yet. + GLuint m_contour_VBO_id{0}; + GLuint m_contour_EBO_id{0}; +}; class TriangleSelectorGUI : public TriangleSelector { public: @@ -49,12 +85,18 @@ public: protected: bool m_update_render_data = false; + static std::array get_seed_fill_color(const std::array &base_color); + private: void update_render_data(); GLIndexedVertexArray m_iva_enforcers; GLIndexedVertexArray m_iva_blockers; + std::array m_iva_seed_fills; std::array m_varrays; + +protected: + GLPaintContour m_paint_contour; }; class GLGizmoTransparentRender @@ -84,7 +126,7 @@ public: virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); protected: - void render_triangles(const Selection& selection, const bool use_polygon_offset_fill = true) const; + virtual void render_triangles(const Selection& selection) const; void render_cursor() const; void render_cursor_circle() const; void render_cursor_sphere(const Transform3d& trafo) const; @@ -117,6 +159,9 @@ protected: ToolType m_tool_type = ToolType::BRUSH; float m_smart_fill_angle = 30.f; + bool m_paint_on_overhangs_only = false; + float m_highlight_by_angle_threshold_deg = 0.f; + static constexpr float SmartFillAngleMin = 0.0f; static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; @@ -131,6 +176,14 @@ protected: Right }; + struct ClippingPlaneDataWrapper + { + std::array clp_dataf; + std::array z_range; + }; + + ClippingPlaneDataWrapper get_clipping_plane_data() const; + private: bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; void update_raycast_cache(const Vec2d& mouse_position, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index b23528772..6971e7cdb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -77,7 +77,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(14.0f); + const float approx_height = m_imgui->scaled(12.5f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); @@ -87,27 +87,28 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x - + m_imgui->scaled(2.5f); - const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x - + m_imgui->scaled(2.5f); + + const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc["cursor_type"]).x + m_imgui->scaled(1.f); + const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t + "_caption")).x); - total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); } - caption_max += m_imgui->scaled(1.f); - total_text_max += m_imgui->scaled(1.f); + total_text_max += caption_max + m_imgui->scaled(1.f); + caption_max += m_imgui->scaled(1.f); - float window_width = minimal_slider_width + std::max(cursor_size_slider_left, clipping_slider_left); + float sliders_width = std::max(cursor_size_slider_left, clipping_slider_left); + float window_width = minimal_slider_width + sliders_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); - window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2); + window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_sphere + cursor_type_radio_circle); auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); @@ -119,76 +120,37 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) for (const auto &t : std::array{"enforce", "block", "remove"}) draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); - m_imgui->text(""); - - if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), - UndoRedo::SnapshotType::GizmoAction); - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) { - ++idx; - m_triangle_selectors[idx]->reset(); - m_triangle_selectors[idx]->request_update_render_data(); - } - } - - update_model_object(); - m_parent.set_as_dirty(); - } + ImGui::Separator(); const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(cursor_size_slider_left); - ImGui::PushItemWidth(window_width - cursor_size_slider_left); - m_imgui->slider_float(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + ImGui::SameLine(sliders_width); + ImGui::PushItemWidth(window_width - sliders_width); + m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_type")); - ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f)); - ImGui::PushItemWidth(cursor_type_radio_width1); - bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE; - if (m_imgui->radio_button(m_desc["sphere"], sphere_sel)) - sphere_sel = true; + float cursor_type_offset = cursor_type_radio_left + (window_width - cursor_type_radio_left - cursor_type_radio_sphere - cursor_type_radio_circle + m_imgui->scaled(0.5f)) / 2.f; + ImGui::SameLine(cursor_type_offset); + ImGui::PushItemWidth(cursor_type_radio_sphere); + if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f)); - ImGui::PushItemWidth(cursor_type_radio_width2); - - if (m_imgui->radio_button(m_desc["circle"], ! sphere_sel)) - sphere_sel = false; - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - m_cursor_type = sphere_sel - ? TriangleSelector::CursorType::SPHERE - : TriangleSelector::CursorType::CIRCLE; + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); + ImGui::PushItemWidth(cursor_type_radio_circle); + if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + m_cursor_type = TriangleSelector::CursorType::CIRCLE; + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { @@ -203,18 +165,29 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) } } - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SameLine(sliders_width); + ImGui::PushItemWidth(window_width - sliders_width); auto clp_dist = float(m_c->object_clipper()->get_position()); - if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f")) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); + + ImGui::Separator(); + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + m_triangle_selectors[idx]->request_update_render_data(); + } + + update_model_object(); + m_parent.set_as_dirty(); } m_imgui->end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 7ffd80aab..2e135b805 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -22,15 +22,16 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D &parent) , m_obj_index(0) , m_need_reload(false) , m_show_wireframe(false) - + // translation for GUI size , tr_mesh_name(_u8L("Mesh name")) , tr_triangles(_u8L("Triangles")) , tr_preview(_u8L("Preview")) , tr_detail_level(_u8L("Detail level")) , tr_decimate_ratio(_u8L("Decimate ratio")) - + // for wireframe , m_wireframe_VBO_id(0) , m_wireframe_IBO_id(0) + , m_wireframe_IBO_size(0) {} GLGizmoSimplify::~GLGizmoSimplify() { @@ -39,10 +40,11 @@ GLGizmoSimplify::~GLGizmoSimplify() { free_gpu(); } -bool GLGizmoSimplify::on_init() -{ - //m_grabbers.emplace_back(); - //m_shortcut_key = WXK_CONTROL_C; +bool GLGizmoSimplify::on_esc_key_down() { + if (m_state == State::settings || m_state == State::canceling) + return false; + + m_state = State::canceling; return true; } @@ -51,10 +53,6 @@ std::string GLGizmoSimplify::on_get_name() const return _u8L("Simplify"); } -void GLGizmoSimplify::on_render() { } - -void GLGizmoSimplify::on_render_for_picking() {} - void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { create_gui_cfg(); @@ -141,8 +139,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Separator(); if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) { - m_is_valid_result = false; m_configuration.use_count = !m_configuration.use_count; + live_preview(); } ImGui::SameLine(); m_imgui->disabled_begin(m_configuration.use_count); @@ -158,7 +156,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SetNextItemWidth(m_gui_cfg->input_width); static int reduction = 2; if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) { - m_is_valid_result = false; if (reduction < 0) reduction = 0; if (reduction > 4) reduction = 4; switch (reduction) { @@ -168,12 +165,13 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi case 3: m_configuration.max_error = 0.5f; break; case 4: m_configuration.max_error = 1.f; break; } + live_preview(); } m_imgui->disabled_end(); // !use_count if (ImGui::RadioButton("##use_count", m_configuration.use_count)) { - m_is_valid_result = false; m_configuration.use_count = !m_configuration.use_count; + live_preview(); } ImGui::SameLine(); @@ -190,13 +188,14 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SetNextItemWidth(m_gui_cfg->input_width); const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%": ((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%"); + if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) { - m_is_valid_result = false; if (m_configuration.decimate_ratio < 0.f) m_configuration.decimate_ratio = 0.01f; if (m_configuration.decimate_ratio > 100.f) m_configuration.decimate_ratio = 100.f; m_configuration.fix_count_by_ratio(triangle_count); + live_preview(); } ImGui::NewLine(); @@ -204,43 +203,48 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count); m_imgui->disabled_end(); // use_count - if (ImGui::Checkbox(_L("Show wireframe").c_str(), &m_show_wireframe)) { + if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) { if (m_show_wireframe) init_wireframe(); else free_gpu(); } - if (m_state == State::settings) { - if (m_imgui->button(_L("Cancel"))) { - if (m_original_its.has_value()) { + bool is_canceling = m_state == State::canceling; + m_imgui->disabled_begin(is_canceling); + if (m_imgui->button(_L("Cancel"))) { + if (m_state == State::settings) { + if (m_original_its.has_value()) { set_its(*m_original_its); m_state = State::close_on_end; } else { close(); } + } else { + m_state = State::canceling; } - ImGui::SameLine(m_gui_cfg->bottom_left_width); - if (m_imgui->button(_L("Preview"))) { - m_state = State::preview; - // simplify but not apply on mesh - process(); - } - ImGui::SameLine(); - if (m_imgui->button(_L("Apply"))) { - if (!m_is_valid_result) { - m_state = State::close_on_end; - process(); - } else if (m_exist_preview) { - // use preview and close - after_apply(); - } else { // no changes made - close(); - } - } - } else { - m_imgui->disabled_begin(m_state == State::canceling); - if (m_imgui->button(_L("Cancel"))) m_state = State::canceling; - m_imgui->disabled_end(); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling) + ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str()); + m_imgui->disabled_end(); // state canceling + ImGui::SameLine(); + + bool is_processing = m_state != State::settings; + m_imgui->disabled_begin(is_processing); + if (m_imgui->button(_L("Apply"))) { + if (!m_is_valid_result) { + m_state = State::close_on_end; + process(); + } else if (m_exist_preview) { + // use preview and close + after_apply(); + } else { // no changes made + close(); + } + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing) + ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str()); + m_imgui->disabled_end(); // state !settings + + // draw progress bar + if (is_processing) { // apply or preview ImGui::SameLine(m_gui_cfg->bottom_left_width); // draw progress bar char buf[32]; @@ -249,6 +253,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } m_imgui->end(); + // refresh view when needed if (m_need_reload) { m_need_reload = false; bool close_on_end = (m_state == State::close_on_end); @@ -278,6 +283,22 @@ void GLGizmoSimplify::close() { gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } +void GLGizmoSimplify::live_preview() { + m_is_valid_result = false; + if (m_state != State::settings) { + // already canceling process + if (m_state == State::canceling) return; + + // wait until cancel + if (m_worker.joinable()) { + m_state = State::canceling; + m_worker.join(); + } + } + + m_state = State::preview; + process(); +} void GLGizmoSimplify::process() { @@ -406,6 +427,7 @@ void GLGizmoSimplify::create_gui_cfg() { cfg.input_width = cfg.bottom_left_width * 1.5; cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2; cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5; + m_gui_cfg = cfg; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 230c62cee..7fcb9fb1e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -24,21 +24,25 @@ class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GL public: GLGizmoSimplify(GLCanvas3D& parent); virtual ~GLGizmoSimplify(); + bool on_esc_key_down(); protected: - virtual bool on_init() override; virtual std::string on_get_name() const override; - virtual void on_render() override; - virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual bool on_is_activable() const override; virtual bool on_is_selectable() const override { return false; } virtual void on_set_state() override; + // must implement + virtual bool on_init() override { return true;}; + virtual void on_render() override{}; + virtual void on_render_for_picking() override{}; + // GLGizmoPainterBase virtual void render_painter_gizmo() const override{ render_wireframe(); } private: void after_apply(); void close(); + void live_preview(); void process(); void set_its(indexed_triangle_set &its); void create_gui_cfg(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index b7a6d89fa..ccc67b630 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -673,7 +673,7 @@ RENDER_AGAIN: // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene // - take correct undo/redo snapshot after the user is done with moving the slider float initial_value = m_new_point_head_diameter; - m_imgui->slider_float("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); + m_imgui->slider_float("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); if (ImGui::IsItemClicked()) { if (m_old_point_head_diameter == 0.f) m_old_point_head_diameter = initial_value; @@ -733,7 +733,7 @@ RENDER_AGAIN: float density = static_cast(opts[0])->value; float minimal_point_distance = static_cast(opts[1])->value; - m_imgui->slider_float("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); + m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm"); bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider @@ -742,7 +742,7 @@ RENDER_AGAIN: m_imgui->text(m_desc.at("points_density")); ImGui::SameLine(settings_sliders_left); - m_imgui->slider_float(" ", &density, 0.f, 200.f, "%.f %%"); + m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%"); slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); @@ -802,7 +802,7 @@ RENDER_AGAIN: ImGui::SameLine(clipping_slider_left); ImGui::PushItemWidth(window_width - clipping_slider_left); float clp_dist = m_c->object_clipper()->get_position(); - if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f")) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c7434ab9f..09480492b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -550,7 +550,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // mouse anywhere if (evt.Moving()) { m_tooltip = update_hover_state(mouse_pos); - if (m_current == MmuSegmentation) + if (m_current == MmuSegmentation || m_current == FdmSupports) gizmo_event(SLAGizmoEventType::Moving, mouse_pos, evt.ShiftDown(), evt.AltDown()); } else if (evt.LeftUp()) { if (m_mouse_capture.left) { @@ -926,6 +926,10 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } default: { break; } } + } else if (m_current == Simplify && keyCode == WXK_ESCAPE) { + GLGizmoSimplify *simplify = dynamic_cast(get_current()); + if (simplify != nullptr) + processed = simplify->on_esc_key_down(); } } diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index fc410fce2..21aecd15b 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -919,7 +919,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp } if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - wxGetApp().open_preferences(2); + wxGetApp().open_preferences(2, "show_hints"); } ImGui::PopStyleColor(5); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 22c575f4c..d4c93de25 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -280,10 +280,10 @@ void ImGuiWrapper::render() m_new_frame_open = false; } -ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) +ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) const { auto text_utf8 = into_u8(text); - ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str()); + ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width); /*#ifdef __linux__ size.x *= m_style_scaling; @@ -293,6 +293,29 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) return size; } +ImVec2 ImGuiWrapper::calc_button_size(const wxString &text, const ImVec2 &button_size) const +{ + const ImVec2 text_size = this->calc_text_size(text); + const ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + + return ImGui::CalcItemSize(button_size, text_size.x + style.FramePadding.x * 2.0f, text_size.y + style.FramePadding.y * 2.0f); +} + +ImVec2 ImGuiWrapper::get_item_spacing() const +{ + const ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + return g.Style.ItemSpacing; +} + +float ImGuiWrapper::get_slider_float_height() const +{ + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + return g.FontSize + style.FramePadding.y * 2.0f + style.ItemSpacing.y; +} + void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) { ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); @@ -425,6 +448,42 @@ void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label) this->text_colored(color, label_utf8.c_str()); } +void ImGuiWrapper::text_wrapped(const char *label, float wrap_width) +{ + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + this->text(label); + ImGui::PopTextWrapPos(); +} + +void ImGuiWrapper::text_wrapped(const std::string &label, float wrap_width) +{ + this->text_wrapped(label.c_str(), wrap_width); +} + +void ImGuiWrapper::text_wrapped(const wxString &label, float wrap_width) +{ + auto label_utf8 = into_u8(label); + this->text_wrapped(label_utf8.c_str(), wrap_width); +} + +void ImGuiWrapper::tooltip(const char *label, float wrap_width) +{ + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextUnformatted(label); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); +} + +void ImGuiWrapper::tooltip(const wxString &label, float wrap_width) +{ + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextUnformatted(label.ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); +} + bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/) { bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 6d3d3218a..c81e9d3e3 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -52,7 +52,11 @@ public: float scaled(float x) const { return x * m_font_size; } ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } - ImVec2 calc_text_size(const wxString &text); + ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f) const; + ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const; + + ImVec2 get_item_spacing() const; + float get_slider_float_height() const; void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); @@ -78,6 +82,11 @@ public: void text_colored(const ImVec4& color, const char* label); void text_colored(const ImVec4& color, const std::string& label); void text_colored(const ImVec4& color, const wxString& label); + void text_wrapped(const char *label, float wrap_width); + void text_wrapped(const std::string &label, float wrap_width); + void text_wrapped(const wxString &label, float wrap_width); + void tooltip(const char *label, float wrap_width); + void tooltip(const wxString &label, float wrap_width); // Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true). bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index e86b37a25..e7ef13d7e 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -271,7 +271,7 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f sizer->AddStretchSpacer(); // logo - m_logo_bmp = ScalableBitmap(this, wxGetApp().is_editor() ? "PrusaSlicer_32px.png" : "PrusaSlicer-gcodeviewer_32px.png", 32); + m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 32); m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, m_logo_bmp.bmp()); sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 66e22c694..799c4e793 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1400,7 +1400,7 @@ void MainFrame::init_menubar_as_editor() } windowMenu->AppendSeparator(); - append_menu_item(windowMenu, wxID_ANY, _L("Modify Shapes Gallery"), _L("Open the dialog to modify shapes gallery"), + append_menu_item(windowMenu, wxID_ANY, _L("Shape Gallery"), _L("Open the dialog to modify shape gallery"), [this](wxCommandEvent&) { GalleryDialog dlg(this, true); if (dlg.ShowModal() == wxID_OK) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index ed6f11ddb..40f5c3116 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -43,6 +43,10 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat }, {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, + {NotificationType::NewAlphaAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New alpha release is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { + wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, + {NotificationType::NewBetaAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New beta release is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { + wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, {NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10, _u8L("You have just added a G-code for color change, but its value is empty.\n" "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, @@ -56,6 +60,7 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat _u8L("Undo desktop integration was successful.") }, {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, _u8L("Undo desktop integration failed.") }, + {NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") }, //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, //{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") }, //{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification @@ -210,7 +215,8 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init } if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { - ImGui::SetNextWindowFocus(); + // Uncomment if imgui window focus is needed on hover. I cant find any case. + //ImGui::SetNextWindowFocus(); set_hovered(); } @@ -1147,6 +1153,8 @@ bool NotificationManager::SlicingProgressNotification::set_progress_state(Notifi m_sp_state = state; return true; case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: + if (m_sp_state != SlicingProgressState::SP_BEGAN && m_sp_state != SlicingProgressState::SP_PROGRESS) + return false; set_percentage(1); m_has_cancel_button = false; m_has_print_info = false; @@ -1504,6 +1512,16 @@ void NotificationManager::push_notification(NotificationType type, int duration = get_standart_duration(level); push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp); } + +void NotificationManager::push_delayed_notification(const NotificationType type, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) +{ + auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), + boost::bind(&NotificationData::type, boost::placeholders::_1) == type); + assert(it != std::end(basic_notifications)); + if (it != std::end(basic_notifications)) + push_delayed_notification_data(std::make_unique(*it, m_id_provider, m_evt_handler), condition_callback, initial_delay, delay_interval); +} + void NotificationManager::push_validate_error_notification(const std::string& text) { push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); @@ -1907,7 +1925,7 @@ void NotificationManager::push_hint_notification(bool open_next) auto condition = [&self = std::as_const(*this)]() { return self.get_notification_count() == 0; }; - push_delayed_notification(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); + push_delayed_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); } } @@ -1970,7 +1988,7 @@ bool NotificationManager::push_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) +void NotificationManager::push_delayed_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) { if (initial_delay == 0 && condition_callback()) { if( push_notification_data(std::move(notification), 0)) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index ad2e315b7..2031586b8 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -52,6 +52,9 @@ enum class NotificationType // Notification on the start of PrusaSlicer, when a new PrusaSlicer version is published. // Contains a hyperlink to open a web browser pointing to the PrusaSlicer download location. NewAppAvailable, + // Like NewAppAvailable but with text and link for alpha / bet release + NewAlphaAvailable, + NewBetaAvailable, // Notification on the start of PrusaSlicer, when updates of system profiles are detected. // Contains a hyperlink to execute installation of the new system profiles. PresetUpdateAvailable, @@ -106,8 +109,10 @@ enum class NotificationType // Give user advice to simplify object with big amount of triangles // Contains ObjectID for closing when object is deleted SimplifySuggestion, - // information about netfabb is finished repairing model (blocking proccess) - NetfabbFinished + // information about netfabb is finished repairing model (blocking proccess) + NetfabbFinished, + // Short meesage to fill space between start and finish of export + ExportOngoing }; class NotificationManager @@ -148,6 +153,10 @@ public: // ErrorNotificationLevel and ImportantNotificationLevel are never faded out. void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", std::function callback = std::function(), int timestamp = 0); + // Pushes basic_notification with delay. See push_delayed_notification_data. + void push_delayed_notification(const NotificationType type, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); + // Removes all notifications of type from m_waiting_notifications + void stop_delayed_notifications_of_type(const NotificationType type); // Creates Validate Error notification with a custom text and no fade out. void push_validate_error_notification(const std::string& text); // Creates Slicing Error notification with a custom text and no fade out. @@ -696,10 +705,8 @@ private: // and condition callback is success, notification is regular pushed from update function. // Otherwise another delay interval waiting. Timestamp is 0. // Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push. - // Also note that no control of same notification is done during push_delayed_notification but if waiting notif fails to push, it continues waiting. - void push_delayed_notification(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); - // Removes all notifications of type from m_waiting_notifications - void stop_delayed_notifications_of_type(const NotificationType type); + // Also note that no control of same notification is done during push_delayed_notification_data but if waiting notif fails to push, it continues waiting. + void push_delayed_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); //finds older notification of same type and moves it to the end of queue. returns true if found bool activate_existing(const NotificationManager::PopNotification* notification); // Put the more important notifications to the bottom of the list. diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index f01a1e962..18553b9cf 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -72,6 +72,11 @@ void OG_CustomCtrl::init_ctrl_lines() const std::vector& og_lines = opt_group->get_lines(); for (const Line& line : og_lines) { + if (line.is_separator()) { + ctrl_lines.emplace_back(CtrlLine(0, this, line)); + continue; + } + if (line.full_width && ( // description line line.widget != nullptr || @@ -124,6 +129,15 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) line_height = win_height; }; + auto correct_horiz_pos = [this](int& h_pos, Field* field) { + if (m_max_win_width > 0 && field->getWindow()) { + int win_width = field->getWindow()->GetSize().GetWidth(); + if (dynamic_cast(field)) + win_width *= 0.5; + h_pos += m_max_win_width - win_width; + } + }; + for (CtrlLine& ctrl_line : ctrl_lines) { if (&ctrl_line.og_line == &line) { @@ -160,6 +174,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) h_pos += 3 * blinking_button_width; Field* field = opt_group->get_field(option_set.front().opt_id); correct_line_height(ctrl_line.height, field->getWindow()); + correct_horiz_pos(h_pos, field); break; } @@ -189,8 +204,10 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) } h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; - if (field == field_in) + if (field == field_in) { + correct_horiz_pos(h_pos, field); break; + } if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; @@ -361,6 +378,28 @@ void OG_CustomCtrl::correct_widgets_position(wxSizer* widget, const Line& line, } }; +void OG_CustomCtrl::init_max_win_width() +{ + if (opt_group->ctrl_horiz_alignment == wxALIGN_RIGHT && m_max_win_width == 0) + for (CtrlLine& line : ctrl_lines) { + if (int max_win_width = line.get_max_win_width(); + m_max_win_width < max_win_width) + m_max_win_width = max_win_width; + } +} + +void OG_CustomCtrl::set_max_win_width(int max_win_width) +{ + if (m_max_win_width == max_win_width) + return; + m_max_win_width = max_win_width; + for (CtrlLine& line : ctrl_lines) + line.correct_items_positions(); + + GetParent()->Layout(); +} + + void OG_CustomCtrl::msw_rescale() { #ifdef __WXOSX__ @@ -374,6 +413,8 @@ void OG_CustomCtrl::msw_rescale() m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize(); m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize(); + m_max_win_width = 0; + wxCoord v_pos = 0; for (CtrlLine& line : ctrl_lines) { line.msw_rescale(); @@ -407,6 +448,21 @@ OG_CustomCtrl::CtrlLine::CtrlLine( wxCoord height, } } +int OG_CustomCtrl::CtrlLine::get_max_win_width() +{ + int max_win_width = 0; + if (!draw_just_act_buttons) { + const std::vector