Merge branch 'master' into fs_emboss

# Conflicts:
#	src/slic3r/GUI/3DScene.cpp
This commit is contained in:
Filip Sykala 2021-10-19 13:40:19 +02:00
commit 6c8f754186
148 changed files with 4147 additions and 1605 deletions

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" enable-background="new 0 0 1024 1024" xml:space="preserve">
<circle fill="#FFFFFF" cx="512" cy="512" r="512"/>
<path fill="#333333" d="M308.97,650.05c0.72,0.89,1.44,1.77,2.17,2.65c0.47,0.57,0.94,1.13,1.42,1.69
c5.82,6.94,11.97,13.7,18.49,20.22l435.89-435.89c-21.69-21.69-45.83-39.46-71.5-53.33c-0.46-0.25-0.93-0.51-1.39-0.75
c-1.31-0.7-2.63-1.39-3.96-2.07c-0.58-0.3-1.16-0.59-1.73-0.88c-1.34-0.68-2.69-1.36-4.04-2.02c0,0,0,0,0,0l0,0
c-23.73-11.59-48.64-19.95-74.07-25.08l0,0c0,0,0,0,0,0c-4.86-0.98-9.74-1.84-14.63-2.59c0,0,0,0-0.01,0
c-2.44-0.37-4.89-0.71-7.34-1.03c0,0-0.01,0-0.01,0c-2.45-0.31-4.91-0.6-7.37-0.85l0,0l0,0c-8.26-0.85-16.54-1.39-24.83-1.58
c-1.96-0.04-3.92-0.06-5.87-0.07c-0.4,0-0.79-0.01-1.19-0.01c-0.3,0-0.61,0.01-0.91,0.01c-1.78,0.01-3.57,0.03-5.35,0.06
c-0.96,0.02-1.92,0.05-2.88,0.08c-1.03,0.03-2.05,0.07-3.08,0.11c-1.84,0.07-3.67,0.16-5.5,0.27c-0.56,0.03-1.12,0.07-1.68,0.1
c-15.09,0.95-30.12,2.99-44.97,6.15l0,0c0,0,0,0,0,0c-2.2,0.47-4.39,0.96-6.58,1.48c-0.14,0.03-0.28,0.06-0.42,0.1
c-2.11,0.5-4.22,1.03-6.33,1.57c-0.2,0.05-0.4,0.1-0.61,0.16c-2.07,0.54-4.14,1.11-6.2,1.7c-0.22,0.06-0.44,0.12-0.67,0.19
c-2.06,0.59-4.12,1.21-6.17,1.84c-0.21,0.06-0.42,0.13-0.63,0.19c-2.07,0.65-4.14,1.31-6.2,2c-0.18,0.06-0.35,0.12-0.53,0.18
c-2.09,0.71-4.18,1.43-6.26,2.18c-0.13,0.05-0.26,0.1-0.4,0.14c-2.12,0.77-4.24,1.56-6.35,2.38c-0.07,0.03-0.14,0.06-0.22,0.08
c-2.17,0.84-4.33,1.71-6.48,2.6c0,0,0,0,0,0l0,0c-33.3,13.84-64.67,33.76-92.4,59.79h0c0,0,0,0,0,0c-0.94,0.88-1.87,1.77-2.8,2.66
c-1.46,1.4-2.91,2.81-4.35,4.24c-1.43,1.43-2.84,2.88-4.23,4.34c-25.08,26.07-44.67,55.46-58.78,86.73v0c0,0,0,0,0,0
c-2.06,4.57-4.01,9.17-5.83,13.82c-0.18,0.45-0.36,0.91-0.53,1.36c-0.54,1.4-1.08,2.8-1.61,4.21c0,0,0,0,0,0l0,0
c-15.88,42.78-21.94,88.37-18.19,133.3c0,0,0,0,0,0l0,0c1.15,13.7,3.19,27.35,6.16,40.83c0.4,1.81,0.82,3.61,1.25,5.42
c0.19,0.79,0.38,1.57,0.57,2.36c0.41,1.64,0.83,3.28,1.26,4.92c0.27,1.03,0.55,2.05,0.84,3.07c0.23,0.82,0.46,1.64,0.7,2.46
c0.56,1.95,1.13,3.89,1.73,5.83c0.12,0.39,0.24,0.78,0.36,1.17c0.79,2.54,1.61,5.07,2.47,7.59c0,0,0,0,0,0l0,0
c10.76,31.64,26.79,61.88,48.12,89.34C306.53,647,307.74,648.53,308.97,650.05z M346.52,261.4l26.16,17.44v51.62l-26.16,17.44
l-26.16,17.44l-26.16-17.44l-9.53-6.35c13.53-31.12,32.34-59.34,56-84.05L346.52,261.4z M549.89,168.46l9.76,6.51v51.62
l-26.16,17.44l-26.16,17.44l-46.15-30.77l-6.17-4.11v-42.45c30.19-10.41,61.74-15.69,93.99-15.69
C549.29,168.44,549.59,168.45,549.89,168.46z M641.98,330.46v-51.62l26.16-17.44l26.16-17.44l23.46,15.64l-72.83,72.83
L641.98,330.46z M509.79,467.58l-2.46,1.64L455,434.34v-51.62l10.98-7.32l41.34-27.56l26.16,17.44l26.16,17.44v35L509.79,467.58z
M330.35,642.09v-51.62l26.16-17.44l26.16-17.44l23.46,15.64l-72.83,72.83L330.35,642.09z M418.84,555.65l-26.16-17.44v-51.62
l26.16-17.44L445,451.71l26.16,17.44l22.23,14.82l-72.83,72.83L418.84,555.65z M621.98,330.46l-26.16,17.44l-26.16,17.44
l-52.32-34.88v-51.62l52.32-34.88l26.16,17.44l26.16,17.44V330.46z M624.65,352.72l-45,45v-15l26.16-17.44L624.65,352.72z
M445,243.96l26.16,17.44l26.16,17.44v51.62l-26.16,17.44L445,365.34l-26.16-17.44l-26.16-17.44v-51.62l26.16-17.44L445,243.96z
M408.84,365.27L435,382.72v51.62l-52.32,34.88l-26.16-17.44l-26.16-17.44v-51.62l26.16-17.44l26.16-17.44L408.84,365.27z
M346.52,469.15l26.16,17.44v51.62l-26.16,17.44l-26.16,17.44l-26.16-17.44l-20.57-13.72c-2.12-6.87-3.99-13.8-5.59-20.79v-34.56
l26.16-17.44l26.16-17.44L346.52,469.15z M310.35,590.47v27.94c-9.53-14.02-17.77-28.84-24.69-44.4L310.35,590.47z M732.19,245.18
l-27.89-18.59v-12.8c11.86,7.6,23.16,16.08,33.87,25.42L732.19,245.18z M657.46,189.55c9.2,3.73,18.15,7.91,26.85,12.53v24.51
l-26.16,17.44l-26.16,17.44l-26.16-17.44l-26.16-17.44v-51.62l6.25-4.17C610.43,173.93,634.38,180.19,657.46,189.55z M435,191.87
v34.72l-26.16,17.44l-26.16,17.44l-26.16-17.44l-1.21-0.81C378.99,221.68,405.73,204.45,435,191.87z M284.19,365.27l26.16,17.44
v51.62l-49.37,32.92c-0.13-3.52-0.21-7.05-0.21-10.59c0-32.99,5.52-65.24,16.41-96.07L284.19,365.27z"/>
<path fill="#ED6B21" d="M759.84,687.31c0.31-0.74,0.63-1.49,0.94-2.23c0.51-1.24,1.01-2.48,1.51-3.72c0.3-0.75,0.59-1.5,0.88-2.26
c15.37-39.59,22.26-81.7,20.67-123.56c-0.02-0.42-0.03-0.85-0.05-1.28c-0.08-1.87-0.18-3.74-0.29-5.61
c-0.01-0.19-0.02-0.38-0.03-0.57c-0.12-1.99-0.27-3.98-0.43-5.96c0,0,0,0,0,0l0,0c-1.24-15.16-3.59-30.24-7.06-45.12l0,0
c0,0,0,0,0,0c-0.58-2.49-1.19-4.98-1.84-7.46c0,0,0,0,0,0c-0.64-2.47-1.31-4.92-2.01-7.38c-0.01-0.02-0.01-0.05-0.02-0.07
c-0.7-2.43-1.43-4.86-2.18-7.28c-0.01-0.04-0.02-0.07-0.03-0.11c-0.77-2.44-1.56-4.87-2.39-7.3c0,0,0,0,0,0l0,0
c-10.8-31.61-26.88-61.81-48.25-89.23c-1.1-1.41-2.22-2.81-3.35-4.21c-0.8-0.99-1.61-1.98-2.43-2.96c-0.44-0.53-0.88-1.05-1.32-1.57
c-5.77-6.88-11.87-13.57-18.34-20.04l-5.16,5.16l0,0l-14.42,14.42l0,0L590.4,452.8l-3.42,3.42l0,0l-60.3,60.3l0,0l-20,20l0,0
l-2.64,2.64l0,0l-30.11,30.11l-96.89,96.89l0,0l-10.84,10.84l-76.42,76.42h0l-13.83,13.83l-18.03,18.03
c18.94,18.94,39.73,34.89,61.78,47.87c1.78,1.05,3.56,2.07,5.36,3.08c0.86,0.48,1.72,0.95,2.58,1.42c1.13,0.62,2.26,1.24,3.4,1.84
c0.89,0.47,1.78,0.95,2.67,1.41c1.25,0.65,2.5,1.28,3.75,1.91c28.95,14.55,59.72,24.25,91.1,29.1c0,0,0,0,0,0
c2.44,0.38,4.88,0.72,7.32,1.04c0,0,0.01,0,0.01,0c2.45,0.32,4.89,0.61,7.34,0.87c0,0,0,0,0,0l0,0c8.87,0.94,17.78,1.5,26.69,1.67
c1.44,0.03,2.87,0.04,4.31,0.05c0.54,0,1.08,0.02,1.62,0.02c0.41,0,0.83-0.01,1.24-0.01c1.49-0.01,2.97-0.02,4.46-0.05
c1.11-0.02,2.23-0.05,3.34-0.08c0.66-0.02,1.31-0.05,1.97-0.07c2.2-0.08,4.4-0.18,6.59-0.3c0.26-0.02,0.52-0.03,0.79-0.05
c15.01-0.89,29.97-2.88,44.75-5.95l0,0c0,0,0,0,0,0c2.3-0.48,4.61-0.99,6.9-1.52c0.04-0.01,0.08-0.02,0.12-0.03
c2.26-0.53,4.52-1.08,6.77-1.66c0.08-0.02,0.15-0.04,0.23-0.06c2.22-0.57,4.43-1.17,6.64-1.79c0.11-0.03,0.21-0.06,0.32-0.09
c2.18-0.62,4.36-1.26,6.54-1.93c0.13-0.04,0.25-0.08,0.38-0.12c2.16-0.67,4.31-1.35,6.46-2.07c0.13-0.04,0.26-0.09,0.39-0.13
c2.15-0.72,4.29-1.46,6.42-2.22c0.12-0.04,0.24-0.09,0.35-0.13c2.15-0.78,4.3-1.57,6.44-2.4c0.08-0.03,0.15-0.06,0.23-0.09
c37.07-14.33,71.92-36.1,102.27-65.29c1.46-1.39,2.91-2.8,4.34-4.23c1.43-1.43,2.84-2.89,4.24-4.35c0.9-0.94,1.8-1.88,2.69-2.83l0,0
l0,0C726.34,750.82,746.03,720.02,759.84,687.31z M739.81,658.73l-26.16-17.44v-51.62l26.16-17.44l24.04-16.03
c0.14,3.71,0.23,7.42,0.23,11.14c0,33.15-5.58,65.56-16.57,96.52L739.81,658.73z M474.09,855.53l-9.75-6.5v-51.62l26.16-17.44
l26.16-17.44l26.16,17.44L569,797.41v42.74c-29.93,10.21-61.19,15.4-93.13,15.4C475.27,855.56,474.68,855.54,474.09,855.53z
M516.69,554.79l26.14,17.43L569,589.66v51.62l-26.16,17.44l-26.16,17.44l-26.16-17.44l-26.16-17.44v-34.15L516.69,554.79z
M579,658.66l26.16,17.44l26.16,17.44v51.62l-26.16,17.44L579,780.04l-26.16-17.44l-26.16-17.44v-51.62l26.16-17.44L579,658.66z
M402.02,693.54l26.16-17.44l26.16-17.44l26.16,17.44l26.16,17.44v51.62l-26.16,17.44l-26.16,17.44l-26.16-17.44l-26.16-17.44
V693.54z M401.91,669.58l42.44-42.44v14.15l-26.16,17.44L401.91,669.58z M589,797.41l26.16-17.44l26.16-17.44l26.16,17.44l1.71,1.14
c-23.82,21.6-50.73,38.83-80.2,51.38V797.41z M615.16,658.73L589,641.28v-51.62l26.16-17.44l26.16-17.44l26.16,17.44l26.16,17.44
v51.62l-26.16,17.44l-26.16,17.44L615.16,658.73z M677.48,554.85l-26.16-17.44v-51.62l26.16-17.44l26.16-17.44l26.16,17.44
l21.65,14.43c1.66,5.43,3.18,10.89,4.51,16.4v38.23l-26.16,17.44l-26.16,17.44L677.48,554.85z M739.55,450.8l-25.9-17.27v-29.18
C723.7,418.98,732.35,434.49,739.55,450.8z M693.65,381.91v51.62l-26.16,17.44l-26.16,17.44l-22.95-15.3l72.83-72.83L693.65,381.91z
M605.16,468.35l26.16,17.44v51.62l-26.16,17.44L579,572.29l-26.16-17.44l-21.72-14.48l72.83-72.83L605.16,468.35z M379.58,691.91
l2.44,1.63v51.62l-26.16,17.44l-26.16,17.44l-22.95-15.3L379.58,691.91z M292.32,779.16l27.38,18.25v12.26
c-11.55-7.46-22.56-15.75-33.01-24.87L292.32,779.16z M339.7,821.46v-24.04l26.16-17.44l26.16-17.44l26.16,17.44l26.16,17.44v51.62
l-6.11,4.07c-24.28-3.16-47.99-9.39-70.84-18.65C357.9,830.6,348.66,826.26,339.7,821.46z M677.48,762.6l-26.16-17.44v-51.62
l26.16-17.44l26.16-17.44l26.16,17.44l10.18,6.79c-13.58,31.09-32.43,59.28-56.14,83.96L677.48,762.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 374 B

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="19" height="19" version="1.1" viewBox="0 0 19 19" xmlns="http://www.w3.org/2000/svg">
<path id="rectangle_transparent" d="m0 0h19v19h-19z" fill-opacity="0"/>
</svg>

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -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);
}

