Merge branch 'master' into fs_emboss
# Conflicts: # src/slic3r/GUI/3DScene.cpp
73
resources/icons/PrusaSlicer-gcodeviewer.svg
Normal 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 |
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 374 B |
Before Width: | Height: | Size: 600 B |
Before Width: | Height: | Size: 695 B |
Before Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 651 B |
Before Width: | Height: | Size: 93 B |
4
resources/icons/mirroring_transparent.svg
Normal 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 |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 158 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 164 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 158 B |
Before Width: | Height: | Size: 1001 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 997 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 654 B |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 803 B |
Before Width: | Height: | Size: 465 B |
Before Width: | Height: | Size: 650 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 9.8 KiB |
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
106
resources/shaders/gouraud_mod.fs
Normal 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);
|
||||
}
|
73
resources/shaders/gouraud_mod.vs
Normal 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);
|
||||
}
|
55
resources/shaders/mm_gouraud.fs
Normal 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);
|
||||
}
|
23
resources/shaders/mm_gouraud.vs
Normal 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);
|
||||
}
|
BIN
resources/shapes/M3_hex_nut.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
resources/shapes/M3_hex_nut.stl
Normal file
BIN
resources/shapes/M3x10_screw.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
resources/shapes/M3x10_screw.stl
Normal file
BIN
resources/shapes/cone.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
resources/shapes/cone.stl
Normal file
BIN
resources/shapes/helper_disk.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
resources/shapes/helper_disk.stl
Normal file
BIN
resources/shapes/torus.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
resources/shapes/torus.stl
Normal 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();
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 ¢er, double max_distance_squared)
|
||||
: grid(grid), center(center), max_distance_squared(max_distance_squared)
|
||||
{}
|
||||
|
||||
void init()
|
||||
{
|
||||
this->closest_lines.clear();
|
||||
this->closest_lines_set.clear();
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix)
|
||||
{
|
||||
// Called with a row and column of the grid cell, which is inside a bounding box.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = grid.segment(*it_contour_and_segment);
|
||||
Point closest_point;
|
||||
if (closest_lines_set.find(*it_contour_and_segment) == closest_lines_set.end() &&
|
||||
line_alg::distance_to_squared(Line(segment.first, segment.second), center, &closest_point) <= this->max_distance_squared) {
|
||||
closest_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, closest_point});
|
||||
closest_lines_set.insert(*it_contour_and_segment);
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid & grid;
|
||||
const Slic3r::Point center;
|
||||
std::vector<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 ¢er, float search_radius)
|
||||
{
|
||||
Point radius_vector(search_radius, search_radius);
|
||||
MinDistanceVisitor visitor(grid, center, search_radius * search_radius);
|
||||
grid.visit_cells_intersecting_box(BoundingBox(center - radius_vector, center + radius_vector), visitor);
|
||||
std::sort(visitor.closest_lines.begin(), visitor.closest_lines.end(), [¢er](const auto &l, const auto &r) {
|
||||
return (center - l.point).template cast<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;
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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> ®ion : 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> ®ion : segmentation)
|
||||
segmented_regions[layer_idx].emplace_back(std::move(region));
|
||||
}
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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_
|
||||
|
@ -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();
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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()));
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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, ¢er, &radius))
|
||||
check_against_circular_bed(*volume, overall_state, center, radius);
|
||||
else if (GUI::Bed3D::is_convex(opt->values))
|
||||
check_against_convex_bed(*volume, overall_state);
|
||||
}
|
||||
|
||||
contained_min_one |= !volume->is_outside;
|
||||
#else
|
||||
if (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0)))
|
||||
continue;
|
||||
|
||||
@ -941,15 +1163,16 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
|
||||
|
||||
contained_min_one |= contained;
|
||||
|
||||
if (state == ModelInstancePVS_Inside && volume->is_outside)
|
||||
state = ModelInstancePVS_Fully_Outside;
|
||||
if (overall_state == ModelInstancePVS_Inside && volume->is_outside)
|
||||
overall_state = ModelInstancePVS_Fully_Outside;
|
||||
|
||||
if (state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb))
|
||||
state = ModelInstancePVS_Partly_Outside;
|
||||
if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb))
|
||||
overall_state = ModelInstancePVS_Partly_Outside;
|
||||
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
}
|
||||
|
||||
if (out_state != nullptr)
|
||||
*out_state = state;
|
||||
*out_state = overall_state;
|
||||
|
||||
return contained_min_one;
|
||||
}
|
||||
@ -1005,35 +1228,28 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
|
||||
std::vector<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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -22,9 +22,25 @@ namespace GUI {
|
||||
|
||||
BedShape::BedShape(const ConfigOptionPoints& points)
|
||||
{
|
||||
auto polygon = Polygon::new_scale(points.values);
|
||||
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
if (points.size() < 3) {
|
||||
m_type = Bed3D::EShapeType::Invalid;
|
||||
return;
|
||||
}
|
||||
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
|
||||
// is this a rectangle ?
|
||||
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
Vec2d min;
|
||||
Vec2d max;
|
||||
if (Bed3D::is_rectangle(points.values, &min, &max)) {
|
||||
m_type = Bed3D::EShapeType::Rectangle;
|
||||
m_rectSize = max - min;
|
||||
m_rectOrigin = -min;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
Polygon polygon = Polygon::new_scale(points.values);
|
||||
if (points.size() == 4) {
|
||||
auto lines = polygon.lines();
|
||||
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
|
||||
@ -48,8 +64,21 @@ BedShape::BedShape(const ConfigOptionPoints& points)
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
|
||||
// is this a circle ?
|
||||
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
Vec2d center;
|
||||
double radius;
|
||||
if (Bed3D::is_circle(points.values, ¢er, &radius)) {
|
||||
m_type = Bed3D::EShapeType::Circle;
|
||||
m_diameter = 2.0 * radius;
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a custom bed shape, use the polygon provided.
|
||||
m_type = Bed3D::EShapeType::Custom;
|
||||
#else
|
||||
{
|
||||
// Analyze the array of points.Do they reside on a circle ?
|
||||
auto center = polygon.bounding_box().center();
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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(), ¢er, &radius);
|
||||
const double sq_radius = sqr(radius);
|
||||
for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) {
|
||||
if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) {
|
||||
if (sq_radius < (Vec2d(move.position.x(), move.position.y()) - center).squaredNorm()) {
|
||||
m_contained_in_bed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (bed_type == Bed3D::EShapeType::Custom) {
|
||||
const Pointfs& shape = wxGetApp().plater()->get_bed().get_shape();
|
||||
if (Bed3D::is_convex(shape)) {
|
||||
const Polygon poly = Polygon::new_scale(shape);
|
||||
for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) {
|
||||
if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) {
|
||||
if (!poly.contains(Point::new_scale(Vec2d(move.position.x(), move.position.y())))) {
|
||||
m_contained_in_bed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_sequential_view.gcode_ids.clear();
|
||||
|
@ -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)
|
||||
|