View File

@ -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.

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

BIN
resources/shapes/cone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
resources/shapes/cone.stl Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

BIN
resources/shapes/torus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
resources/shapes/torus.stl Normal file

Binary file not shown.

View File

@ -157,6 +157,19 @@ bool PolyNode::IsHole() const
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();
}
}
//------------------------------------------------------------------------------

View File

@ -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<DoublePoint> 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;

View File

@ -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
@ -126,6 +123,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())
set("use_environment_map", "0");
@ -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

View File

@ -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);

View File

@ -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<typename PathsProvider>
static ClipperLib::Paths safety_offset(PathsProvider &&paths)
// Offset CCW contours outside, CW contours (holes) inside.
// Don't calculate union of the output paths.
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
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<typename PathsProvider>
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<PathsProvider>(input), joinType, endType);
ClipperLib::Paths retval;
co.Execute(retval, delta_scaled);
return raw_offset(std::forward<PathsProvider>(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit);
}
template<class TResult, class TSubj, class TClip>
TResult clipper_do(
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<TClip>(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<class TResult, class TSubj, class TClip>
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<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
}
template<class TResult, class TSubj>
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<TSubj>(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<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit);
}
template<class TResult, typename PathsProvider>
static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
return clipper_union<TResult>(raw_offset(std::forward<PathsProvider>(paths), offset, joinType, miterLimit));
}
// used by shrink_paths()
template<class Container> static void remove_outermost_polygon(Container & solution);
template<> void remove_outermost_polygon<ClipperLib::Paths>(ClipperLib::Paths &solution)
{ if (! solution.empty()) solution.erase(solution.begin()); }
template<> void remove_outermost_polygon<ClipperLib::PolyTree>(ClipperLib::PolyTree &solution)
{ solution.RemoveOutermostPolygon(); }
template<class TResult, typename PathsProvider>
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<PathsProvider>(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<class TResult, typename PathsProvider>
static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset != 0);
return offset > 0 ?
expand_paths<TResult>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit) :
shrink_paths<TResult>(std::forward<PathsProvider>(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<ClipperLib::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<ClipperLib::PolyTree>(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<ClipperLib::Paths>(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<ClipperLib::Paths>(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)
@ -275,13 +355,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
} 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()) {
if (auto output = clipper_do<ClipperLib::Paths>(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<typename ExPolygonVector>
ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
static std::pair<ClipperLib::Paths, size_t> 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<typename ExPolygonVector>
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<ClipperLib::Paths>(output) :
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
}
output;
}
return output;
// See comment on expolygons_offset_raw. In addition, the polygons are always united to conver to polytree.
template<typename ExPolygonVector>
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<ClipperLib::PolyTree>(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<ClipperLib::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<ClipperLib::PolyTree>(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<ClipperLib::PolyTree>(expolygons_offset(surfaces, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
template<class TResult, class TSubj, class TClip>
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<TSubj>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<TClip>(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<ClipperLib::Paths>(expand_paths<ClipperLib::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<ClipperLib::PolyTree>(expand_paths<ClipperLib::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<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
template<class TResult, class TSubj, class TClip>
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<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
assert(delta1 > 0);
assert(delta2 > 0);
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::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<ClipperLib::Paths>(shrink_paths<ClipperLib::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<ClipperLib::Paths>(shrink_paths<ClipperLib::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<typename PathProvider1, typename PathProvider2>
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<PathProvider1>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<PathProvider2>(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<ClipperLib::Paths>(clipType, subject, clip, fillType); ! output.empty())
// Perform an additional Union operation to generate the PolyTree ordering.
return clipper_union<ClipperLib::PolyTree>(output, fillType);
return ClipperLib::PolyTree();
}
template<typename PathProvider1, typename PathProvider2>
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<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
}
template<class TSubj, class TClip>
static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset)
{
return to_polygons(_clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset));
return to_polygons(clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(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 <typename TSubject, typename TClip>
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<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); }
{ return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward<TSubject>(subject), std::forward<TClip>(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<typename PathsProvider1, typename PathsProvider2>
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::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
}
ClipperLib::PolyTree union_pt(const ExPolygons &subject)
{
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
return clipper_do<ClipperLib::PolyTree>(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;
}

View File

@ -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<typename PathType>
class PathsProvider {
public:
PathsProvider(const std::vector<PathType> &paths) : m_paths(paths) {}
struct iterator : public PathsProviderIteratorBase {
public:
explicit iterator(typename std::vector<PathType>::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<PathType>::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<PathType> &m_paths;
};
template<typename MultiPointType>
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);

View File

@ -252,11 +252,11 @@ std::vector<SurfaceFill> 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;

View File

@ -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);

View File

@ -11,6 +11,7 @@
#include <numeric>
#include <unordered_set>
#include <boost/range/adaptor/reversed.hpp>
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 &center, 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<ClosestLine> closest_lines;
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> closest_lines_set;
double max_distance_squared = std::numeric_limits<double>::max();
};
// Returns sorted list of closest lines to a passed point within a passed radius
static std::vector<ClosestLine> get_closest_lines_in_radius(const EdgeGrid::Grid &grid, const Point &center, 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(), [&center](const auto &l, const auto &r) {
return (center - l.point).template cast<double>().squaredNorm() < (center - r.point).template cast<double>().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<Intersection> extend_for_closest_lines(const std::vector<Intersection> &intersections,
const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start,
const Point &end,
const float search_radius)
{
const std::vector<ClosestLine> start_lines = get_closest_lines_in_radius(boundary.grid, start, search_radius);
const std::vector<ClosestLine> 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<float>().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<size_t, size_t> {
std::unordered_set<size_t> 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<size_t>::max(), std::numeric_limits<size_t>::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<size_t, size_t> cl_indices = endpoints_close_to_same_boundary();
if (cl_indices.first != std::numeric_limits<size_t>::max()) {
assert(cl_indices.second != std::numeric_limits<size_t>::max());
const ClosestLine &cl_start = start_lines[cl_indices.first];
const ClosestLine &cl_end = end_lines[cl_indices.second];
std::vector<Intersection> 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<ClosestLine> &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<float>().squaredNorm();
if (cl.border_idx == intersection.border_idx && old_dist <= (search_radius * search_radius) &&
(close_to - cl.point).cast<float>().squaredNorm() < old_dist)
return &cl - &closest_lines.front();
}
return std::numeric_limits<size_t>::max();
};
// Try to find ClosestLine with same boundary_idx as any existing Intersection
auto find_closest_line_with_same_boundary_idx = [](const std::vector<ClosestLine> & closest_lines,
const std::vector<Intersection> &intersections, const bool reverse) -> size_t {
std::unordered_set<size_t> 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<size_t>::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<size_t>::max();
};
std::vector<Intersection> 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<size_t>::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<size_t>::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<size_t>::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<size_t>::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<bool forward>
static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point)
@ -268,10 +453,63 @@ static std::vector<TravelPoint> 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<unsigned int> 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<TravelPoint> &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<double>().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<TravelPoint> 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<double>();
Vec2d intersection_vec = (intersection.point - start).cast<double>();
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<TravelPoint> 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<unsigned int> 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<const SupportLayer *>(&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<coord_t>(), endf.cast<coord_t>(), result_pl);
travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), *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<coord_t>(), endf.cast<coord_t>(), result_pl);
travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), *gcodegen.layer(), result_pl);
result_pl.points.front() = start;
result_pl.points.back() = end;
}

View File

@ -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<std::vector<float>> boundaries_params;
// Used for detection of intersection between line and any polygon from boundaries
EdgeGrid::Grid grid;
EdgeGrid::Grid grid;
void clear()
{

View File

@ -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)) {

View File

@ -41,6 +41,8 @@ private:
// X,Y,Z,E,F
std::vector<char> m_axis;
std::vector<float> 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<unsigned int> m_extruder_ids;

View File

@ -720,6 +720,9 @@ void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor)
void GCodeProcessor::Result::reset() {
moves = std::vector<GCodeProcessor::MoveVertex>();
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<std::string>();
@ -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<std::string>();
@ -883,6 +889,10 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
const ConfigOptionFloatOrPercent* first_layer_height = config.option<ConfigOptionFloatOrPercent>("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<ConfigOptionFloatOrPercent>("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<ConfigOptionFloat>("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::function<voi
cancel_callback();
}
this->process_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<Vec3f> 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);

View File

@ -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<size_t> 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<std::string> extruder_colors;

View File

@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print)
std::vector<float> 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
};

View File

@ -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;
}
}

View File

@ -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<class T>
bool contains(const std::vector<T> &vector, const Point &point)
{

View File

@ -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<class T> bool contains(const std::vector<T> &vector, const Point &point);
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
double rad2deg_dir(double angle);

View File

@ -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<typename LayerContainer>

View File

@ -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);
}

View File

@ -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;

View File

@ -40,23 +40,42 @@ template<class L> auto get_b(L &&l) { return Traits<remove_cvref_t<L>>::get_b(l)
// Distance to the closest point of line.
template<class L>
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point)
{
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
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<double>().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<double>().squaredNorm();
}
*nearest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
return (t * v - va).squaredNorm();
}
// Distance to the closest point of line.
template<class L>
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
{
Vec<Dim<L>, Scalar<L>> nearest_point;
return distance_to_squared<L>(line, point, &nearest_point);
}
template<class L>
double distance_to(const L &line, const Vec<Dim<L>, Scalar<L>> &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;

View File

@ -47,7 +47,7 @@ private:
public:
// Forwarded constructor
template<class... Args>
inline CachedObject(Setter fn, Args &&... args)
inline CachedObject(Setter &&fn, Args &&... args)
: m_obj(std::forward<Args>(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<void()> fn)
template<class Fn> void invalidate(Fn &&fn)
{
std::lock_guard<SpinMutex> lck(m_lck);
fn();

View File

@ -159,8 +159,9 @@ template<class _Mesh> 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)

View File

@ -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,6 +359,15 @@ BoundingBoxf3 Model::bounding_box() const
return bb;
}
#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;
@ -337,6 +375,7 @@ unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume)
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<float>(), 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
{

View File

@ -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();

View File

@ -190,8 +190,7 @@ static inline std::vector<ColoredLine> to_lines(const std::vector<std::vector<Co
return lines;
}
// Double vertex equal to a coord_t point after conversion to double.
static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Point &ipt)
static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Vec2d &ipt)
{
// Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare.
// This should work with any settings of math compiler switches and the C++ compiler
@ -199,11 +198,11 @@ static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const
using ulp_cmp_type = boost::polygon::detail::ulp_comparison<double>;
ulp_cmp_type ulp_cmp;
static constexpr int ULPS = boost::polygon::voronoi_diagram_traits<double>::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<double>::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<double>::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<size_t> 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<CPoint, CPointAccessor> 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<double>::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<std::pair<const CPoint *, double>> all_closes_c_points = closest_voronoi_point.find_all(vertex_point);
int merge_to_point = -1;
for (const std::pair<const CPoint *, double> &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::vector<std::vector<Col
MMU_Graph graph;
graph.nodes.reserve(points.size() + vd.vertices().size());
for (const Point &point : points)
graph.nodes.push_back({point});
graph.nodes.push_back({Vec2d(double(point.x()), double(point.y()))});
graph.add_contours(color_poly);
init_polygon_indices(graph, color_poly, lines_colored);
@ -984,8 +1004,10 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
}
} else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
mark_processed(edge_it);
Point real_v0 = graph.nodes[edge_it->vertex0()->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::vector<std::vector<Col
graph.append_edge(edge_it->vertex1()->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<std::vector<Col
return graph;
}
static inline Polygon to_polygon(const Lines &lines)
static inline Polygon to_polygon(const std::vector<Linef> &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<std::pair<Polygon, size_t>> extract_colored_segments(const MM
{
std::vector<bool> 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<std::pair<const MMU_Graph::Arc *, double>> 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<std::pair<Polygon, size_t>> 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<double>().normalized();
Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).cast<double>().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<std::pair<Polygon, size_t>> 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<Linef> 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<std::pair<Polygon, size_t>> 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<double>().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<double>();
Vec2d second_line_vec = (second_line.b - second_line.a).cast<double>();
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<double>().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<size_t, size_t> &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<std::pair<MMU_Graph::Arc *, double>> arc_to_check;
@ -1214,7 +1237,7 @@ static void cut_segmented_layers(const std::vector<ExPolygons>
const std::function<void()> &throw_on_cancel_callback)
{
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()),[&](const tbb::blocked_range<size_t>& range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &throw_on_cancel_callback](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
std::vector<std::pair<ExPolygon, size_t>> segmented_regions_cuts;
@ -1366,7 +1389,8 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
return out;
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(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<size_t> &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<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(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<size_t> &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<std::vector<std::pair<ExPolygon, size_t>>> merge_segmented_la
std::vector<std::vector<std::pair<ExPolygon, size_t>>> segmented_regions_merged(segmented_regions.size());
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()), [&](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
for (const std::pair<ExPolygon, size_t> &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<std::vector<ColoredLine>> &colored_polygons)
{
assert(!colored_polygons.empty());
assert(!colored_polygons.front().empty());
int first_line_color = colored_polygons.front().front().color;
for (const std::vector<ColoredLine> &colored_polygon : colored_polygons)
for (const ColoredLine &colored_line : colored_polygon)
if (first_line_color != colored_line.color)
return false;
return true;
}
std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback)
{
std::vector<std::vector<std::pair<ExPolygon, size_t>>> segmented_regions(print_object.layers().size());
@ -1539,7 +1575,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> 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<size_t>(0, layers.size()), [&](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, layers.size()), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &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<std::vector<std::pair<ExPolygon, size_t>>> 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<PaintedLine> &pl) { return !pl.empty(); });
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, print_object.layers().size()), [&](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, print_object.layers().size()), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &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<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
if (!painted_lines_single.empty()) {
std::vector<std::vector<ColoredLine>> 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<std::pair<Polygon, size_t>> segmentation = extract_colored_segments(graph);
for (std::pair<Polygon, size_t> &region : segmentation)
segmented_regions[layer_idx].emplace_back(std::move(region));
std::vector<std::pair<Polygon, size_t>> segmentation = extract_colored_segments(graph);
for (std::pair<Polygon, size_t> &region : segmentation)
segmented_regions[layer_idx].emplace_back(std::move(region));
}
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS
{

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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");

View File

@ -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

View File

@ -393,7 +393,7 @@ static std::vector<std::vector<ExPolygons>> 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);
}
}

View File

@ -186,8 +186,8 @@ static std::vector<SupportPointGenerator::MyLayer> 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);

View File

@ -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());

View File

@ -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<PrintObjectSupportMaterial::SupporLayerType> 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<Polygons> 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<float>(m_support_material_closing_radius);
auto smoothing_distance = scaled<float>(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<coord_t>(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<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */
@ -1325,6 +1388,7 @@ std::vector<Polygons> 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<Polygons, Polygons, Polygons, float> detect_overhangs(
const Layer &layer,
const size_t layer_id,
@ -1410,9 +1476,9 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
overhang_polygons = to_polygons(layer.lslices);
#endif
// Expand for better stability.
contact_polygons = offset(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
contact_polygons = expand(overhang_polygons, scaled<float>(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<Polygons, Polygons, Polygons, float> 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<Polygons, Polygons, Polygons, float> 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<Polygons, Polygons, Polygons, float> 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<Polygons, Polygons, Polygons, float> 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<Polygons, Polygons, Polygons, float> 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<Polygons, Polygons, Polygons, float> 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<Polygons, Polygons, Polygons, float> 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<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupportMaterial::MyLayer*> new_contact_layer(
const PrintConfig &print_config,
const PrintObjectConfig &object_config,
@ -1630,15 +1703,18 @@ static inline std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupport
// Don't want to print a layer below the first layer height as it may not stick well.
//FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
// and it may actually make sense to do it with a thinner layer than the first layer height.
const coordf_t min_print_z = slicing_params.raft_layers() > 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<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupportMaterial::MyLayer*>(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<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupport
coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object;
if (bridging_print_z >= 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<coord_t>(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<Polygons>(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<Polygons>(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::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
m_object_config->support_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<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
interface_layers.assign(intermediate_layers.size(), nullptr);
if (num_base_interface_layers_top || num_base_interface_layers_bottom)
base_interface_layers.assign(intermediate_layers.size(), nullptr);
auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5;
auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density;
auto closing_distance = smoothing_distance; // scaled<float>(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<int>(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<int>& range) {
snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& 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<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
//FIXME maybe this adds one interface layer in excess?
if (top_contact_layer.bottom_z - EPSILON > top_z)
break;
polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, 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<ExtrusionEntityCollection> eec;
if (no_sort) {
@ -3430,9 +3544,9 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
Polygons loop_polygons = loops0;
for (int i = 1; i < n_contact_loops; ++ i)
polygons_append(loop_polygons,
offset2(
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<float> angles;
angles.push_back(base_angle);
if (support_pattern == smpRectilinearGrid)
angles.push_back(interface_angle);
std::vector<float> 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<size_t>(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<size_t>& 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<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(ipRectilinear));
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.interface_fill_pattern));
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(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<LayerCache> 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<size_t>(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<size_t>& 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>(Fill::new_from_type(fill_type_interface));
auto filler_interface = std::unique_ptr<Fill>(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<Fill>(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<Fill>(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<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear));
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
auto filler_base_interface = std::unique_ptr<Fill>(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>(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).

View File

@ -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

View File

@ -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_

View File

@ -208,7 +208,7 @@ void name_tbb_thread_pool_threads_set_locale()
nthreads = 1;
#endif
std::atomic<size_t> 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<size_t>(0, nthreads, 1),
[&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) {
assert(range.begin() + 1 == range.end());
if (nthreads_running.fetch_add(1) + 1 == nthreads) {
if (std::unique_lock<std::mutex> 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<std::mutex> lk(cv_m);
cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;});
}
auto thread_id = std::this_thread::get_id();

View File

@ -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<float>();
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<double>());
}
if (p2.z() >= world_min_z)
bbox.merge(p2.cast<double>());
iprev = iedge;
}
}
return bbox;
}
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
TriangleMesh TriangleMesh::convex_hull_3d() const
{
// The qhull call:

View File

@ -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<double>(); }
/// Return the center of the related bounding box.

View File

@ -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;

View File

@ -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<float>();
// Now start with the facet the pointer points to and check all adjacent facets.
std::vector<int> 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<int> 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<float>();
// 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()));

View File

@ -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

View File

@ -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)
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()
if(miniz_FOUND)
target_link_libraries(miniz INTERFACE miniz_static)
target_include_directories(miniz INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
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...")
endif()

View File

@ -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 <GL/glew.h>
@ -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<double> 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::EType, std::string, std::string> 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::EType, std::string, std::string> 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

View File

@ -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);

View File

@ -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 <Eigen/Dense>
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
#include <libqhullcpp/Qhull.h>
#include <libqhullcpp/QhullFacetList.h>
#include <libqhullcpp/QhullVertexSet.h>
#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<size_t, size_t>& tverts_range,
const std::pair<size_t, size_t>& 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<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
@ -463,9 +467,15 @@ std::array<float, 4> 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<BoundingBoxf3>* trans_box = const_cast<std::optional<BoundingBoxf3>*>(&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<Vec3f>& vertices = mesh.its.vertices;
// The qhull call:
orgQhull::Qhull qhull;
qhull.disableOutputStream(); // we want qhull to be quiet
std::vector<realT> 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<Vec3f> dst_vertices;
std::vector<Vec3i> dst_facets;
// Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices).
std::vector<int> 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<int> GLVolumeCollection::load_object(
const ModelObject *model_object,
int obj_idx,
@ -754,6 +881,9 @@ int GLVolumeCollection::load_wipe_tower_preview(
volumes.emplace_back(new GLVolume(color));
GLVolume& v = *volumes.back();
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<int>(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<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
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<float>(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<double>(bed_box_2D.min.x()), unscale<double>(bed_box_2D.min.y()), -1e10 },
{ unscale<double>(bed_box_2D.max.x()), unscale<double>(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<float>(), 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<float>(), 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<double>(bed_box_2D.min.x()), unscale<double>(bed_box_2D.min.y()), 0.0 },
{ unscale<double>(bed_box_2D.max.x()), unscale<double>(bed_box_2D.max.y()),
config->opt_float("max_print_height") });
{ unscale<double>(bed_box_2D.max.x()), unscale<double>(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, &center, &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<Color> 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;
}
}

View File

@ -41,7 +41,6 @@ enum ModelInstanceEPrintVolumeState : unsigned char;
// Return appropriate color based on the ModelVolume.
std::array<float, 4> 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<const TriangleMesh> m_convex_hull;
// Bounding box of this volume, in unscaled coordinates.
std::optional<BoundingBoxf3> 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<BoundingBoxf3> 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<GLVolume*> 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<float, 4> data;
// [0] = min z, [1] = max z
std::array<float, 2> 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);

View File

@ -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);

View File

@ -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, &center, &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();
@ -84,6 +113,7 @@ BedShape::BedShape(const ConfigOptionPoints& points)
// 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<size_t>(m_type == Bed3D::EShapeType::Invalid ? Bed3D::EShapeType::Rectangle : m_type);
#else
return static_cast<size_t>(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<ConfigOptionsGroup>(panel, _(L("Settings")));
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(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<ConfigOptionsGroup>(panel, _(L("Texture")));
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(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<wxStaticText*>(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<ConfigOptionsGroup>(panel, _(L("Model")));
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(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<wxStaticText*>(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<Bed3D::EShapeType>(page_idx);
#else
BedShape::Type page_type = static_cast<BedShape::Type>(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<Vec2d>(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<double>(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;
}

View File

@ -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 <wx/dialog.h>
@ -19,12 +22,14 @@ using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
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};

View File

@ -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<bool(Layer*)> 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())

View File

@ -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<bool(Layer*)> break_condition);
// custom message the slider sends to its parent to notify a tick-change:
wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);

View File

@ -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 <imgui/imgui_internal.h>
#include <GL/glew.h>
@ -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<Color>();
m_extruders_count = 0;
m_extruder_ids = std::vector<unsigned char>();
@ -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<double>();
print_volume.max += Vec3f(BedEpsilon, BedEpsilon, 0.0f).cast<double>();
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(), &center, &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();

View File

@ -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<Color> m_tool_colors;
Layers m_layers;
std::array<unsigned int, 2> m_layers_z_range;
@ -804,6 +807,10 @@ private:
std::vector<CustomGCode::Item> 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)

Some files were not shown because too many files have changed in this diff Show More