diff --git a/resources/images/copy_menu.svg b/resources/images/copy_menu.svg
new file mode 100644
index 00000000000..23e0bfeb2aa
--- /dev/null
+++ b/resources/images/copy_menu.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/resources/images/copy_menu_dark.svg b/resources/images/copy_menu_dark.svg
new file mode 100644
index 00000000000..eaee113a1b6
--- /dev/null
+++ b/resources/images/copy_menu_dark.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/resources/images/toolbar_measure.svg b/resources/images/toolbar_measure.svg
new file mode 100644
index 00000000000..1606b5ee6f5
--- /dev/null
+++ b/resources/images/toolbar_measure.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/resources/images/toolbar_measure_dark.svg b/resources/images/toolbar_measure_dark.svg
new file mode 100644
index 00000000000..a273e753473
--- /dev/null
+++ b/resources/images/toolbar_measure_dark.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/resources/shaders/110/background.fs b/resources/shaders/110/background.fs
new file mode 100644
index 00000000000..b1484408988
--- /dev/null
+++ b/resources/shaders/110/background.fs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform vec4 top_color;
+uniform vec4 bottom_color;
+
+varying vec2 tex_coord;
+
+void main()
+{
+ gl_FragColor = mix(bottom_color, top_color, tex_coord.y);
+}
diff --git a/resources/shaders/110/background.vs b/resources/shaders/110/background.vs
new file mode 100644
index 00000000000..9b56ab43a26
--- /dev/null
+++ b/resources/shaders/110/background.vs
@@ -0,0 +1,12 @@
+#version 110
+
+attribute vec3 v_position;
+attribute vec2 v_tex_coord;
+
+varying vec2 tex_coord;
+
+void main()
+{
+ tex_coord = v_tex_coord;
+ gl_Position = vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/cali.fs b/resources/shaders/110/flat.fs
similarity index 100%
rename from resources/shaders/cali.fs
rename to resources/shaders/110/flat.fs
diff --git a/resources/shaders/110/flat.vs b/resources/shaders/110/flat.vs
new file mode 100644
index 00000000000..d9063f0c70e
--- /dev/null
+++ b/resources/shaders/110/flat.vs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+attribute vec3 v_position;
+
+void main()
+{
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/110/flat_clip.fs b/resources/shaders/110/flat_clip.fs
new file mode 100644
index 00000000000..ececb8eb1ae
--- /dev/null
+++ b/resources/shaders/110/flat_clip.fs
@@ -0,0 +1,15 @@
+#version 110
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+uniform vec4 uniform_color;
+
+varying vec3 clipping_planes_dots;
+
+void main()
+{
+ if (any(lessThan(clipping_planes_dots, ZERO)))
+ discard;
+
+ gl_FragColor = uniform_color;
+}
diff --git a/resources/shaders/110/flat_clip.vs b/resources/shaders/110/flat_clip.vs
new file mode 100644
index 00000000000..cdf7d4b3b2c
--- /dev/null
+++ b/resources/shaders/110/flat_clip.vs
@@ -0,0 +1,23 @@
+#version 110
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+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;
+
+attribute vec3 v_position;
+
+varying vec3 clipping_planes_dots;
+
+void main()
+{
+ // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
+ vec4 world_pos = volume_world_matrix * vec4(v_position, 1.0);
+ clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
+
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/110/flat_texture.fs b/resources/shaders/110/flat_texture.fs
new file mode 100644
index 00000000000..ffe193b1c00
--- /dev/null
+++ b/resources/shaders/110/flat_texture.fs
@@ -0,0 +1,10 @@
+#version 110
+
+uniform sampler2D uniform_texture;
+
+varying vec2 tex_coord;
+
+void main()
+{
+ gl_FragColor = texture2D(uniform_texture, tex_coord);
+}
diff --git a/resources/shaders/110/flat_texture.vs b/resources/shaders/110/flat_texture.vs
new file mode 100644
index 00000000000..dc4868b04df
--- /dev/null
+++ b/resources/shaders/110/flat_texture.vs
@@ -0,0 +1,15 @@
+#version 110
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+attribute vec3 v_position;
+attribute vec2 v_tex_coord;
+
+varying vec2 tex_coord;
+
+void main()
+{
+ tex_coord = v_tex_coord;
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/110/gouraud.fs b/resources/shaders/110/gouraud.fs
new file mode 100644
index 00000000000..6f354ff9a6b
--- /dev/null
+++ b/resources/shaders/110/gouraud.fs
@@ -0,0 +1,107 @@
+#version 110
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+//BBS: add grey and orange
+//const vec3 GREY = vec3(0.9, 0.9, 0.9);
+const vec3 ORANGE = vec3(0.8, 0.4, 0.0);
+const vec3 LightRed = vec3(0.78, 0.0, 0.0);
+const vec3 LightBlue = vec3(0.73, 1.0, 1.0);
+const float EPSILON = 0.0001;
+
+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 bool use_color_clip_plane;
+uniform vec4 uniform_color_clip_plane_1;
+uniform vec4 uniform_color_clip_plane_2;
+uniform SlopeDetection slope;
+
+//BBS: add outline_color
+uniform bool is_outline;
+
+#ifdef ENABLE_ENVIRONMENT_MAP
+ uniform sampler2D environment_tex;
+ uniform bool use_environment_tex;
+#endif // ENABLE_ENVIRONMENT_MAP
+
+uniform PrintVolumeDetection print_volume;
+
+varying vec3 clipping_planes_dots;
+varying float color_clip_plane_dot;
+
+// x = diffuse, y = specular;
+varying vec2 intensity;
+
+varying vec4 world_pos;
+varying float world_normal_z;
+varying vec3 eye_normal;
+
+void main()
+{
+ if (any(lessThan(clipping_planes_dots, ZERO)))
+ discard;
+
+ vec4 color;
+ if (use_color_clip_plane) {
+ color.rgb = (color_clip_plane_dot < 0.0) ? uniform_color_clip_plane_1.rgb : uniform_color_clip_plane_2.rgb;
+ color.a = uniform_color.a;
+ }
+ else
+ color = uniform_color;
+
+ if (slope.actived) {
+ if(world_pos.z<0.1&&world_pos.z>-0.1)
+ {
+ color.rgb = LightBlue;
+ color.a = 0.8;
+ }
+ else if( world_normal_z < slope.normal_z - EPSILON)
+ {
+ color.rgb = color.rgb * 0.5 + LightRed * 0.5;
+ color.a = 0.8;
+ }
+ }
+ // 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.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb;
+
+ //BBS: add outline_color
+ if (is_outline)
+ gl_FragColor = uniform_color;
+#ifdef ENABLE_ENVIRONMENT_MAP
+ else if (use_environment_tex)
+ gl_FragColor = vec4(0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x, color.a);
+#endif
+ else
+ gl_FragColor = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a);
+}
\ No newline at end of file
diff --git a/resources/shaders/110/gouraud.vs b/resources/shaders/110/gouraud.vs
new file mode 100644
index 00000000000..ff3a190c799
--- /dev/null
+++ b/resources/shaders/110/gouraud.vs
@@ -0,0 +1,81 @@
+#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 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_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;
+// Color clip plane - general orientation. Used by the cut gizmo.
+uniform vec4 color_clip_plane;
+
+attribute vec3 v_position;
+attribute vec3 v_normal;
+
+// x = diffuse, y = specular;
+varying vec2 intensity;
+
+varying vec3 clipping_planes_dots;
+varying float color_clip_plane_dot;
+
+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(view_normal_matrix * v_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;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), 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;
+
+ // Point in homogenous coordinates.
+ world_pos = volume_world_matrix * vec4(v_position, 1.0);
+
+ // z component of normal vector in world coordinate used for slope shading
+ world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
+
+ gl_Position = projection_matrix * position;
+ // 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);
+ color_clip_plane_dot = dot(world_pos, color_clip_plane);
+}
diff --git a/resources/shaders/110/gouraud_light.fs b/resources/shaders/110/gouraud_light.fs
new file mode 100644
index 00000000000..970185a00ed
--- /dev/null
+++ b/resources/shaders/110/gouraud_light.fs
@@ -0,0 +1,12 @@
+#version 110
+
+uniform vec4 uniform_color;
+uniform float emission_factor;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
+}
diff --git a/resources/shaders/110/gouraud_light.vs b/resources/shaders/110/gouraud_light.vs
new file mode 100644
index 00000000000..135a23b3da8
--- /dev/null
+++ b/resources/shaders/110/gouraud_light.vs
@@ -0,0 +1,45 @@
+#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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+
+attribute vec3 v_position;
+attribute vec3 v_normal;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(view_normal_matrix * v_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(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ gl_Position = projection_matrix * position;
+}
diff --git a/resources/shaders/110/gouraud_light_instanced.fs b/resources/shaders/110/gouraud_light_instanced.fs
new file mode 100644
index 00000000000..970185a00ed
--- /dev/null
+++ b/resources/shaders/110/gouraud_light_instanced.fs
@@ -0,0 +1,12 @@
+#version 110
+
+uniform vec4 uniform_color;
+uniform float emission_factor;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
+}
diff --git a/resources/shaders/110/gouraud_light_instanced.vs b/resources/shaders/110/gouraud_light_instanced.vs
new file mode 100644
index 00000000000..f512a9cafcf
--- /dev/null
+++ b/resources/shaders/110/gouraud_light_instanced.vs
@@ -0,0 +1,50 @@
+#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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+
+// vertex attributes
+attribute vec3 v_position;
+attribute vec3 v_normal;
+// instance attributes
+attribute vec3 i_offset;
+attribute vec2 i_scales;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 eye_normal = normalize(view_normal_matrix * v_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;
+ vec4 world_position = vec4(v_position * vec3(vec2(1.5 * i_scales.x), 1.5 * i_scales.y) + i_offset - vec3(0.0, 0.0, 0.5 * i_scales.y), 1.0);
+ vec4 eye_position = view_model_matrix * world_position;
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), 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_Position = projection_matrix * eye_position;
+}
diff --git a/resources/shaders/110/imgui.fs b/resources/shaders/110/imgui.fs
new file mode 100644
index 00000000000..4b0e27ce9da
--- /dev/null
+++ b/resources/shaders/110/imgui.fs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform sampler2D Texture;
+
+varying vec2 Frag_UV;
+varying vec4 Frag_Color;
+
+void main()
+{
+ gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
+}
\ No newline at end of file
diff --git a/resources/shaders/110/imgui.vs b/resources/shaders/110/imgui.vs
new file mode 100644
index 00000000000..100813e2b5e
--- /dev/null
+++ b/resources/shaders/110/imgui.vs
@@ -0,0 +1,17 @@
+#version 110
+
+uniform mat4 ProjMtx;
+
+attribute vec2 Position;
+attribute vec2 UV;
+attribute vec4 Color;
+
+varying vec2 Frag_UV;
+varying vec4 Frag_Color;
+
+void main()
+{
+ Frag_UV = UV;
+ Frag_Color = Color;
+ gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/resources/shaders/options_110.fs b/resources/shaders/110/mm_contour.fs
similarity index 100%
rename from resources/shaders/options_110.fs
rename to resources/shaders/110/mm_contour.fs
diff --git a/resources/shaders/110/mm_contour.vs b/resources/shaders/110/mm_contour.vs
new file mode 100644
index 00000000000..b37394b6191
--- /dev/null
+++ b/resources/shaders/110/mm_contour.vs
@@ -0,0 +1,15 @@
+#version 110
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform float offset;
+
+attribute vec3 v_position;
+
+void main()
+{
+ // Add small epsilon to z to solve z-fighting between painted triangles and contour lines.
+ vec4 clip_position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+ clip_position.z -= offset * abs(clip_position.w);
+ gl_Position = clip_position;
+}
diff --git a/resources/shaders/110/mm_gouraud.fs b/resources/shaders/110/mm_gouraud.fs
new file mode 100644
index 00000000000..8ca23df66d9
--- /dev/null
+++ b/resources/shaders/110/mm_gouraud.fs
@@ -0,0 +1,90 @@
+#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;
+//BBS: add grey and orange
+//const vec3 GREY = vec3(0.9, 0.9, 0.9);
+const vec3 ORANGE = vec3(0.8, 0.4, 0.0);
+const vec3 LightRed = vec3(0.78, 0.0, 0.0);
+const vec3 LightBlue = vec3(0.73, 1.0, 1.0);
+uniform vec4 uniform_color;
+
+uniform bool volume_mirrored;
+
+uniform mat4 view_model_matrix;
+uniform mat3 view_normal_matrix;
+
+varying vec3 clipping_planes_dots;
+varying vec4 model_pos;
+varying vec4 world_pos;
+
+struct SlopeDetection
+{
+ bool actived;
+ float normal_z;
+ mat3 volume_world_normal_matrix;
+};
+uniform SlopeDetection slope;
+
+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
+
+ if (volume_mirrored)
+ triangle_normal = -triangle_normal;
+
+ vec3 transformed_normal = normalize(slope.volume_world_normal_matrix * triangle_normal);
+
+ if (slope.actived) {
+ if(world_pos.z<0.1&&world_pos.z>-0.1)
+ {
+ color = LightBlue;
+ alpha = 1.0;
+ }
+ else if( transformed_normal.z < slope.normal_z - EPSILON)
+ {
+ color = color * 0.5 + LightRed * 0.5;
+ alpha = 1.0;
+ }
+ }
+ // First transform the normal into camera space and normalize the result.
+ vec3 eye_normal = normalize(view_normal_matrix * 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);
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec3 position = (view_model_matrix * model_pos).xyz;
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
+}
diff --git a/resources/shaders/110/mm_gouraud.vs b/resources/shaders/110/mm_gouraud.vs
new file mode 100644
index 00000000000..e8c679b6bf3
--- /dev/null
+++ b/resources/shaders/110/mm_gouraud.vs
@@ -0,0 +1,35 @@
+#version 110
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+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;
+
+attribute vec3 v_position;
+
+varying vec3 clipping_planes_dots;
+varying vec4 model_pos;
+varying vec4 world_pos;
+struct SlopeDetection
+{
+ bool actived;
+ float normal_z;
+ mat3 volume_world_normal_matrix;
+};
+uniform SlopeDetection slope;
+void main()
+{
+ model_pos = vec4(v_position, 1.0);
+ // Point in homogenous coordinates.
+ world_pos = volume_world_matrix * model_pos;
+
+ gl_Position = projection_matrix * view_model_matrix * model_pos;
+ // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
+ clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
+}
diff --git a/resources/shaders/110/printbed.fs b/resources/shaders/110/printbed.fs
new file mode 100644
index 00000000000..833dff08f46
--- /dev/null
+++ b/resources/shaders/110/printbed.fs
@@ -0,0 +1,34 @@
+#version 110
+
+const vec3 back_color_dark = vec3(0.235, 0.235, 0.235);
+const vec3 back_color_light = vec3(0.365, 0.365, 0.365);
+
+uniform sampler2D texture;
+uniform bool transparent_background;
+uniform bool svg_source;
+
+varying vec2 tex_coord;
+
+vec4 svg_color()
+{
+ // takes foreground from texture
+ vec4 fore_color = texture2D(texture, tex_coord);
+
+ // calculates radial gradient
+ vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coord.xy) - vec2(0.5)))));
+
+ // blends foreground with background
+ return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0);
+}
+
+vec4 non_svg_color()
+{
+ // takes foreground from texture
+ vec4 color = texture2D(texture, tex_coord);
+ return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a);
+}
+
+void main()
+{
+ gl_FragColor = svg_source ? svg_color() : non_svg_color();
+}
\ No newline at end of file
diff --git a/resources/shaders/110/printbed.vs b/resources/shaders/110/printbed.vs
new file mode 100644
index 00000000000..dc4868b04df
--- /dev/null
+++ b/resources/shaders/110/printbed.vs
@@ -0,0 +1,15 @@
+#version 110
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+attribute vec3 v_position;
+attribute vec2 v_tex_coord;
+
+varying vec2 tex_coord;
+
+void main()
+{
+ tex_coord = v_tex_coord;
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/110/thumbnail.fs b/resources/shaders/110/thumbnail.fs
new file mode 100644
index 00000000000..4b269734ee6
--- /dev/null
+++ b/resources/shaders/110/thumbnail.fs
@@ -0,0 +1,16 @@
+#version 110
+
+uniform vec4 uniform_color;
+uniform float emission_factor;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+//varying float drop;
+varying vec4 world_pos;
+
+void main()
+{
+ if (world_pos.z < 0.0)
+ discard;
+ gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
+}
diff --git a/resources/shaders/110/thumbnail.vs b/resources/shaders/110/thumbnail.vs
new file mode 100644
index 00000000000..69fd0c753cb
--- /dev/null
+++ b/resources/shaders/110/thumbnail.vs
@@ -0,0 +1,50 @@
+#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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+uniform mat4 volume_world_matrix;
+
+attribute vec3 v_position;
+attribute vec3 v_normal;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+varying vec4 world_pos;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(view_normal_matrix * v_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(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ // Point in homogenous coordinates.
+ world_pos = volume_world_matrix * vec4(v_position, 1.0);
+
+ gl_Position = projection_matrix * position;
+}
diff --git a/resources/shaders/110/variable_layer_height.fs b/resources/shaders/110/variable_layer_height.fs
new file mode 100644
index 00000000000..693c1c6a0b2
--- /dev/null
+++ b/resources/shaders/110/variable_layer_height.fs
@@ -0,0 +1,41 @@
+#version 110
+
+#define M_PI 3.1415926535897932384626433832795
+
+// 2D texture (1D texture split by the rows) of color along the object Z axis.
+uniform sampler2D z_texture;
+// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
+uniform float z_to_texture_row;
+uniform float z_texture_row_to_normalized;
+uniform float z_cursor;
+uniform float z_cursor_band_width;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying float object_z;
+
+void main()
+{
+ float object_z_row = z_to_texture_row * object_z;
+ // Index of the row in the texture.
+ float z_texture_row = floor(object_z_row);
+ // Normalized coordinate from 0. to 1.
+ float z_texture_col = object_z_row - z_texture_row;
+ float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
+ // Calculate level of detail from the object Z coordinate.
+ // This makes the slowly sloping surfaces to be shown with high detail (with stripes),
+ // and the vertical surfaces to be shown with low detail (no stripes)
+ float z_in_cells = object_z_row * 190.;
+ // Gradient of Z projected on the screen.
+ float dx_vtc = dFdx(z_in_cells);
+ float dy_vtc = dFdy(z_in_cells);
+ float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.);
+ // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
+ vec4 color = vec4(0.25, 0.25, 0.25, 1.0);
+ if (z_texture_row >= 0.0)
+ color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
+ texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
+ // Mix the final color.
+ gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
+}
diff --git a/resources/shaders/110/variable_layer_height.vs b/resources/shaders/110/variable_layer_height.vs
new file mode 100644
index 00000000000..2d6334f44e8
--- /dev/null
+++ b/resources/shaders/110/variable_layer_height.vs
@@ -0,0 +1,60 @@
+#version 110
+
+#define INTENSITY_CORRECTION 0.6
+
+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
+
+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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+uniform mat4 volume_world_matrix;
+uniform float object_max_z;
+
+attribute vec3 v_position;
+attribute vec3 v_normal;
+attribute vec2 v_tex_coord;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying float object_z;
+
+void main()
+{
+ // =====================================================
+ // NOTE:
+ // when object_max_z > 0.0 we are rendering the overlay
+ // when object_max_z == 0.0 we are rendering the volumes
+ // =====================================================
+
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = (object_max_z > 0.0) ? vec3(0.0, 0.0, 1.0) : normalize(view_normal_matrix * v_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(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular)
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ // Scaled to widths of the Z texture.
+ object_z = (object_max_z > 0.0) ? object_max_z * v_tex_coord.y : (volume_world_matrix * vec4(v_position, 1.0)).z;
+
+ gl_Position = projection_matrix * position;
+}
diff --git a/resources/shaders/140/background.fs b/resources/shaders/140/background.fs
new file mode 100644
index 00000000000..c21f3a70cd0
--- /dev/null
+++ b/resources/shaders/140/background.fs
@@ -0,0 +1,11 @@
+#version 140
+
+uniform vec4 top_color;
+uniform vec4 bottom_color;
+
+in vec2 tex_coord;
+
+void main()
+{
+ gl_FragColor = mix(bottom_color, top_color, tex_coord.y);
+}
diff --git a/resources/shaders/140/background.vs b/resources/shaders/140/background.vs
new file mode 100644
index 00000000000..13609b3a210
--- /dev/null
+++ b/resources/shaders/140/background.vs
@@ -0,0 +1,12 @@
+#version 140
+
+in vec3 v_position;
+in vec2 v_tex_coord;
+
+out vec2 tex_coord;
+
+void main()
+{
+ tex_coord = v_tex_coord;
+ gl_Position = vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/140/flat.fs b/resources/shaders/140/flat.fs
new file mode 100644
index 00000000000..e74124dcaef
--- /dev/null
+++ b/resources/shaders/140/flat.fs
@@ -0,0 +1,8 @@
+#version 140
+
+uniform vec4 uniform_color;
+
+void main()
+{
+ gl_FragColor = uniform_color;
+}
diff --git a/resources/shaders/140/flat.vs b/resources/shaders/140/flat.vs
new file mode 100644
index 00000000000..7042671de20
--- /dev/null
+++ b/resources/shaders/140/flat.vs
@@ -0,0 +1,11 @@
+#version 140
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+in vec3 v_position;
+
+void main()
+{
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/140/flat_clip.fs b/resources/shaders/140/flat_clip.fs
new file mode 100644
index 00000000000..b77e0bfaa69
--- /dev/null
+++ b/resources/shaders/140/flat_clip.fs
@@ -0,0 +1,17 @@
+#version 140
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+uniform vec4 uniform_color;
+
+in vec3 clipping_planes_dots;
+
+out vec4 out_color;
+
+void main()
+{
+ if (any(lessThan(clipping_planes_dots, ZERO)))
+ discard;
+
+ out_color = uniform_color;
+}
diff --git a/resources/shaders/140/flat_clip.vs b/resources/shaders/140/flat_clip.vs
new file mode 100644
index 00000000000..40cddf1e5a6
--- /dev/null
+++ b/resources/shaders/140/flat_clip.vs
@@ -0,0 +1,23 @@
+#version 140
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+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;
+
+in vec3 v_position;
+
+out vec3 clipping_planes_dots;
+
+void main()
+{
+ // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
+ vec4 world_pos = volume_world_matrix * vec4(v_position, 1.0);
+ clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
+
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/140/flat_texture.fs b/resources/shaders/140/flat_texture.fs
new file mode 100644
index 00000000000..dec946721e3
--- /dev/null
+++ b/resources/shaders/140/flat_texture.fs
@@ -0,0 +1,10 @@
+#version 140
+
+uniform sampler2D uniform_texture;
+
+in vec2 tex_coord;
+
+void main()
+{
+ gl_FragColor = texture(uniform_texture, tex_coord);
+}
diff --git a/resources/shaders/140/flat_texture.vs b/resources/shaders/140/flat_texture.vs
new file mode 100644
index 00000000000..57d8ca3b7c6
--- /dev/null
+++ b/resources/shaders/140/flat_texture.vs
@@ -0,0 +1,15 @@
+#version 140
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+in vec3 v_position;
+in vec2 v_tex_coord;
+
+out vec2 tex_coord;
+
+void main()
+{
+ tex_coord = v_tex_coord;
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/140/gouraud.fs b/resources/shaders/140/gouraud.fs
new file mode 100644
index 00000000000..84bce5c035b
--- /dev/null
+++ b/resources/shaders/140/gouraud.fs
@@ -0,0 +1,109 @@
+#version 140
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+//BBS: add grey and orange
+//const vec3 GREY = vec3(0.9, 0.9, 0.9);
+const vec3 ORANGE = vec3(0.8, 0.4, 0.0);
+const vec3 LightRed = vec3(0.78, 0.0, 0.0);
+const vec3 LightBlue = vec3(0.73, 1.0, 1.0);
+const float EPSILON = 0.0001;
+
+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 bool use_color_clip_plane;
+uniform vec4 uniform_color_clip_plane_1;
+uniform vec4 uniform_color_clip_plane_2;
+uniform SlopeDetection slope;
+
+//BBS: add outline_color
+uniform bool is_outline;
+
+#ifdef ENABLE_ENVIRONMENT_MAP
+ uniform sampler2D environment_tex;
+ uniform bool use_environment_tex;
+#endif // ENABLE_ENVIRONMENT_MAP
+
+uniform PrintVolumeDetection print_volume;
+
+in vec3 clipping_planes_dots;
+in float color_clip_plane_dot;
+
+// x = diffuse, y = specular;
+in vec2 intensity;
+
+in vec4 world_pos;
+in float world_normal_z;
+in vec3 eye_normal;
+
+out vec4 out_color;
+
+void main()
+{
+ if (any(lessThan(clipping_planes_dots, ZERO)))
+ discard;
+
+ vec4 color;
+ if (use_color_clip_plane) {
+ color.rgb = (color_clip_plane_dot < 0.0) ? uniform_color_clip_plane_1.rgb : uniform_color_clip_plane_2.rgb;
+ color.a = uniform_color.a;
+ }
+ else
+ color = uniform_color;
+
+ if (slope.actived) {
+ if(world_pos.z<0.1&&world_pos.z>-0.1)
+ {
+ color.rgb = LightBlue;
+ color.a = 0.8;
+ }
+ else if( world_normal_z < slope.normal_z - EPSILON)
+ {
+ color.rgb = color.rgb * 0.5 + LightRed * 0.5;
+ color.a = 0.8;
+ }
+ }
+ // 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.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb;
+
+ //BBS: add outline_color
+ if (is_outline)
+ out_color = uniform_color;
+#ifdef ENABLE_ENVIRONMENT_MAP
+ else if (use_environment_tex)
+ out_color = vec4(0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x, color.a);
+#endif
+ else
+ out_color = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a);
+}
\ No newline at end of file
diff --git a/resources/shaders/140/gouraud.vs b/resources/shaders/140/gouraud.vs
new file mode 100644
index 00000000000..72dd4ff1bf1
--- /dev/null
+++ b/resources/shaders/140/gouraud.vs
@@ -0,0 +1,81 @@
+#version 140
+
+#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 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_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;
+// Color clip plane - general orientation. Used by the cut gizmo.
+uniform vec4 color_clip_plane;
+
+in vec3 v_position;
+in vec3 v_normal;
+
+// x = diffuse, y = specular;
+out vec2 intensity;
+
+out vec3 clipping_planes_dots;
+out float color_clip_plane_dot;
+
+out vec4 world_pos;
+out float world_normal_z;
+out vec3 eye_normal;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ eye_normal = normalize(view_normal_matrix * v_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;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), 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;
+
+ // Point in homogenous coordinates.
+ world_pos = volume_world_matrix * vec4(v_position, 1.0);
+
+ // z component of normal vector in world coordinate used for slope shading
+ world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
+
+ gl_Position = projection_matrix * position;
+ // 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);
+ color_clip_plane_dot = dot(world_pos, color_clip_plane);
+}
diff --git a/resources/shaders/140/gouraud_light.fs b/resources/shaders/140/gouraud_light.fs
new file mode 100644
index 00000000000..de616e066ad
--- /dev/null
+++ b/resources/shaders/140/gouraud_light.fs
@@ -0,0 +1,12 @@
+#version 140
+
+uniform vec4 uniform_color;
+uniform float emission_factor;
+
+// x = tainted, y = specular;
+in vec2 intensity;
+
+void main()
+{
+ gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
+}
diff --git a/resources/shaders/140/gouraud_light.vs b/resources/shaders/140/gouraud_light.vs
new file mode 100644
index 00000000000..fad848f8bd5
--- /dev/null
+++ b/resources/shaders/140/gouraud_light.vs
@@ -0,0 +1,45 @@
+#version 140
+
+#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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+
+in vec3 v_position;
+in vec3 v_normal;
+
+// x = tainted, y = specular;
+out vec2 intensity;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(view_normal_matrix * v_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(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ gl_Position = projection_matrix * position;
+}
diff --git a/resources/shaders/140/gouraud_light_instanced.fs b/resources/shaders/140/gouraud_light_instanced.fs
new file mode 100644
index 00000000000..de616e066ad
--- /dev/null
+++ b/resources/shaders/140/gouraud_light_instanced.fs
@@ -0,0 +1,12 @@
+#version 140
+
+uniform vec4 uniform_color;
+uniform float emission_factor;
+
+// x = tainted, y = specular;
+in vec2 intensity;
+
+void main()
+{
+ gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
+}
diff --git a/resources/shaders/140/gouraud_light_instanced.vs b/resources/shaders/140/gouraud_light_instanced.vs
new file mode 100644
index 00000000000..e0437ca3970
--- /dev/null
+++ b/resources/shaders/140/gouraud_light_instanced.vs
@@ -0,0 +1,50 @@
+#version 140
+
+#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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+
+// vertex attributes
+in vec3 v_position;
+in vec3 v_normal;
+// instance attributes
+in vec3 i_offset;
+in vec2 i_scales;
+
+// x = tainted, y = specular;
+out vec2 intensity;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 eye_normal = normalize(view_normal_matrix * v_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;
+ vec4 world_position = vec4(v_position * vec3(vec2(1.5 * i_scales.x), 1.5 * i_scales.y) + i_offset - vec3(0.0, 0.0, 0.5 * i_scales.y), 1.0);
+ vec4 eye_position = view_model_matrix * world_position;
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), 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_Position = projection_matrix * eye_position;
+}
diff --git a/resources/shaders/140/imgui.fs b/resources/shaders/140/imgui.fs
new file mode 100644
index 00000000000..4b2571749fa
--- /dev/null
+++ b/resources/shaders/140/imgui.fs
@@ -0,0 +1,11 @@
+#version 140
+
+uniform sampler2D Texture;
+
+in vec2 Frag_UV;
+in vec4 Frag_Color;
+
+void main()
+{
+ gl_FragColor = Frag_Color * texture(Texture, Frag_UV.st);
+}
\ No newline at end of file
diff --git a/resources/shaders/140/imgui.vs b/resources/shaders/140/imgui.vs
new file mode 100644
index 00000000000..fd743bdf2d1
--- /dev/null
+++ b/resources/shaders/140/imgui.vs
@@ -0,0 +1,17 @@
+#version 140
+
+uniform mat4 ProjMtx;
+
+in vec2 Position;
+in vec2 UV;
+in vec4 Color;
+
+out vec2 Frag_UV;
+out vec4 Frag_Color;
+
+void main()
+{
+ Frag_UV = UV;
+ Frag_Color = Color;
+ gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/resources/shaders/140/mm_contour.fs b/resources/shaders/140/mm_contour.fs
new file mode 100644
index 00000000000..e74124dcaef
--- /dev/null
+++ b/resources/shaders/140/mm_contour.fs
@@ -0,0 +1,8 @@
+#version 140
+
+uniform vec4 uniform_color;
+
+void main()
+{
+ gl_FragColor = uniform_color;
+}
diff --git a/resources/shaders/140/mm_contour.vs b/resources/shaders/140/mm_contour.vs
new file mode 100644
index 00000000000..679291ba6d0
--- /dev/null
+++ b/resources/shaders/140/mm_contour.vs
@@ -0,0 +1,15 @@
+#version 140
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform float offset;
+
+in vec3 v_position;
+
+void main()
+{
+ // Add small epsilon to z to solve z-fighting between painted triangles and contour lines.
+ vec4 clip_position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+ clip_position.z -= offset * abs(clip_position.w);
+ gl_Position = clip_position;
+}
diff --git a/resources/shaders/140/mm_gouraud.fs b/resources/shaders/140/mm_gouraud.fs
new file mode 100644
index 00000000000..2156394bea9
--- /dev/null
+++ b/resources/shaders/140/mm_gouraud.fs
@@ -0,0 +1,90 @@
+#version 140
+
+#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;
+//BBS: add grey and orange
+//const vec3 GREY = vec3(0.9, 0.9, 0.9);
+const vec3 ORANGE = vec3(0.8, 0.4, 0.0);
+const vec3 LightRed = vec3(0.78, 0.0, 0.0);
+const vec3 LightBlue = vec3(0.73, 1.0, 1.0);
+uniform vec4 uniform_color;
+
+uniform bool volume_mirrored;
+
+uniform mat4 view_model_matrix;
+uniform mat3 view_normal_matrix;
+
+in vec3 clipping_planes_dots;
+in vec4 model_pos;
+in vec4 world_pos;
+
+struct SlopeDetection
+{
+ bool actived;
+ float normal_z;
+ mat3 volume_world_normal_matrix;
+};
+uniform SlopeDetection slope;
+
+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
+
+ if (volume_mirrored)
+ triangle_normal = -triangle_normal;
+
+ vec3 transformed_normal = normalize(slope.volume_world_normal_matrix * triangle_normal);
+
+ if (slope.actived) {
+ if(world_pos.z<0.1&&world_pos.z>-0.1)
+ {
+ color = LightBlue;
+ alpha = 1.0;
+ }
+ else if( transformed_normal.z < slope.normal_z - EPSILON)
+ {
+ color = color * 0.5 + LightRed * 0.5;
+ alpha = 1.0;
+ }
+ }
+ // First transform the normal into camera space and normalize the result.
+ vec3 eye_normal = normalize(view_normal_matrix * 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);
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec3 position = (view_model_matrix * model_pos).xyz;
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
+}
diff --git a/resources/shaders/140/mm_gouraud.vs b/resources/shaders/140/mm_gouraud.vs
new file mode 100644
index 00000000000..4add5c0aeed
--- /dev/null
+++ b/resources/shaders/140/mm_gouraud.vs
@@ -0,0 +1,35 @@
+#version 140
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+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;
+
+in vec3 v_position;
+
+out vec3 clipping_planes_dots;
+out vec4 model_pos;
+out vec4 world_pos;
+struct SlopeDetection
+{
+ bool actived;
+ float normal_z;
+ mat3 volume_world_normal_matrix;
+};
+uniform SlopeDetection slope;
+void main()
+{
+ model_pos = vec4(v_position, 1.0);
+ // Point in homogenous coordinates.
+ world_pos = volume_world_matrix * model_pos;
+
+ gl_Position = projection_matrix * view_model_matrix * model_pos;
+ // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
+ clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
+}
diff --git a/resources/shaders/140/printbed.fs b/resources/shaders/140/printbed.fs
new file mode 100644
index 00000000000..6d927a749c4
--- /dev/null
+++ b/resources/shaders/140/printbed.fs
@@ -0,0 +1,35 @@
+#version 140
+
+const vec3 back_color_dark = vec3(0.235, 0.235, 0.235);
+const vec3 back_color_light = vec3(0.365, 0.365, 0.365);
+
+uniform sampler2D in_texture;
+uniform bool transparent_background;
+uniform bool svg_source;
+
+in vec2 tex_coord;
+out vec4 frag_color;
+
+vec4 svg_color()
+{
+ // takes foreground from texture
+ vec4 fore_color = texture(in_texture, tex_coord);
+
+ // calculates radial gradient
+ vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coord.xy) - vec2(0.5)))));
+
+ // blends foreground with background
+ return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0);
+}
+
+vec4 non_svg_color()
+{
+ // takes foreground from texture
+ vec4 color = texture(in_texture, tex_coord);
+ return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a);
+}
+
+void main()
+{
+ frag_color = svg_source ? svg_color() : non_svg_color();
+}
\ No newline at end of file
diff --git a/resources/shaders/140/printbed.vs b/resources/shaders/140/printbed.vs
new file mode 100644
index 00000000000..57d8ca3b7c6
--- /dev/null
+++ b/resources/shaders/140/printbed.vs
@@ -0,0 +1,15 @@
+#version 140
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+
+in vec3 v_position;
+in vec2 v_tex_coord;
+
+out vec2 tex_coord;
+
+void main()
+{
+ tex_coord = v_tex_coord;
+ gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
+}
diff --git a/resources/shaders/140/thumbnail.fs b/resources/shaders/140/thumbnail.fs
new file mode 100644
index 00000000000..9e6d5d854cb
--- /dev/null
+++ b/resources/shaders/140/thumbnail.fs
@@ -0,0 +1,16 @@
+#version 140
+
+uniform vec4 uniform_color;
+uniform float emission_factor;
+
+// x = tainted, y = specular;
+in vec2 intensity;
+//varying float drop;
+in vec4 world_pos;
+
+void main()
+{
+ if (world_pos.z < 0.0)
+ discard;
+ gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
+}
diff --git a/resources/shaders/140/thumbnail.vs b/resources/shaders/140/thumbnail.vs
new file mode 100644
index 00000000000..90ca720a379
--- /dev/null
+++ b/resources/shaders/140/thumbnail.vs
@@ -0,0 +1,50 @@
+#version 140
+
+#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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+uniform mat4 volume_world_matrix;
+
+in vec3 v_position;
+in vec3 v_normal;
+
+// x = tainted, y = specular;
+out vec2 intensity;
+out vec4 world_pos;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(view_normal_matrix * v_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(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ // Point in homogenous coordinates.
+ world_pos = volume_world_matrix * vec4(v_position, 1.0);
+
+ gl_Position = projection_matrix * position;
+}
diff --git a/resources/shaders/140/variable_layer_height.fs b/resources/shaders/140/variable_layer_height.fs
new file mode 100644
index 00000000000..cf1fc309cc4
--- /dev/null
+++ b/resources/shaders/140/variable_layer_height.fs
@@ -0,0 +1,41 @@
+#version 140
+
+#define M_PI 3.1415926535897932384626433832795
+
+// 2D texture (1D texture split by the rows) of color along the object Z axis.
+uniform sampler2D z_texture;
+// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
+uniform float z_to_texture_row;
+uniform float z_texture_row_to_normalized;
+uniform float z_cursor;
+uniform float z_cursor_band_width;
+
+// x = tainted, y = specular;
+in vec2 intensity;
+
+in float object_z;
+
+void main()
+{
+ float object_z_row = z_to_texture_row * object_z;
+ // Index of the row in the texture.
+ float z_texture_row = floor(object_z_row);
+ // Normalized coordinate from 0. to 1.
+ float z_texture_col = object_z_row - z_texture_row;
+ float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
+ // Calculate level of detail from the object Z coordinate.
+ // This makes the slowly sloping surfaces to be shown with high detail (with stripes),
+ // and the vertical surfaces to be shown with low detail (no stripes)
+ float z_in_cells = object_z_row * 190.;
+ // Gradient of Z projected on the screen.
+ float dx_vtc = dFdx(z_in_cells);
+ float dy_vtc = dFdy(z_in_cells);
+ float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.);
+ // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
+ vec4 color = vec4(0.25, 0.25, 0.25, 1.0);
+ if (z_texture_row >= 0.0)
+ color = mix(texture(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
+ texture(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
+ // Mix the final color.
+ gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
+}
diff --git a/resources/shaders/140/variable_layer_height.vs b/resources/shaders/140/variable_layer_height.vs
new file mode 100644
index 00000000000..d8deb2f9eef
--- /dev/null
+++ b/resources/shaders/140/variable_layer_height.vs
@@ -0,0 +1,60 @@
+#version 140
+
+#define INTENSITY_CORRECTION 0.6
+
+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
+
+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
+
+uniform mat4 view_model_matrix;
+uniform mat4 projection_matrix;
+uniform mat3 view_normal_matrix;
+uniform mat4 volume_world_matrix;
+uniform float object_max_z;
+
+in vec3 v_position;
+in vec3 v_normal;
+in vec2 v_tex_coord;
+
+// x = tainted, y = specular;
+out vec2 intensity;
+
+out float object_z;
+
+void main()
+{
+ // =====================================================
+ // NOTE:
+ // when object_max_z > 0.0 we are rendering the overlay
+ // when object_max_z == 0.0 we are rendering the volumes
+ // =====================================================
+
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = (object_max_z > 0.0) ? vec3(0.0, 0.0, 1.0) : normalize(view_normal_matrix * v_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(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec4 position = view_model_matrix * vec4(v_position, 1.0);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular)
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ // Scaled to widths of the Z texture.
+ object_z = (object_max_z > 0.0) ? object_max_z * v_tex_coord.y : (volume_world_matrix * vec4(v_position, 1.0)).z;
+
+ gl_Position = projection_matrix * position;
+}
diff --git a/resources/shaders/background.fs b/resources/shaders/background.fs
new file mode 100644
index 00000000000..b1484408988
--- /dev/null
+++ b/resources/shaders/background.fs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform vec4 top_color;
+uniform vec4 bottom_color;
+
+varying vec2 tex_coord;
+
+void main()
+{
+ gl_FragColor = mix(bottom_color, top_color, tex_coord.y);
+}
diff --git a/resources/shaders/background.vs b/resources/shaders/background.vs
new file mode 100644
index 00000000000..b7c1d92c0e4
--- /dev/null
+++ b/resources/shaders/background.vs
@@ -0,0 +1,9 @@
+#version 110
+
+varying vec2 tex_coord;
+
+void main()
+{
+ gl_Position = gl_Vertex;
+ tex_coord = gl_MultiTexCoord0.xy;
+}
diff --git a/resources/shaders/flat.fs b/resources/shaders/flat.fs
new file mode 100644
index 00000000000..ab656998df7
--- /dev/null
+++ b/resources/shaders/flat.fs
@@ -0,0 +1,8 @@
+#version 110
+
+uniform vec4 uniform_color;
+
+void main()
+{
+ gl_FragColor = uniform_color;
+}
diff --git a/resources/shaders/cali.vs b/resources/shaders/flat.vs
similarity index 100%
rename from resources/shaders/cali.vs
rename to resources/shaders/flat.vs
diff --git a/resources/shaders/flat_texture.fs b/resources/shaders/flat_texture.fs
new file mode 100644
index 00000000000..ffe193b1c00
--- /dev/null
+++ b/resources/shaders/flat_texture.fs
@@ -0,0 +1,10 @@
+#version 110
+
+uniform sampler2D uniform_texture;
+
+varying vec2 tex_coord;
+
+void main()
+{
+ gl_FragColor = texture2D(uniform_texture, tex_coord);
+}
diff --git a/resources/shaders/flat_texture.vs b/resources/shaders/flat_texture.vs
new file mode 100644
index 00000000000..27addc7526a
--- /dev/null
+++ b/resources/shaders/flat_texture.vs
@@ -0,0 +1,9 @@
+#version 110
+
+varying vec2 tex_coord;
+
+void main()
+{
+ gl_Position = ftransform();
+ tex_coord = gl_MultiTexCoord0.xy;
+}
diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs
index 3f12a6e92c4..4084efdbf1f 100644
--- a/resources/shaders/gouraud.fs
+++ b/resources/shaders/gouraud.fs
@@ -48,7 +48,6 @@ varying vec2 intensity;
uniform PrintVolumeDetection print_volume;
-varying vec4 model_pos;
varying vec4 world_pos;
varying float world_normal_z;
varying vec3 eye_normal;
diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs
index 79d7a63c072..c8b3d7b335b 100644
--- a/resources/shaders/gouraud.vs
+++ b/resources/shaders/gouraud.vs
@@ -38,7 +38,6 @@ 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;
@@ -60,7 +59,6 @@ void main()
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;
diff --git a/resources/shaders/options_110.vs b/resources/shaders/options_110.vs
deleted file mode 100644
index 5f2ab23504e..00000000000
--- a/resources/shaders/options_110.vs
+++ /dev/null
@@ -1,22 +0,0 @@
-#version 110
-
-uniform bool use_fixed_screen_size;
-uniform float zoom;
-uniform float point_size;
-uniform float near_plane_height;
-
-float fixed_screen_size()
-{
- return point_size;
-}
-
-float fixed_world_size()
-{
- return (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w;
-}
-
-void main()
-{
- gl_Position = ftransform();
- gl_PointSize = use_fixed_screen_size ? fixed_screen_size() : fixed_world_size();
-}
diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs
deleted file mode 100644
index e9b61304f26..00000000000
--- a/resources/shaders/options_120.fs
+++ /dev/null
@@ -1,22 +0,0 @@
-// version 120 is needed for gl_PointCoord
-#version 120
-
-uniform vec4 uniform_color;
-uniform float percent_outline_radius;
-uniform float percent_center_radius;
-
-vec4 calc_color(float radius, vec4 color)
-{
- return ((radius < percent_center_radius) || (radius > 1.0 - percent_outline_radius)) ?
- vec4(0.5 * color.rgb, color.a) : color;
-}
-
-void main()
-{
- vec2 pos = (gl_PointCoord - 0.5) * 2.0;
- float radius = length(pos);
- if (radius > 1.0)
- discard;
-
- gl_FragColor = calc_color(radius, uniform_color);
-}
diff --git a/resources/shaders/options_120.vs b/resources/shaders/options_120.vs
deleted file mode 100644
index edb503fb2b2..00000000000
--- a/resources/shaders/options_120.vs
+++ /dev/null
@@ -1,22 +0,0 @@
-#version 120
-
-uniform bool use_fixed_screen_size;
-uniform float zoom;
-uniform float point_size;
-uniform float near_plane_height;
-
-float fixed_screen_size()
-{
- return point_size;
-}
-
-float fixed_world_size()
-{
- return (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w;
-}
-
-void main()
-{
- gl_Position = ftransform();
- gl_PointSize = use_fixed_screen_size ? fixed_screen_size() : fixed_world_size();
-}
diff --git a/resources/shaders/printbed.fs b/resources/shaders/printbed.fs
index d1316ca2fe8..833dff08f46 100644
--- a/resources/shaders/printbed.fs
+++ b/resources/shaders/printbed.fs
@@ -1,21 +1,21 @@
#version 110
-const vec3 back_color_dark = vec3(0.235, 0.235, 0.235);
+const vec3 back_color_dark = vec3(0.235, 0.235, 0.235);
const vec3 back_color_light = vec3(0.365, 0.365, 0.365);
uniform sampler2D texture;
uniform bool transparent_background;
uniform bool svg_source;
-varying vec2 tex_coords;
+varying vec2 tex_coord;
vec4 svg_color()
{
// takes foreground from texture
- vec4 fore_color = texture2D(texture, tex_coords);
+ vec4 fore_color = texture2D(texture, tex_coord);
// calculates radial gradient
- vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coords.xy) - vec2(0.5)))));
+ vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coord.xy) - vec2(0.5)))));
// blends foreground with background
return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0);
@@ -24,7 +24,7 @@ vec4 svg_color()
vec4 non_svg_color()
{
// takes foreground from texture
- vec4 color = texture2D(texture, tex_coords);
+ vec4 color = texture2D(texture, tex_coord);
return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a);
}
diff --git a/resources/shaders/printbed.vs b/resources/shaders/printbed.vs
index 7633017f12c..27addc7526a 100644
--- a/resources/shaders/printbed.vs
+++ b/resources/shaders/printbed.vs
@@ -1,14 +1,9 @@
#version 110
-attribute vec3 v_position;
-attribute vec2 v_tex_coords;
-
-varying vec2 tex_coords;
+varying vec2 tex_coord;
void main()
{
- gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position.x, v_position.y, v_position.z, 1.0);
- // the following line leads to crash on some Intel graphics card
- //gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position, 1.0);
- tex_coords = v_tex_coords;
+ gl_Position = ftransform();
+ tex_coord = gl_MultiTexCoord0.xy;
}
diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp
index 3ab0342d2c6..7271af2d973 100644
--- a/src/OrcaSlicer.cpp
+++ b/src/OrcaSlicer.cpp
@@ -1553,23 +1553,11 @@ int CLI::run(int argc, char **argv)
o->cut(Z, m_config.opt_float("cut"), &out);
}
#else
- ModelObject* object = model.objects.front();
- const BoundingBoxf3& box = object->bounding_box();
- const float Margin = 20.0;
- const float max_x = box.size()(0) / 2.0 + Margin;
- const float min_x = -max_x;
- const float max_y = box.size()(1) / 2.0 + Margin;
- const float min_y = -max_y;
-
- std::array plane_points;
- plane_points[0] = { min_x, min_y, 0 };
- plane_points[1] = { max_x, min_y, 0 };
- plane_points[2] = { max_x, max_y, 0 };
- plane_points[3] = { min_x, max_y, 0 };
- for (Vec3d& point : plane_points) {
- point += box.center();
- }
- model.objects.front()->cut(0, plane_points, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower);
+ Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
+ ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper);
+ auto cut_objects = cut.perform_with_plane();
+ for (ModelObject* obj : cut_objects)
+ model.add_object(*obj);
#endif
model.delete_object(size_t(0));
}
@@ -2279,12 +2267,12 @@ int CLI::run(int argc, char **argv)
else
colors.push_back("#FFFFFF");
- std::vector> colors_out(colors.size());
- unsigned char rgb_color[3] = {};
+ std::vector colors_out(colors.size());
+ ColorRGBA rgb_color;
for (const std::string& color : colors) {
- Slic3r::GUI::BitmapCache::parse_color(color, rgb_color);
+ Slic3r::decode_color(color, rgb_color);
size_t color_idx = &color - &colors.front();
- colors_out[color_idx] = { float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f };
+ colors_out[color_idx] = rgb_color;
}
int gl_major, gl_minor, gl_verbos;
@@ -2353,19 +2341,16 @@ int CLI::run(int argc, char **argv)
// continue;
for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) {
const ModelInstance &model_instance = *model_object.instances[instance_idx];
- glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, "volume", true, false, true);
+ glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, false, true);
//glvolume_collection.volumes.back()->geometry_id = key.geometry_id;
std::string color = filament_color?filament_color->get_at(extruder_id - 1):"#00FF00";
- unsigned char rgb_color[3] = {};
- Slic3r::GUI::BitmapCache::parse_color(color, rgb_color);
- glvolume_collection.volumes.back()->set_render_color( float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f);
+ ColorRGBA rgb_color;
+ Slic3r::decode_color(color, rgb_color);
+ glvolume_collection.volumes.back()->set_render_color(rgb_color);
- std::array new_color;
- new_color[0] = float(rgb_color[0]) / 255.f;
- new_color[1] = float(rgb_color[1]) / 255.f;
- new_color[2] = float(rgb_color[2]) / 255.f;
- new_color[3] = 1.f;
+ ColorRGBA new_color;
+ new_color = rgb_color;
glvolume_collection.volumes.back()->set_color(new_color);
glvolume_collection.volumes.back()->printable = model_instance.printable;
}
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index 1774eb28ac5..9380ea16498 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -161,6 +161,7 @@ namespace ImGui
const wchar_t ClippyMarker = 0x0802;
const wchar_t InfoMarker = 0x0803;
const wchar_t SliderFloatEditBtnIcon = 0x0804;
+ const wchar_t ClipboardBtnIcon = 0x0805;
// BBS
const wchar_t CircleButtonIcon = 0x0810;
@@ -196,6 +197,7 @@ namespace ImGui
const wchar_t CloseBlockNotifButton = 0x0833;
const wchar_t CloseBlockNotifHoverButton = 0x0834;
const wchar_t BlockNotifErrorIcon = 0x0835;
+ const wchar_t ClipboardBtnDarkIcon = 0x0836;
// void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/src/imgui/imgui.cpp b/src/imgui/imgui.cpp
index fd32e4cf5fb..8f45263a39d 100644
--- a/src/imgui/imgui.cpp
+++ b/src/imgui/imgui.cpp
@@ -6046,8 +6046,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->Pos = FindBestWindowPosForPopup(window);
else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
window->Pos = FindBestWindowPosForPopup(window);
- else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
- window->Pos = FindBestWindowPosForPopup(window);
+ // Orca: Allow fixed tooltip pos while still being clamped inside the render area
+ else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_is_child_tooltip) {
+ if (window_pos_set_by_api) {
+ // Hack: add ImGuiWindowFlags_Popup so it does not follow cursor
+ ImGuiWindowFlags old_flags = window->Flags;
+ window->Flags |= ImGuiWindowFlags_Popup;
+ window->Pos = FindBestWindowPosForPopup(window);
+ window->Flags = old_flags;
+ } else {
+ window->Pos = FindBestWindowPosForPopup(window);
+ }
+ }
// Calculate the range of allowed position for that window (to be movable and visible past safe area padding)
// When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect.
diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp
index 4ab007f8b8c..0df41f1be55 100644
--- a/src/libslic3r/BuildVolume.hpp
+++ b/src/libslic3r/BuildVolume.hpp
@@ -93,6 +93,9 @@ class BuildVolume
// Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices.
bool all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& bbox, bool ignore_bottom = true) const;
+ const std::pair, std::vector>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; }
+ const std::pair, std::vector>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; }
+
private:
// Source definition of the print bed geometry (PrintConfig::printable_area)
std::vector m_bed_shape;
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 3cfb82b0733..01347071786 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -56,6 +56,8 @@ set(lisbslic3r_sources
Clipper2Utils.cpp
Clipper2Utils.hpp
ClipperZUtils.hpp
+ Color.cpp
+ Color.hpp
Config.cpp
Config.hpp
CurveAnalyzer.cpp
@@ -200,12 +202,17 @@ set(lisbslic3r_sources
BlacklistedLibraryCheck.hpp
LocalesUtils.cpp
LocalesUtils.hpp
+ CutUtils.cpp
+ CutUtils.hpp
Model.cpp
Model.hpp
ModelArrange.hpp
ModelArrange.cpp
MultiMaterialSegmentation.cpp
MultiMaterialSegmentation.hpp
+ Measure.hpp
+ Measure.cpp
+ MeasureUtils.hpp
CustomGCode.cpp
CustomGCode.hpp
Arrange.hpp
@@ -305,6 +312,7 @@ set(lisbslic3r_sources
Surface.hpp
SurfaceCollection.cpp
SurfaceCollection.hpp
+ SurfaceMesh.hpp
SVG.cpp
SVG.hpp
Technologies.hpp
diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp
new file mode 100644
index 00000000000..c282e307ed5
--- /dev/null
+++ b/src/libslic3r/Color.cpp
@@ -0,0 +1,420 @@
+#include "libslic3r.h"
+#include "Color.hpp"
+
+#include
+
+static const float INV_255 = 1.0f / 255.0f;
+
+namespace Slic3r {
+
+// Conversion from RGB to HSV color space
+// The input RGB values are in the range [0, 1]
+// The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1]
+static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v)
+{
+ assert(0.0f <= r && r <= 1.0f);
+ assert(0.0f <= g && g <= 1.0f);
+ assert(0.0f <= b && b <= 1.0f);
+
+ const float max_comp = std::max(std::max(r, g), b);
+ const float min_comp = std::min(std::min(r, g), b);
+ const float delta = max_comp - min_comp;
+
+ if (delta > 0.0f) {
+ if (max_comp == r)
+ h = 60.0f * (std::fmod(((g - b) / delta), 6.0f));
+ else if (max_comp == g)
+ h = 60.0f * (((b - r) / delta) + 2.0f);
+ else if (max_comp == b)
+ h = 60.0f * (((r - g) / delta) + 4.0f);
+
+ s = (max_comp > 0.0f) ? delta / max_comp : 0.0f;
+ }
+ else {
+ h = 0.0f;
+ s = 0.0f;
+ }
+ v = max_comp;
+
+ while (h < 0.0f) { h += 360.0f; }
+ while (h > 360.0f) { h -= 360.0f; }
+
+ assert(0.0f <= s && s <= 1.0f);
+ assert(0.0f <= v && v <= 1.0f);
+ assert(0.0f <= h && h <= 360.0f);
+}
+
+// Conversion from HSV to RGB color space
+// The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1]
+// The output RGB values are in the range [0, 1]
+static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b)
+{
+ assert(0.0f <= s && s <= 1.0f);
+ assert(0.0f <= v && v <= 1.0f);
+ assert(0.0f <= h && h <= 360.0f);
+
+ const float chroma = v * s;
+ const float h_prime = std::fmod(h / 60.0f, 6.0f);
+ const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f));
+ const float m = v - chroma;
+
+ if (0.0f <= h_prime && h_prime < 1.0f) {
+ r = chroma;
+ g = x;
+ b = 0.0f;
+ }
+ else if (1.0f <= h_prime && h_prime < 2.0f) {
+ r = x;
+ g = chroma;
+ b = 0.0f;
+ }
+ else if (2.0f <= h_prime && h_prime < 3.0f) {
+ r = 0.0f;
+ g = chroma;
+ b = x;
+ }
+ else if (3.0f <= h_prime && h_prime < 4.0f) {
+ r = 0.0f;
+ g = x;
+ b = chroma;
+ }
+ else if (4.0f <= h_prime && h_prime < 5.0f) {
+ r = x;
+ g = 0.0f;
+ b = chroma;
+ }
+ else if (5.0f <= h_prime && h_prime < 6.0f) {
+ r = chroma;
+ g = 0.0f;
+ b = x;
+ }
+ else {
+ r = 0.0f;
+ g = 0.0f;
+ b = 0.0f;
+ }
+
+ r += m;
+ g += m;
+ b += m;
+
+ assert(0.0f <= r && r <= 1.0f);
+ assert(0.0f <= g && g <= 1.0f);
+ assert(0.0f <= b && b <= 1.0f);
+}
+
+class Randomizer
+{
+ std::random_device m_rd;
+
+public:
+ float random_float(float min, float max) {
+ std::mt19937 rand_generator(m_rd());
+ std::uniform_real_distribution distrib(min, max);
+ return distrib(rand_generator);
+ }
+};
+
+ColorRGB::ColorRGB(float r, float g, float b)
+: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) })
+{
+}
+
+ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b)
+: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) })
+{
+}
+
+bool ColorRGB::operator < (const ColorRGB& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] < other.m_data[i])
+ return true;
+ else if (m_data[i] > other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+bool ColorRGB::operator > (const ColorRGB& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] > other.m_data[i])
+ return true;
+ else if (m_data[i] < other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+ColorRGB ColorRGB::operator + (const ColorRGB& other) const
+{
+ ColorRGB ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f);
+ }
+ return ret;
+}
+
+ColorRGB ColorRGB::operator * (float value) const
+{
+ assert(value >= 0.0f);
+ ColorRGB ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f);
+ }
+ return ret;
+}
+
+ColorRGBA::ColorRGBA(float r, float g, float b, float a)
+: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) })
+{
+}
+
+ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) })
+{
+}
+
+bool ColorRGBA::operator < (const ColorRGBA& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] < other.m_data[i])
+ return true;
+ else if (m_data[i] > other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+bool ColorRGBA::operator > (const ColorRGBA& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] > other.m_data[i])
+ return true;
+ else if (m_data[i] < other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const
+{
+ ColorRGBA ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f);
+ }
+ return ret;
+}
+
+ColorRGBA ColorRGBA::operator * (float value) const
+{
+ assert(value >= 0.0f);
+ ColorRGBA ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f);
+ }
+ ret.m_data[3] = this->m_data[3];
+ return ret;
+}
+
+ColorRGB operator * (float value, const ColorRGB& other) { return other * value; }
+ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; }
+
+ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t)
+{
+ assert(0.0f <= t && t <= 1.0f);
+ return (1.0f - t) * a + t * b;
+}
+
+ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t)
+{
+ assert(0.0f <= t && t <= 1.0f);
+ return (1.0f - t) * a + t * b;
+}
+
+ColorRGB complementary(const ColorRGB& color)
+{
+ return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() };
+}
+
+ColorRGBA complementary(const ColorRGBA& color)
+{
+ return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() };
+}
+
+ColorRGB saturate(const ColorRGB& color, float factor)
+{
+ float h, s, v;
+ RGBtoHSV(color.r(), color.g(), color.b(), h, s, v);
+ s = std::clamp(s * factor, 0.0f, 1.0f);
+ float r, g, b;
+ HSVtoRGB(h, s, v, r, g, b);
+ return { r, g, b };
+}
+
+ColorRGBA saturate(const ColorRGBA& color, float factor)
+{
+ return to_rgba(saturate(to_rgb(color), factor), color.a());
+}
+
+ColorRGB opposite(const ColorRGB& color)
+{
+ float h, s, v;
+ RGBtoHSV(color.r(), color.g(), color.b(), h, s, v);
+
+ h += 65.0f; // 65 instead 60 to avoid circle values
+ if (h > 360.0f)
+ h -= 360.0f;
+
+ Randomizer rnd;
+ s = rnd.random_float(0.65f, 1.0f);
+ v = rnd.random_float(0.65f, 1.0f);
+
+ float r, g, b;
+ HSVtoRGB(h, s, v, r, g, b);
+ return { r, g, b };
+}
+
+ColorRGB opposite(const ColorRGB& a, const ColorRGB& b)
+{
+ float ha, sa, va;
+ RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va);
+ float hb, sb, vb;
+ RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb);
+
+ float delta_h = std::abs(ha - hb);
+ float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb);
+
+ start_h += 5.0f; // to avoid circle change of colors for 120 deg
+ if (delta_h < 180.0f)
+ delta_h = 360.0f - delta_h;
+
+ Randomizer rnd;
+ float out_h = start_h + 0.5f * delta_h;
+ if (out_h > 360.0f)
+ out_h -= 360.0f;
+ float out_s = rnd.random_float(0.65f, 1.0f);
+ float out_v = rnd.random_float(0.65f, 1.0f);
+
+ float out_r, out_g, out_b;
+ HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b);
+ return { out_r, out_g, out_b };
+}
+
+bool can_decode_color(const std::string &color)
+{
+ return (color.size() == 7 && color.front() == '#') || (color.size() == 9 && color.front() == '#');
+}
+
+bool decode_color(const std::string& color_in, ColorRGB& color_out)
+{
+ ColorRGBA rgba;
+ if (!decode_color(color_in, rgba))
+ return false;
+
+ color_out = to_rgb(rgba);
+ return true;
+}
+
+bool decode_color(const std::string& color_in, ColorRGBA& color_out)
+{
+ auto hex_digit_to_int = [](const char c) {
+ return
+ (c >= '0' && c <= '9') ? int(c - '0') :
+ (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
+ (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
+ };
+
+ color_out = ColorRGBA::BLACK();
+ if (can_decode_color(color_in)) {
+ const char *c = color_in.data() + 1;
+ if (color_in.size() == 7) {
+ for (unsigned int i = 0; i < 3; ++i) {
+ const int digit1 = hex_digit_to_int(*c++);
+ const int digit2 = hex_digit_to_int(*c++);
+ if (digit1 != -1 && digit2 != -1)
+ color_out.set(i, float(digit1 * 16 + digit2) * INV_255);
+ }
+ } else {
+ for (unsigned int i = 0; i < 4; ++i) {
+ const int digit1 = hex_digit_to_int(*c++);
+ const int digit2 = hex_digit_to_int(*c++);
+ if (digit1 != -1 && digit2 != -1)
+ color_out.set(i, float(digit1 * 16 + digit2) * INV_255);
+ }
+ }
+ } else
+ return false;
+
+ assert(0.0f <= color_out.r() && color_out.r() <= 1.0f);
+ assert(0.0f <= color_out.g() && color_out.g() <= 1.0f);
+ assert(0.0f <= color_out.b() && color_out.b() <= 1.0f);
+ assert(0.0f <= color_out.a() && color_out.a() <= 1.0f);
+ return true;
+}
+
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out)
+{
+ colors_out = std::vector(colors_in.size(), ColorRGB::BLACK());
+ for (size_t i = 0; i < colors_in.size(); ++i) {
+ if (!decode_color(colors_in[i], colors_out[i]))
+ return false;
+ }
+ return true;
+}
+
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out)
+{
+ colors_out = std::vector(colors_in.size(), ColorRGBA::BLACK());
+ for (size_t i = 0; i < colors_in.size(); ++i) {
+ if (!decode_color(colors_in[i], colors_out[i]))
+ return false;
+ }
+ return true;
+}
+
+std::string encode_color(const ColorRGB& color)
+{
+ char buffer[64];
+ ::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar());
+ return std::string(buffer);
+}
+
+std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); }
+
+ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; }
+ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; }
+ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; }
+
+ColorRGBA picking_decode(unsigned int id)
+{
+ return {
+ float((id >> 0) & 0xff) * INV_255, // red
+ float((id >> 8) & 0xff) * INV_255, // green
+ float((id >> 16) & 0xff) * INV_255, // blue
+ float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling
+ };
+}
+
+unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); }
+
+unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue)
+{
+ // 8 bit hash for the color
+ unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff;
+ // Increase enthropy by a bit reversal
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ // Flip every second bit to increase the enthropy even more.
+ b ^= 0x55;
+ return b;
+}
+
+} // namespace Slic3r
+
diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp
new file mode 100644
index 00000000000..ea17328bec1
--- /dev/null
+++ b/src/libslic3r/Color.hpp
@@ -0,0 +1,175 @@
+#ifndef slic3r_Color_hpp_
+#define slic3r_Color_hpp_
+
+#include
+#include
+
+namespace Slic3r {
+
+class ColorRGB
+{
+ std::array m_data{1.0f, 1.0f, 1.0f};
+
+public:
+ ColorRGB() = default;
+ ColorRGB(float r, float g, float b);
+ ColorRGB(unsigned char r, unsigned char g, unsigned char b);
+ ColorRGB(const ColorRGB& other) = default;
+
+ ColorRGB& operator = (const ColorRGB& other) { m_data = other.m_data; return *this; }
+
+ bool operator == (const ColorRGB& other) const { return m_data == other.m_data; }
+ bool operator != (const ColorRGB& other) const { return !operator==(other); }
+ bool operator < (const ColorRGB& other) const;
+ bool operator > (const ColorRGB& other) const;
+
+ ColorRGB operator + (const ColorRGB& other) const;
+ ColorRGB operator * (float value) const;
+
+ const float* const data() const { return m_data.data(); }
+
+ float r() const { return m_data[0]; }
+ float g() const { return m_data[1]; }
+ float b() const { return m_data[2]; }
+
+ void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); }
+ void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); }
+ void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); }
+
+ void set(unsigned int comp, float value) {
+ assert(0 <= comp && comp <= 2);
+ m_data[comp] = std::clamp(value, 0.0f, 1.0f);
+ }
+
+ unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); }
+ unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); }
+ unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); }
+
+ static const ColorRGB BLACK() { return { 0.0f, 0.0f, 0.0f }; }
+ static const ColorRGB BLUE() { return { 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGB BLUEISH() { return { 0.5f, 0.5f, 1.0f }; }
+ static const ColorRGB CYAN() { return { 0.0f, 1.0f, 1.0f }; }
+ static const ColorRGB DARK_GRAY() { return { 0.25f, 0.25f, 0.25f }; }
+ static const ColorRGB DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f }; }
+ static const ColorRGB GRAY() { return { 0.5f, 0.5f, 0.5f }; }
+ static const ColorRGB GREEN() { return { 0.0f, 1.0f, 0.0f }; }
+ static const ColorRGB GREENISH() { return { 0.5f, 1.0f, 0.5f }; }
+ static const ColorRGB LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f }; }
+ static const ColorRGB MAGENTA() { return { 1.0f, 0.0f, 1.0f }; }
+ static const ColorRGB ORANGE() { return { 0.92f, 0.50f, 0.26f }; }
+ static const ColorRGB RED() { return { 1.0f, 0.0f, 0.0f }; }
+ static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; }
+ static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; }
+ static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; }
+
+ static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; }
+ static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; }
+ static const ColorRGB Z() { return { 0.0f, 0.0f, 0.75f }; }
+};
+
+class ColorRGBA
+{
+ std::array m_data{ 1.0f, 1.0f, 1.0f, 1.0f };
+
+public:
+ ColorRGBA() = default;
+ ColorRGBA(float r, float g, float b, float a);
+ ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+ ColorRGBA(const ColorRGBA& other) = default;
+
+ ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; }
+
+ bool operator == (const ColorRGBA& other) const { return m_data == other.m_data; }
+ bool operator != (const ColorRGBA& other) const { return !operator==(other); }
+ bool operator < (const ColorRGBA& other) const;
+ bool operator > (const ColorRGBA& other) const;
+
+ ColorRGBA operator + (const ColorRGBA& other) const;
+ ColorRGBA operator * (float value) const;
+
+ const float* const data() const { return m_data.data(); }
+
+ float r() const { return m_data[0]; }
+ float g() const { return m_data[1]; }
+ float b() const { return m_data[2]; }
+ float a() const { return m_data[3]; }
+
+ void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); }
+ void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); }
+ void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); }
+ void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); }
+
+ void set(unsigned int comp, float value) {
+ assert(0 <= comp && comp <= 3);
+ m_data[comp] = std::clamp(value, 0.0f, 1.0f);
+ }
+
+ unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); }
+ unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); }
+ unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); }
+ unsigned char a_uchar() const { return static_cast(m_data[3] * 255.0f); }
+
+ bool is_transparent() const { return m_data[3] < 1.0f; }
+
+ static const ColorRGBA BLACK() { return { 0.0f, 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA BLUE() { return { 0.0f, 0.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA BLUEISH() { return { 0.5f, 0.5f, 1.0f, 1.0f }; }
+ static const ColorRGBA CYAN() { return { 0.0f, 1.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA DARK_GRAY() { return { 0.25f, 0.25f, 0.25f, 1.0f }; }
+ static const ColorRGBA DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f, 1.0f }; }
+ static const ColorRGBA GRAY() { return { 0.5f, 0.5f, 0.5f, 1.0f }; }
+ static const ColorRGBA GREEN() { return { 0.0f, 1.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA GREENISH() { return { 0.5f, 1.0f, 0.5f, 1.0f }; }
+ static const ColorRGBA LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f, 1.0f }; }
+ static const ColorRGBA MAGENTA() { return { 1.0f, 0.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA ORANGE() { return { 0.923f, 0.504f, 0.264f, 1.0f }; }
+ static const ColorRGBA RED() { return { 1.0f, 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA REDISH() { return { 1.0f, 0.5f, 0.5f, 1.0f }; }
+ static const ColorRGBA YELLOW() { return { 1.0f, 1.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA WHITE() { return { 1.0f, 1.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA ORCA() { return {0.0f, 150.f / 255.0f, 136.0f / 255, 1.0f}; }
+
+ static const ColorRGBA X() { return { 0.75f, 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA Y() { return { 0.0f, 0.75f, 0.0f, 1.0f }; }
+ static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; }
+};
+
+ColorRGB operator * (float value, const ColorRGB& other);
+ColorRGBA operator * (float value, const ColorRGBA& other);
+
+ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t);
+ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t);
+
+ColorRGB complementary(const ColorRGB& color);
+ColorRGBA complementary(const ColorRGBA& color);
+
+ColorRGB saturate(const ColorRGB& color, float factor);
+ColorRGBA saturate(const ColorRGBA& color, float factor);
+
+ColorRGB opposite(const ColorRGB& color);
+ColorRGB opposite(const ColorRGB& a, const ColorRGB& b);
+
+bool can_decode_color(const std::string& color);
+
+bool decode_color(const std::string& color_in, ColorRGB& color_out);
+bool decode_color(const std::string& color_in, ColorRGBA& color_out);
+
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out);
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out);
+
+std::string encode_color(const ColorRGB& color);
+std::string encode_color(const ColorRGBA& color);
+
+ColorRGB to_rgb(const ColorRGBA& other_rgba);
+ColorRGBA to_rgba(const ColorRGB& other_rgb);
+ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha);
+
+ColorRGBA picking_decode(unsigned int id);
+unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b);
+// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components
+// were not interpolated by alpha blending or multi sampling.
+unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
+
+} // namespace Slic3r
+
+#endif /* slic3r_Color_hpp_ */
diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp
new file mode 100644
index 00000000000..cdda3097aca
--- /dev/null
+++ b/src/libslic3r/CutUtils.cpp
@@ -0,0 +1,662 @@
+///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+
+#include "CutUtils.hpp"
+#include "Geometry.hpp"
+#include "libslic3r.h"
+#include "Model.hpp"
+#include "TriangleMeshSlicer.hpp"
+#include "TriangleSelector.hpp"
+#include "ObjectID.hpp"
+
+#include
+
+namespace Slic3r {
+
+using namespace Geometry;
+
+static void apply_tolerance(ModelVolume* vol)
+{
+ ModelVolume::CutInfo& cut_info = vol->cut_info;
+
+ assert(cut_info.is_connector);
+ if (!cut_info.is_processed)
+ return;
+
+ Vec3d sf = vol->get_scaling_factor();
+
+ // make a "hole" wider
+ sf[X] += double(cut_info.radius_tolerance);
+ sf[Y] += double(cut_info.radius_tolerance);
+
+ // make a "hole" dipper
+ sf[Z] += double(cut_info.height_tolerance);
+
+ vol->set_scaling_factor(sf);
+
+ // correct offset in respect to the new depth
+ Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ();
+ if (rot_norm.norm() != 0.0)
+ rot_norm.normalize();
+
+ double z_offset = 0.5 * static_cast(cut_info.height_tolerance);
+ if (cut_info.connector_type == CutConnectorType::Plug ||
+ cut_info.connector_type == CutConnectorType::Snap)
+ z_offset -= 0.05; // add small Z offset to better preview
+
+ vol->set_offset(vol->get_offset() + rot_norm * z_offset);
+}
+
+static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
+{
+ if (mesh.empty())
+ return;
+
+ mesh.transform(cut_matrix);
+ ModelVolume* vol = object->add_volume(mesh);
+ vol->set_type(type);
+
+ vol->name = src_volume->name + suffix;
+ // Don't copy the config's ID.
+ vol->config.assign_config(src_volume->config);
+ assert(vol->config.id().valid());
+ assert(vol->config.id() != src_volume->config.id());
+ vol->set_material(src_volume->material_id(), *src_volume->material());
+ vol->cut_info = src_volume->cut_info;
+}
+
+static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
+{
+ const auto volume_matrix = volume->get_matrix();
+
+ const Transformation cut_transformation = Transformation(cut_matrix);
+ const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
+
+ // Transform the mesh by the combined transformation matrix.
+ // Flip the triangles in case the composite transformation is left handed.
+ TriangleMesh mesh(volume->mesh());
+ mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
+
+ indexed_triangle_set upper_its, lower_its;
+ cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper))
+ upper_mesh = TriangleMesh(upper_its);
+ if (attributes.has(ModelObjectCutAttribute::KeepLower))
+ lower_mesh = TriangleMesh(lower_its);
+}
+
+static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
+ std::vector& dowels)
+{
+ assert(volume->cut_info.is_connector);
+ volume->cut_info.set_processed();
+
+ const auto volume_matrix = volume->get_matrix();
+
+ // ! Don't apply instance transformation for the conntectors.
+ // This transformation is already there
+ if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
+ ModelVolume* vol = nullptr;
+ if (volume->cut_info.connector_type == CutConnectorType::Snap) {
+ TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.));
+
+ vol = upper->add_volume(std::move(mesh));
+ vol->set_transformation(volume->get_transformation());
+ vol->set_type(ModelVolumeType::NEGATIVE_VOLUME);
+
+ vol->cut_info = volume->cut_info;
+ vol->name = volume->name;
+ }
+ else
+ vol = upper->add_volume(*volume);
+
+ vol->set_transformation(volume_matrix);
+ apply_tolerance(vol);
+ }
+ if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
+ ModelVolume* vol = lower->add_volume(*volume);
+ vol->set_transformation(volume_matrix);
+ // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
+ vol->set_type(ModelVolumeType::MODEL_PART);
+ }
+ }
+ else {
+ if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
+ ModelObject* dowel{ nullptr };
+ // Clone the object to duplicate instances, materials etc.
+ volume->get_object()->clone_for_cut(&dowel);
+
+ // add one more solid part same as connector if this connector is a dowel
+ ModelVolume* vol = dowel->add_volume(*volume);
+ vol->set_type(ModelVolumeType::MODEL_PART);
+
+ // But discard rotation and Z-offset for this volume
+ vol->set_rotation(Vec3d::Zero());
+ vol->set_offset(Z, 0.0);
+
+ dowels.push_back(dowel);
+ }
+
+ // Cut the dowel
+ apply_tolerance(volume);
+
+ // Perform cut
+ TriangleMesh upper_mesh, lower_mesh;
+ process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
+
+ // add small Z offset to better preview
+ upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast());
+ lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast());
+
+ // Add cut parts to the related objects
+ add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
+ add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
+ }
+}
+
+static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
+ ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
+{
+ const auto volume_matrix = instance_matrix * volume->get_matrix();
+
+ // Modifiers are not cut, but we still need to add the instance transformation
+ // to the modifier volume transformation to preserve their shape properly.
+ volume->set_transformation(Transformation(volume_matrix));
+
+ if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
+ upper->add_volume(*volume);
+ return;
+ }
+
+ // Some logic for the negative volumes/connectors. Add only needed modifiers
+ auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
+ bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
+ upper->add_volume(*volume);
+ if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
+ lower->add_volume(*volume);
+}
+
+static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
+{
+ // Perform cut
+ TriangleMesh upper_mesh, lower_mesh;
+ process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
+
+ // Add required cut parts to the objects
+
+ if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
+ add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
+ if (!lower_mesh.empty()) {
+ add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
+ upper->volumes.back()->cut_info.is_from_upper = false;
+ }
+ return;
+ }
+
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper))
+ add_cut_volume(upper_mesh, upper, volume, cut_matrix);
+
+ if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
+ add_cut_volume(lower_mesh, lower, volume, cut_matrix);
+}
+
+static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx,
+ const Transform3d& cut_matrix = Transform3d::Identity(),
+ bool place_on_cut = false, bool flip = false)
+{
+ // Reset instance transformation except offset and Z-rotation
+
+ for (size_t i = 0; i < object->instances.size(); ++i) {
+ auto& obj_instance = object->instances[i];
+ const double rot_z = obj_instance->get_rotation().z();
+
+ Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix(false, false, true));
+ // add respect to mirroring
+ if (obj_instance->is_left_handed())
+ inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
+
+ obj_instance->set_transformation(inst_trafo);
+
+ Vec3d rotation = Vec3d::Zero();
+ if (!flip && !place_on_cut) {
+ if ( i != src_instance_idx)
+ rotation[Z] = rot_z;
+ }
+ else {
+ Transform3d rotation_matrix = Transform3d::Identity();
+ if (flip)
+ rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
+
+ if (place_on_cut)
+ rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
+
+ if (i != src_instance_idx)
+ rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
+
+ rotation = Transformation(rotation_matrix).get_rotation();
+ }
+
+ obj_instance->set_rotation(rotation);
+ }
+}
+
+
+Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/)
+ : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes)
+{
+ m_model = Model();
+ if (object)
+ m_model.add_object(*object);
+}
+
+void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip)
+{
+ if (!object) return;
+
+ if (keep && !object->volumes.empty()) {
+ reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip);
+ cut_object_ptrs.push_back(object);
+ }
+ else
+ m_model.objects.push_back(object); // will be deleted in m_model.clear_objects();
+}
+
+void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs)
+{
+ post_process(upper, cut_object_ptrs,
+ m_attributes.has(ModelObjectCutAttribute::KeepUpper),
+ m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
+ m_attributes.has(ModelObjectCutAttribute::FlipUpper));
+
+ post_process(lower, cut_object_ptrs,
+ m_attributes.has(ModelObjectCutAttribute::KeepLower),
+ m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
+ m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
+}
+
+
+void Cut::finalize(const ModelObjectPtrs& objects)
+{
+ //clear model from temporarry objects
+ m_model.clear_objects();
+
+ // add to model result objects
+ m_model.objects = objects;
+}
+
+
+const ModelObjectPtrs& Cut::perform_with_plane()
+{
+ if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) {
+ m_model.clear_objects();
+ return m_model.objects;
+ }
+
+ ModelObject* mo = m_model.objects.front();
+
+ BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
+
+ // Clone the object to duplicate instances, materials etc.
+ ModelObject* upper{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepUpper))
+ mo->clone_for_cut(&upper);
+
+ ModelObject* lower{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::KeepAsParts))
+ mo->clone_for_cut(&lower);
+
+ std::vector dowels;
+
+ // Because transformations are going to be applied to meshes directly,
+ // we reset transformation of all instances and volumes,
+ // except for translation and Z-rotation on instances, which are preserved
+ // in the transformation matrix and not applied to the mesh transform.
+
+ const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix(true);
+ const Transformation cut_transformation = Transformation(m_cut_matrix);
+ const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
+
+ for (ModelVolume* volume : mo->volumes) {
+ volume->reset_extra_facets();
+
+ if (!volume->is_model_part()) {
+ if (volume->cut_info.is_processed)
+ process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower);
+ else
+ process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels);
+ }
+ else if (!volume->mesh().empty())
+ process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower);
+ }
+
+ // Post-process cut parts
+
+ if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) {
+ m_model = Model();
+ m_model.objects.push_back(upper);
+ return m_model.objects;
+ }
+
+ ModelObjectPtrs cut_object_ptrs;
+
+ if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
+ reset_instance_transformation(upper, m_instance, m_cut_matrix);
+ cut_object_ptrs.push_back(upper);
+ }
+ else {
+ // Delete all modifiers which are not intersecting with solid parts bounding box
+ auto delete_extra_modifiers = [this](ModelObject* mo) {
+ if (!mo) return;
+ const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance);
+ const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix();
+
+ for (int i = int(mo->volumes.size()) - 1; i >= 0; --i)
+ if (const ModelVolume* vol = mo->volumes[i];
+ !vol->is_model_part() && !vol->is_cut_connector()) {
+ auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
+ if (!obj_bb.intersects(bb))
+ mo->delete_volume(i);
+ }
+ };
+
+ post_process(upper, lower, cut_object_ptrs);
+ delete_extra_modifiers(upper);
+ delete_extra_modifiers(lower);
+
+ if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
+ for (auto dowel : dowels) {
+ reset_instance_transformation(dowel, m_instance);
+ dowel->name += "-Dowel-" + dowel->volumes[0]->name;
+ cut_object_ptrs.push_back(dowel);
+ }
+ }
+ }
+
+ BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
+
+ finalize(cut_object_ptrs);
+
+ return m_model.objects;
+}
+
+static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2)
+{
+ auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3();
+ auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3();
+ const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix();
+
+ for (ModelVolume* vol : from_obj->volumes)
+ if (!vol->is_model_part()) {
+ // Don't add modifiers which are processed connectors
+ if (vol->cut_info.is_connector && !vol->cut_info.is_processed)
+ continue;
+ auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
+ // Don't add modifiers which are not intersecting with solid parts
+ if (obj1_bb.intersects(bb))
+ to_obj1->add_volume(*vol);
+ if (obj2_bb.intersects(bb))
+ to_obj2->add_volume(*vol);
+ }
+}
+
+static void merge_solid_parts_inside_object(ModelObjectPtrs& objects)
+{
+ for (ModelObject* mo : objects) {
+ TriangleMesh mesh;
+ // Merge all SolidPart but not Connectors
+ for (const ModelVolume* mv : mo->volumes) {
+ if (mv->is_model_part() && !mv->is_cut_connector()) {
+ TriangleMesh m = mv->mesh();
+ m.transform(mv->get_matrix());
+ mesh.merge(m);
+ }
+ }
+ if (!mesh.empty()) {
+ ModelVolume* new_volume = mo->add_volume(mesh);
+ new_volume->name = mo->name;
+ // Delete all merged SolidPart but not Connectors
+ for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) {
+ const ModelVolume* mv = mo->volumes[i];
+ if (mv->is_model_part() && !mv->is_cut_connector())
+ mo->delete_volume(i);
+ }
+ // Ensuring that volumes start with solid parts for proper slicing
+ mo->sort_volumes(true);
+ }
+ }
+}
+
+
+const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowels_count)
+{
+ ModelObject* cut_mo = m_model.objects.front();
+
+ // Clone the object to duplicate instances, materials etc.
+ ModelObject* upper{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper);
+ ModelObject* lower{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower);
+
+ const size_t cut_parts_cnt = parts.size();
+ bool has_modifiers = false;
+
+ // Distribute SolidParts to the Upper/Lower object
+ for (size_t id = 0; id < cut_parts_cnt; ++id) {
+ if (parts[id].is_modifier)
+ has_modifiers = true; // modifiers will be added later to the related parts
+ else if (ModelObject* obj = (parts[id].selected ? upper : lower))
+ obj->add_volume(*(cut_mo->volumes[id]));
+ }
+
+ if (has_modifiers) {
+ // Distribute Modifiers to the Upper/Lower object
+ distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
+ }
+
+ ModelObjectPtrs cut_object_ptrs;
+
+ ModelVolumePtrs& volumes = cut_mo->volumes;
+ if (volumes.size() == cut_parts_cnt) {
+ // Means that object is cut without connectors
+
+ // Just add Upper and Lower objects to cut_object_ptrs
+ post_process(upper, lower, cut_object_ptrs);
+
+ // Now merge all model parts together:
+ merge_solid_parts_inside_object(cut_object_ptrs);
+
+ // replace initial objects in model with cut object
+ finalize(cut_object_ptrs);
+ }
+ else if (volumes.size() > cut_parts_cnt) {
+ // Means that object is cut with connectors
+
+ // All volumes are distributed to Upper / Lower object,
+ // So we don’t need them anymore
+ for (size_t id = 0; id < cut_parts_cnt; id++)
+ delete* (volumes.begin() + id);
+ volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
+
+ // Perform cut just to get connectors
+ Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes);
+ const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane();
+ assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
+
+ // Connectors from upper object
+ for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
+ upper->add_volume(*volume, volume->type());
+
+ // Connectors from lower object
+ for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
+ lower->add_volume(*volume, volume->type());
+
+ // Add Upper and Lower objects to cut_object_ptrs
+ post_process(upper, lower, cut_object_ptrs);
+
+ // Now merge all model parts together:
+ merge_solid_parts_inside_object(cut_object_ptrs);
+
+ // replace initial objects in model with cut object
+ finalize(cut_object_ptrs);
+
+ // Add Dowel-connectors as separate objects to model
+ if (cut_connectors_obj.size() >= 3)
+ for (size_t id = 2; id < cut_connectors_obj.size(); id++)
+ m_model.add_object(*cut_connectors_obj[id]);
+ }
+
+ return m_model.objects;
+}
+
+
+const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/)
+{
+ ModelObject* cut_mo = m_model.objects.front();
+
+ // Clone the object to duplicate instances, materials etc.
+ ModelObject* upper{ nullptr };
+ cut_mo->clone_for_cut(&upper);
+ ModelObject* lower{ nullptr };
+ cut_mo->clone_for_cut(&lower);
+
+ const double groove_half_depth = 0.5 * double(groove.depth);
+
+ Model tmp_model_for_cut = Model();
+
+ Model tmp_model = Model();
+ tmp_model.add_object(*cut_mo);
+ ModelObject* tmp_object = tmp_model.objects.front();
+
+ auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) {
+ const auto& volumes = tmp_model_for_cut.objects.front()->volumes;
+ for (const ModelVolume* volume : volumes)
+ if (volume->is_model_part()) {
+ if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) ||
+ (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) {
+ ModelVolume* new_vol = object->add_volume(*volume);
+ new_vol->reset_from_upper();
+ }
+ }
+ };
+
+ auto cut = [this, add_volumes_from_cut]
+ (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) {
+ Cut cut(object, m_instance, cut_matrix);
+
+ tmp_model_for_cut = Model();
+ tmp_model_for_cut.add_object(*cut.perform_with_plane().front());
+ assert(!tmp_model_for_cut.objects.empty());
+
+ object->clear_volumes();
+ add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
+ reset_instance_transformation(object, m_instance);
+ };
+
+ // cut by upper plane
+
+ const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
+ {
+ cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // cut by lower plane
+
+ const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
+ {
+ cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ }
+
+ // cut middle part with 2 angles and add parts to related upper/lower objects
+
+ const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
+
+ // cut by angle1 plane
+ {
+ const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
+
+ cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // cut by angle2 plane
+ {
+ const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
+
+ cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // apply tolerance to the middle part
+ {
+ const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
+
+ const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
+ cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+
+ const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
+
+ const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
+ cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+
+ const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
+ cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // this part can be added to the upper object now
+ add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+
+ ModelObjectPtrs cut_object_ptrs;
+
+ if (keep_as_parts) {
+ // add volumes from lower object to the upper, but mark them as a lower
+ const auto& volumes = lower->volumes;
+ for (const ModelVolume* volume : volumes) {
+ ModelVolume* new_vol = upper->add_volume(*volume);
+ new_vol->cut_info.is_from_upper = false;
+ }
+
+ // add modifiers
+ for (const ModelVolume* volume : cut_mo->volumes)
+ if (!volume->is_model_part())
+ upper->add_volume(*volume);
+
+ cut_object_ptrs.push_back(upper);
+
+ // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks
+ cut_object_ptrs.push_back(lower);
+ }
+ else {
+ // add modifiers if object has any
+ for (const ModelVolume* volume : cut_mo->volumes)
+ if (!volume->is_model_part()) {
+ distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
+ break;
+ }
+
+ assert(!upper->volumes.empty() && !lower->volumes.empty());
+
+ // Add Upper and Lower parts to cut_object_ptrs
+
+ post_process(upper, lower, cut_object_ptrs);
+
+ // Now merge all model parts together:
+ merge_solid_parts_inside_object(cut_object_ptrs);
+ }
+
+ finalize(cut_object_ptrs);
+
+ return m_model.objects;
+}
+
+} // namespace Slic3r
+
diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp
new file mode 100644
index 00000000000..5067aaaff1b
--- /dev/null
+++ b/src/libslic3r/CutUtils.hpp
@@ -0,0 +1,70 @@
+///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#ifndef slic3r_CutUtils_hpp_
+#define slic3r_CutUtils_hpp_
+
+#include "enum_bitmask.hpp"
+#include "Point.hpp"
+#include "Model.hpp"
+
+#include
+
+namespace Slic3r {
+
+using ModelObjectPtrs = std::vector;
+
+enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
+using ModelObjectCutAttributes = enum_bitmask;
+ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
+
+
+class Cut {
+
+ Model m_model;
+ int m_instance;
+ const Transform3d m_cut_matrix;
+ ModelObjectCutAttributes m_attributes;
+
+ void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip);
+ void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects);
+ void finalize(const ModelObjectPtrs& objects);
+
+public:
+
+ Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper |
+ ModelObjectCutAttribute::KeepLower |
+ ModelObjectCutAttribute::KeepAsParts );
+ ~Cut() { m_model.clear_objects(); }
+
+ struct Groove
+ {
+ float depth{ 0.f };
+ float width{ 0.f };
+ float flaps_angle{ 0.f };
+ float angle{ 0.f };
+ float depth_init{ 0.f };
+ float width_init{ 0.f };
+ float flaps_angle_init{ 0.f };
+ float angle_init{ 0.f };
+ float depth_tolerance{ 0.1f };
+ float width_tolerance{ 0.1f };
+ };
+
+ struct Part
+ {
+ bool selected;
+ bool is_modifier;
+ };
+
+ const ModelObjectPtrs& perform_with_plane();
+ const ModelObjectPtrs& perform_by_contour(std::vector parts, int dowels_count);
+ const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false);
+
+}; // namespace Cut
+
+} // namespace Slic3r
+
+#endif /* slic3r_CutUtils_hpp_ */
diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp
index 391ac2d5872..e6ae1edd4d3 100644
--- a/src/libslic3r/ExtrusionEntityCollection.cpp
+++ b/src/libslic3r/ExtrusionEntityCollection.cpp
@@ -56,12 +56,9 @@ ExtrusionEntityCollection::operator ExtrusionPaths() const
return paths;
}
-ExtrusionEntity* ExtrusionEntityCollection::clone() const
+ExtrusionEntity *ExtrusionEntityCollection::clone() const
{
- ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this);
- for (size_t i = 0; i < coll->entities.size(); ++i)
- coll->entities[i] = this->entities[i]->clone();
- return coll;
+ return new ExtrusionEntityCollection(*this);
}
void ExtrusionEntityCollection::reverse()
diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp
index 6be38e14a14..c616d0f1b4d 100644
--- a/src/libslic3r/Format/bbs_3mf.cpp
+++ b/src/libslic3r/Format/bbs_3mf.cpp
@@ -1,3 +1,8 @@
+///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros
+///|/ Copyright (c) 2020 Henner Zeller
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#include "../libslic3r.h"
#include "../Exception.hpp"
#include "../Model.hpp"
@@ -741,8 +746,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
{
int volume_id;
int type;
- float radius;
- float height;
float r_tolerance;
float h_tolerance;
};
@@ -758,10 +761,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//typedef std::map IdToAliasesMap;
typedef std::vector InstancesList;
typedef std::map IdToMetadataMap;
- typedef std::map IdToCutObjectInfoMap;
//typedef std::map IdToGeometryMap;
typedef std::map> IdToLayerHeightsProfileMap;
typedef std::map IdToLayerConfigRangesMap;
+ typedef std::map IdToCutObjectInfoMap;
/*typedef std::map> IdToSlaSupportPointsMap;
typedef std::map> IdToSlaDrainHolesMap;*/
@@ -951,7 +954,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//IdToGeometryMap m_orig_geometries; // backup & restore
CurrentConfig m_curr_config;
IdToMetadataMap m_objects_metadata;
- IdToCutObjectInfoMap m_cut_object_infos;
+ IdToCutObjectInfoMap m_cut_object_infos;
IdToLayerHeightsProfileMap m_layer_heights_profiles;
IdToLayerConfigRangesMap m_layer_config_ranges;
/*IdToSlaSupportPointsMap m_sla_support_points;
@@ -1013,7 +1016,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
- void _extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions);
+ void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
@@ -1955,11 +1958,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1);
if (cut_object_info != m_cut_object_infos.end()) {
model_object->cut_id = cut_object_info->second.id;
-
+ int vol_cnt = int(model_object->volumes.size());
for (auto connector : cut_object_info->second.connectors) {
- assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size()));
- model_object->volumes[connector.volume_id]->cut_info =
- ModelVolume::CutInfo(CutConnectorType(connector.type), connector.radius, connector.height, connector.r_tolerance, connector.h_tolerance, true);
+ if (connector.volume_id < 0 || connector.volume_id >= vol_cnt) {
+ add_error("Invalid connector is found");
+ continue;
+ }
+ model_object->volumes[connector.volume_id]->cut_info =
+ ModelVolume::CutInfo(CutConnectorType(connector.type), connector.r_tolerance, connector.h_tolerance, true);
}
}
}
@@ -2324,7 +2330,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
void _BBS_3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions)
{
if (stat.m_uncomp_size > 0) {
- std::string buffer((size_t) stat.m_uncomp_size, 0);
+ std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void *) buffer.data(), (size_t) stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading cut information data to buffer");
@@ -2332,12 +2338,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
std::istringstream iss(buffer); // wrap returned xml to istringstream
- pt::ptree objects_tree;
+ pt::ptree objects_tree;
pt::read_xml(iss, objects_tree);
- for (const auto &object : objects_tree.get_child("objects")) {
+ for (const auto& object : objects_tree.get_child("objects")) {
pt::ptree object_tree = object.second;
- int obj_idx = object_tree.get(".id", -1);
+ int obj_idx = object_tree.get(".id", -1);
if (obj_idx <= 0) {
add_error("Found invalid object id");
continue;
@@ -2349,30 +2355,33 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
continue;
}
- CutObjectBase cut_id;
- std::vector connectors;
+ CutObjectBase cut_id;
+ std::vector connectors;
- for (const auto &obj_cut_info : object_tree) {
+ for (const auto& obj_cut_info : object_tree) {
if (obj_cut_info.first == "cut_id") {
pt::ptree cut_id_tree = obj_cut_info.second;
- cut_id = CutObjectBase(ObjectID(cut_id_tree.get(".id")), cut_id_tree.get(".check_sum"),
- cut_id_tree.get(".connectors_cnt"));
+ cut_id = CutObjectBase(ObjectID( cut_id_tree.get(".id")),
+ cut_id_tree.get(".check_sum"),
+ cut_id_tree.get(".connectors_cnt"));
}
if (obj_cut_info.first == "connectors") {
pt::ptree cut_connectors_tree = obj_cut_info.second;
- for (const auto &cut_connector : cut_connectors_tree) {
- if (cut_connector.first != "connector") continue;
- pt::ptree connector_tree = cut_connector.second;
- CutObjectInfo::Connector connector = {connector_tree.get(".volume_id"), connector_tree.get(".type"),
- connector_tree.get(".radius", 0.f), connector_tree.get(".height", 0.f),
- connector_tree.get(".r_tolerance"), connector_tree.get(".h_tolerance")};
+ for (const auto& cut_connector : cut_connectors_tree) {
+ if (cut_connector.first != "connector")
+ continue;
+ pt::ptree connector_tree = cut_connector.second;
+ CutObjectInfo::Connector connector = {connector_tree.get(".volume_id"),
+ connector_tree.get(".type"),
+ connector_tree.get(".r_tolerance"),
+ connector_tree.get(".h_tolerance")};
connectors.emplace_back(connector);
}
}
}
- CutObjectInfo cut_info{cut_id, connectors};
- m_cut_object_infos.insert({obj_idx, cut_info});
+ CutObjectInfo cut_info {cut_id, connectors};
+ m_cut_object_infos.insert({ obj_idx, cut_info });
}
}
}
@@ -5260,6 +5269,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//BBS: change volume to seperate objects
bool _add_mesh_to_object_stream(std::function const &flush, ObjectData const &object_data) const;
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const;
+ bool _add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
@@ -5270,7 +5280,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//BBS: add project embedded preset files
bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector project_presets);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false);
- bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model);
bool _add_slice_info_config_file_to_archive(mz_zip_archive &archive, const Model &model, PlateDataPtrs &plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config);
bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr);
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
@@ -6233,7 +6242,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
continue;
volume_count++;
if (m_share_mesh) {
- auto iter = m_shared_meshes.find(volume->mesh_ptr());
+ auto iter = m_shared_meshes.find(volume->mesh_ptr().get());
if (iter != m_shared_meshes.end())
{
const ModelVolume* shared_volume = iter->second.second;
@@ -6248,7 +6257,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
continue;
}
}
- const_cast<_BBS_3MF_Exporter *>(this)->m_shared_meshes.insert({volume->mesh_ptr(), {&object_data, volume}});
+ const_cast<_BBS_3MF_Exporter *>(this)->m_shared_meshes.insert({volume->mesh_ptr().get(), {&object_data, volume}});
}
if (m_from_backup_save)
volume_id = (volume_count << 16 | backup_id);
@@ -6704,6 +6713,69 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
+ bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model)
+ {
+ std::string out = "";
+ pt::ptree tree;
+
+ unsigned int object_cnt = 0;
+ for (const ModelObject* object : model.objects) {
+ object_cnt++;
+ if (!object->is_cut())
+ continue;
+ pt::ptree& obj_tree = tree.add("objects.object", "");
+
+ obj_tree.put(".id", object_cnt);
+
+ // Store info for cut_id
+ pt::ptree& cut_id_tree = obj_tree.add("cut_id", "");
+
+ // store cut_id atributes
+ cut_id_tree.put(".id", object->cut_id.id().id);
+ cut_id_tree.put(".check_sum", object->cut_id.check_sum());
+ cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt());
+
+ int volume_idx = -1;
+ for (const ModelVolume* volume : object->volumes) {
+ ++volume_idx;
+ if (volume->is_cut_connector()) {
+ pt::ptree& connectors_tree = obj_tree.add("connectors.connector", "");
+ connectors_tree.put(".volume_id", volume_idx);
+ connectors_tree.put(".type", int(volume->cut_info.connector_type));
+ connectors_tree.put(".r_tolerance", volume->cut_info.radius_tolerance);
+ connectors_tree.put(".h_tolerance", volume->cut_info.height_tolerance);
+ }
+ }
+ }
+
+ if (!tree.empty()) {
+ std::ostringstream oss;
+ pt::write_xml(oss, tree);
+ out = oss.str();
+
+ // Post processing("beautification") of the output string for a better preview
+ boost::replace_all(out, ">");
+ // OR just
+ boost::replace_all(out, "><", ">\n<");
+ }
+
+ if (!out.empty()) {
+ if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
+ add_error("Unable to add cut information file to archive");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
bool _BBS_3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
{
assert(is_decimal_separator_point());
@@ -7266,69 +7338,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
- bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model)
- {
- std::string out = "";
- pt::ptree tree;
-
- unsigned int object_cnt = 0;
- for (const ModelObject *object : model.objects) {
- object_cnt++;
- pt::ptree &obj_tree = tree.add("objects.object", "");
-
- obj_tree.put(".id", object_cnt);
-
- // Store info for cut_id
- pt::ptree &cut_id_tree = obj_tree.add("cut_id", "");
-
- // store cut_id atributes
- cut_id_tree.put(".id", object->cut_id.id().id);
- cut_id_tree.put(".check_sum", object->cut_id.check_sum());
- cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt());
-
- int volume_idx = -1;
- for (const ModelVolume *volume : object->volumes) {
- ++volume_idx;
- if (volume->is_cut_connector()) {
- pt::ptree &connectors_tree = obj_tree.add("connectors.connector", "");
- connectors_tree.put(".volume_id", volume_idx);
- connectors_tree.put(".type", int(volume->cut_info.connector_type));
- connectors_tree.put(".radius", volume->cut_info.radius);
- connectors_tree.put(".height", volume->cut_info.height);
- connectors_tree.put(".r_tolerance", volume->cut_info.radius_tolerance);
- connectors_tree.put(".h_tolerance", volume->cut_info.height_tolerance);
- }
- }
- }
-
- if (!tree.empty()) {
- std::ostringstream oss;
- pt::write_xml(oss, tree);
- out = oss.str();
-
- // Post processing("beautification") of the output string for a better preview
- boost::replace_all(out, ">\n \n ", ">\n ");
- boost::replace_all(out, ">\n ", ">\n ");
- boost::replace_all(out, ">\n ", ">\n ");
- boost::replace_all(out, ">", ">\n ");
- // OR just
- boost::replace_all(out, "><", ">\n<");
- }
-
- if (!out.empty()) {
- if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void *) out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
- add_error("Unable to add cut information file to archive");
- return false;
- }
- }
-
- return true;
- }
-
bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config)
{
std::stringstream stream;
diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp
index a2c59ec17d5..e57b774f34e 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -1,3 +1,17 @@
+///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Tomáš Mészáros @tamasmeszaros
+///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
+///|/
+///|/ ported from lib/Slic3r/Geometry.pm:
+///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv
+///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel
+///|/ Copyright (c) 2013 Jose Luis Perez Diez
+///|/ Copyright (c) 2013 Anders Sundman
+///|/ Copyright (c) 2013 Jesse Vincent
+///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake
+///|/ Copyright (c) 2012 Mark Hindess
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#include "libslic3r.h"
#include "Exception.hpp"
#include "Geometry.hpp"
@@ -320,46 +334,103 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation,
return transform;
}
+void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror)
+{
+ transform = translation * rotation * scale * mirror;
+}
+
+Transform3d assemble_transform(const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror)
+{
+ Transform3d transform;
+ assemble_transform(transform, translation, rotation, scale, mirror);
+ return transform;
+}
+
+void translation_transform(Transform3d& transform, const Vec3d& translation)
+{
+ transform = Transform3d::Identity();
+ transform.translate(translation);
+}
+
+Transform3d translation_transform(const Vec3d& translation)
+{
+ Transform3d transform;
+ translation_transform(transform, translation);
+ return transform;
+}
+
+void rotation_transform(Transform3d& transform, const Vec3d& rotation)
+{
+ transform = Transform3d::Identity();
+ transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX()));
+}
+
+Transform3d rotation_transform(const Vec3d& rotation)
+{
+ Transform3d transform;
+ rotation_transform(transform, rotation);
+ return transform;
+}
+
+void scale_transform(Transform3d& transform, double scale)
+{
+ return scale_transform(transform, scale * Vec3d::Ones());
+}
+
+void scale_transform(Transform3d& transform, const Vec3d& scale)
+{
+ transform = Transform3d::Identity();
+ transform.scale(scale);
+}
+
+Transform3d scale_transform(double scale)
+{
+ return scale_transform(scale * Vec3d::Ones());
+}
+
+Transform3d scale_transform(const Vec3d& scale)
+{
+ Transform3d transform;
+ scale_transform(transform, scale);
+ return transform;
+}
+
Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix)
{
// reference: http://www.gregslabaugh.net/publications/euler.pdf
Vec3d angles1 = Vec3d::Zero();
Vec3d angles2 = Vec3d::Zero();
// BBS: rotation_matrix(2, 0) may be slighterly larger than 1 due to numerical accuracy
- if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5 || std::abs(rotation_matrix(2, 0))>1)
- {
- angles1(2) = 0.0;
- if (rotation_matrix(2, 0) < 0.0) // == -1.0
- {
- angles1(1) = 0.5 * (double)PI;
- angles1(0) = angles1(2) + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2));
+ if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5 || std::abs(rotation_matrix(2, 0))>1) {
+ angles1.z() = 0.0;
+ if (rotation_matrix(2, 0) < 0.0) { // == -1.0
+ angles1.y() = 0.5 * double(PI);
+ angles1.x() = angles1.z() + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2));
}
- else // == 1.0
- {
- angles1(1) = - 0.5 * (double)PI;
- angles1(0) = - angles1(2) + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2));
+ else { // == 1.0
+ angles1.y() = - 0.5 * double(PI);
+ angles1.x() = - angles1.y() + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2));
}
angles2 = angles1;
}
- else
- {
- angles1(1) = -::asin(rotation_matrix(2, 0));
- double inv_cos1 = 1.0 / ::cos(angles1(1));
- angles1(0) = ::atan2(rotation_matrix(2, 1) * inv_cos1, rotation_matrix(2, 2) * inv_cos1);
- angles1(2) = ::atan2(rotation_matrix(1, 0) * inv_cos1, rotation_matrix(0, 0) * inv_cos1);
-
- angles2(1) = (double)PI - angles1(1);
- double inv_cos2 = 1.0 / ::cos(angles2(1));
- angles2(0) = ::atan2(rotation_matrix(2, 1) * inv_cos2, rotation_matrix(2, 2) * inv_cos2);
- angles2(2) = ::atan2(rotation_matrix(1, 0) * inv_cos2, rotation_matrix(0, 0) * inv_cos2);
+ else {
+ angles1.y() = -::asin(rotation_matrix(2, 0));
+ const double inv_cos1 = 1.0 / ::cos(angles1.y());
+ angles1.x() = ::atan2(rotation_matrix(2, 1) * inv_cos1, rotation_matrix(2, 2) * inv_cos1);
+ angles1.z() = ::atan2(rotation_matrix(1, 0) * inv_cos1, rotation_matrix(0, 0) * inv_cos1);
+
+ angles2.y() = double(PI) - angles1.y();
+ const double inv_cos2 = 1.0 / ::cos(angles2.y());
+ angles2.x() = ::atan2(rotation_matrix(2, 1) * inv_cos2, rotation_matrix(2, 2) * inv_cos2);
+ angles2.z() = ::atan2(rotation_matrix(1, 0) * inv_cos2, rotation_matrix(0, 0) * inv_cos2);
}
// The following euristic is the best found up to now (in the sense that it works fine with the greatest number of edge use-cases)
// but there are other use-cases were it does not
// We need to improve it
- double min_1 = angles1.cwiseAbs().minCoeff();
- double min_2 = angles2.cwiseAbs().minCoeff();
- bool use_1 = (min_1 < min_2) || (is_approx(min_1, min_2) && (angles1.norm() <= angles2.norm()));
+ const double min_1 = angles1.cwiseAbs().minCoeff();
+ const double min_2 = angles2.cwiseAbs().minCoeff();
+ const bool use_1 = (min_1 < min_2) || (is_approx(min_1, min_2) && (angles1.norm() <= angles2.norm()));
return use_1 ? angles1 : angles2;
}
@@ -375,6 +446,14 @@ Vec3d extract_euler_angles(const Transform3d& transform)
return extract_euler_angles(m);
}
+static Transform3d extract_rotation_matrix(const Transform3d& trafo)
+{
+ Matrix3d rotation;
+ Matrix3d scale;
+ trafo.computeRotationScaling(&rotation, &scale);
+ return Transform3d(rotation);
+}
+
void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix)
{
double epsilon = 1e-5;
@@ -409,28 +488,6 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, doubl
}
}
-Transform3d translation_transform(const Vec3d &translation)
-{
- Transform3d transform = Transform3d::Identity();
- transform.translate(translation);
- return transform;
-}
-
-Transform3d rotation_transform(const Vec3d& rotation)
-{
- Transform3d transform = Transform3d::Identity();
- transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX()));
- return transform;
-}
-
-Transformation::Flags::Flags()
- : dont_translate(true)
- , dont_rotate(true)
- , dont_scale(true)
- , dont_mirror(true)
-{
-}
-
bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const
{
return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror);
@@ -456,35 +513,38 @@ Transformation::Transformation(const Transform3d& transform)
void Transformation::set_offset(const Vec3d& offset)
{
- set_offset(X, offset(0));
- set_offset(Y, offset(1));
- set_offset(Z, offset(2));
+ set_offset(X, offset.x());
+ set_offset(Y, offset.y());
+ set_offset(Z, offset.z());
}
void Transformation::set_offset(Axis axis, double offset)
{
- if (m_offset(axis) != offset)
- {
+ if (m_offset(axis) != offset) {
m_offset(axis) = offset;
m_dirty = true;
}
}
+Transform3d Transformation::get_rotation_matrix() const
+{
+ return extract_rotation_matrix(m_matrix);
+}
+
void Transformation::set_rotation(const Vec3d& rotation)
{
- set_rotation(X, rotation(0));
- set_rotation(Y, rotation(1));
- set_rotation(Z, rotation(2));
+ set_rotation(X, rotation.x());
+ set_rotation(Y, rotation.y());
+ set_rotation(Z, rotation.z());
}
void Transformation::set_rotation(Axis axis, double rotation)
{
rotation = angle_to_0_2PI(rotation);
- if (is_approx(std::abs(rotation), 2.0 * (double)PI))
+ if (is_approx(std::abs(rotation), 2.0 * double(PI)))
rotation = 0.0;
- if (m_rotation(axis) != rotation)
- {
+ if (m_rotation(axis) != rotation) {
m_rotation(axis) = rotation;
m_dirty = true;
}
@@ -492,15 +552,14 @@ void Transformation::set_rotation(Axis axis, double rotation)
void Transformation::set_scaling_factor(const Vec3d& scaling_factor)
{
- set_scaling_factor(X, scaling_factor(0));
- set_scaling_factor(Y, scaling_factor(1));
- set_scaling_factor(Z, scaling_factor(2));
+ set_scaling_factor(X, scaling_factor.x());
+ set_scaling_factor(Y, scaling_factor.y());
+ set_scaling_factor(Z, scaling_factor.z());
}
void Transformation::set_scaling_factor(Axis axis, double scaling_factor)
{
- if (m_scaling_factor(axis) != std::abs(scaling_factor))
- {
+ if (m_scaling_factor(axis) != std::abs(scaling_factor)) {
m_scaling_factor(axis) = std::abs(scaling_factor);
m_dirty = true;
}
@@ -508,9 +567,9 @@ void Transformation::set_scaling_factor(Axis axis, double scaling_factor)
void Transformation::set_mirror(const Vec3d& mirror)
{
- set_mirror(X, mirror(0));
- set_mirror(Y, mirror(1));
- set_mirror(Z, mirror(2));
+ set_mirror(X, mirror.x());
+ set_mirror(Y, mirror.y());
+ set_mirror(Z, mirror.z());
}
void Transformation::set_mirror(Axis axis, double mirror)
@@ -521,8 +580,7 @@ void Transformation::set_mirror(Axis axis, double mirror)
else if (abs_mirror != 1.0)
mirror /= abs_mirror;
- if (m_mirror(axis) != mirror)
- {
+ if (m_mirror(axis) != mirror) {
m_mirror(axis) = mirror;
m_dirty = true;
}
@@ -540,9 +598,8 @@ void Transformation::set_from_transform(const Transform3d& transform)
// we can only detect if the matrix contains a left handed reference system
// in which case we reorient it back to right handed by mirroring the x axis
Vec3d mirror = Vec3d::Ones();
- if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0)
- {
- mirror(0) = -1.0;
+ if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) {
+ mirror.x() = -1.0;
// remove mirror
m3x3.col(0) *= -1.0;
}
@@ -579,8 +636,7 @@ void Transformation::reset()
const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const
{
- if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror))
- {
+ if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) {
m_matrix = Geometry::assemble_transform(
dont_translate ? Vec3d::Zero() : m_offset,
dont_rotate ? Vec3d::Zero() : m_rotation,
@@ -609,8 +665,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
// Just set the inverse.
out.set_from_transform(instance_transformation.get_matrix(true).inverse());
}
- else if (is_rotation_ninety_degrees(instance_transformation.get_rotation()))
- {
+ else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) {
// Anisotropic scaling, rotation by multiples of ninety degrees.
Eigen::Matrix3d instance_rotation_trafo =
(Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) *
@@ -643,8 +698,8 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i));
out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo));
- out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2))));
- out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1));
+ out.set_scaling_factor(Vec3d(std::abs(scale.x()), std::abs(scale.y()), std::abs(scale.z())));
+ out.set_mirror(Vec3d(scale.x() > 0 ? 1. : -1, scale.y() > 0 ? 1. : -1, scale.z() > 0 ? 1. : -1));
}
else
{
@@ -663,19 +718,15 @@ Transform3d transform3d_from_string(const std::string& transform_str)
assert(is_decimal_separator_point()); // for atof
Transform3d transform = Transform3d::Identity();
- if (!transform_str.empty())
- {
+ if (!transform_str.empty()) {
std::vector mat_elements_str;
boost::split(mat_elements_str, transform_str, boost::is_any_of(" "), boost::token_compress_on);
- unsigned int size = (unsigned int)mat_elements_str.size();
- if (size == 16)
- {
+ const unsigned int size = (unsigned int)mat_elements_str.size();
+ if (size == 16) {
unsigned int i = 0;
- for (unsigned int r = 0; r < 4; ++r)
- {
- for (unsigned int c = 0; c < 4; ++c)
- {
+ for (unsigned int r = 0; r < 4; ++r) {
+ for (unsigned int c = 0; c < 4; ++c) {
transform(r, c) = ::atof(mat_elements_str[i++].c_str());
}
}
@@ -689,17 +740,17 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot
{
return
// From the current coordinate system to world.
- Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) *
+ Eigen::AngleAxisd(rot_xyz_to.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to.x(), Vec3d::UnitX()) *
// From world to the initial coordinate system.
- Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ());
+ Eigen::AngleAxisd(-rot_xyz_from.x(), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from.z(), Vec3d::UnitZ());
}
// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis.
double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
{
- Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
- Vec3d axis = angle_axis.axis();
- double angle = angle_axis.angle();
+ const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
+ const Vec3d axis = angle_axis.axis();
+ const double angle = angle_axis.angle();
#ifndef NDEBUG
if (std::abs(angle) > 1e-8) {
assert(std::abs(axis.x()) < 1e-8);
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index 8eb6195a107..4f6511ff6fd 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -1,3 +1,18 @@
+///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Lukáš Hejl @hejllukas
+///|/ Copyright (c) 2017 Eyal Soha @eyal0
+///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
+///|/
+///|/ ported from lib/Slic3r/Geometry.pm:
+///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv
+///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel
+///|/ Copyright (c) 2013 Jose Luis Perez Diez
+///|/ Copyright (c) 2013 Anders Sundman
+///|/ Copyright (c) 2013 Jesse Vincent
+///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake
+///|/ Copyright (c) 2012 Mark Hindess
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#ifndef slic3r_Geometry_hpp_
#define slic3r_Geometry_hpp_
@@ -324,7 +339,8 @@ bool arrange(
// 4) rotate Y
// 5) rotate Z
// 6) translate
-void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
+void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(),
+ const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
// Returns the transform obtained by assembling the given transformations in the following order:
// 1) mirror
@@ -333,7 +349,45 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d
// 4) rotate Y
// 5) rotate Z
// 6) translate
-Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
+Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(),
+ const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
+
+// Sets the given transform by multiplying the given transformations in the following order:
+// T = translation * rotation * scale * mirror
+void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(),
+ const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(),
+ const Transform3d& mirror = Transform3d::Identity());
+
+// Returns the transform obtained by multiplying the given transformations in the following order:
+// T = translation * rotation * scale * mirror
+Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(),
+ const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity());
+
+// Sets the given transform by assembling the given translation
+void translation_transform(Transform3d& transform, const Vec3d& translation);
+
+// Returns the transform obtained by assembling the given translation
+Transform3d translation_transform(const Vec3d& translation);
+
+// Sets the given transform by assembling the given rotations in the following order:
+// 1) rotate X
+// 2) rotate Y
+// 3) rotate Z
+void rotation_transform(Transform3d& transform, const Vec3d& rotation);
+
+// Returns the transform obtained by assembling the given rotations in the following order:
+// 1) rotate X
+// 2) rotate Y
+// 3) rotate Z
+Transform3d rotation_transform(const Vec3d& rotation);
+
+// Sets the given transform by assembling the given scale factors
+void scale_transform(Transform3d& transform, double scale);
+void scale_transform(Transform3d& transform, const Vec3d& scale);
+
+// Returns the transform obtained by assembling the given scale factors
+Transform3d scale_transform(double scale);
+Transform3d scale_transform(const Vec3d& scale);
// Returns the euler angles extracted from the given rotation matrix
// Warning -> The matrix should not contain any scale or shear !!!
@@ -346,40 +400,29 @@ Vec3d extract_euler_angles(const Transform3d& transform);
// get rotation from two vectors.
// Default output is axis-angle. If rotation_matrix pointer is provided, also output rotation matrix
// Euler angles can be obtained by extract_euler_angles()
-void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix = nullptr);
-
-// Returns the transform obtained by assembling the given translation
-Transform3d translation_transform(const Vec3d &translation);
-
-// Returns the transform obtained by assembling the given rotations in the following order:
-// 1) rotate X
-// 2) rotate Y
-// 3) rotate Z
-Transform3d rotation_transform(const Vec3d &rotation);
+void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d &rotation_axis, double &phi, Matrix3d *rotation_matrix = nullptr);
class Transformation
{
struct Flags
{
- bool dont_translate;
- bool dont_rotate;
- bool dont_scale;
- bool dont_mirror;
-
- Flags();
+ bool dont_translate{ true };
+ bool dont_rotate{ true };
+ bool dont_scale{ true };
+ bool dont_mirror{ true };
bool needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const;
void set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror);
};
- Vec3d m_offset; // In unscaled coordinates
- Vec3d m_rotation; // Rotation around the three axes, in radians around mesh center point
- Vec3d m_scaling_factor; // Scaling factors along the three axes
- Vec3d m_mirror; // Mirroring along the three axes
+ Vec3d m_offset{ Vec3d::Zero() }; // In unscaled coordinates
+ Vec3d m_rotation{ Vec3d::Zero() }; // Rotation around the three axes, in radians around mesh center point
+ Vec3d m_scaling_factor{ Vec3d::Ones() }; // Scaling factors along the three axes
+ Vec3d m_mirror{ Vec3d::Ones() }; // Mirroring along the three axes
- mutable Transform3d m_matrix;
+ mutable Transform3d m_matrix{ Transform3d::Identity() };
mutable Flags m_flags;
- mutable bool m_dirty;
+ mutable bool m_dirty{ false };
public:
Transformation();
@@ -397,6 +440,8 @@ class Transformation
const Vec3d& get_rotation() const { return m_rotation; }
double get_rotation(Axis axis) const { return m_rotation(axis); }
+ Transform3d get_rotation_matrix() const;
+
void set_rotation(const Vec3d& rotation);
void set_rotation(Axis axis, double rotation);
@@ -457,7 +502,7 @@ extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to
// Is the angle close to a multiple of 90 degrees?
inline bool is_rotation_ninety_degrees(double a)
{
- a = fmod(std::abs(a), 0.5 * M_PI);
+ a = fmod(std::abs(a), 0.5 * PI);
if (a > 0.25 * PI)
a = 0.5 * PI - a;
return a < 0.001;
diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp
index 4d7c38ccc23..012b240f8a3 100644
--- a/src/libslic3r/Geometry/Circle.cpp
+++ b/src/libslic3r/Geometry/Circle.cpp
@@ -1,3 +1,7 @@
+///|/ Copyright (c) Prusa Research 2021 - 2022 Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#include "Circle.hpp"
#include "../Polygon.hpp"
@@ -108,7 +112,7 @@ Circled circle_taubin_newton(const Vec2ds& input, size_t cycles)
return out;
}
-Circled circle_ransac(const Vec2ds& input, size_t iterations)
+Circled circle_ransac(const Vec2ds& input, size_t iterations, double* min_error)
{
if (input.size() < 3)
return Circled::make_invalid();
@@ -132,6 +136,8 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations)
circle_best = c;
}
}
+ if (min_error)
+ *min_error = err_min;
return circle_best;
}
diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp
index 39973d916d9..a192cc2fd61 100644
--- a/src/libslic3r/Geometry/Circle.hpp
+++ b/src/libslic3r/Geometry/Circle.hpp
@@ -1,3 +1,7 @@
+///|/ Copyright (c) Prusa Research 2021 - 2022 Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#ifndef slic3r_Geometry_Circle_hpp_
#define slic3r_Geometry_Circle_hpp_
@@ -102,7 +106,7 @@ inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20
Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20);
// Find circle using RANSAC randomized algorithm.
-Circled circle_ransac(const Vec2ds& input, size_t iterations = 20);
+Circled circle_ransac(const Vec2ds& input, size_t iterations = 20, double* min_error = nullptr);
// Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon.
template
diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp
new file mode 100644
index 00000000000..2e6156a88e3
--- /dev/null
+++ b/src/libslic3r/Measure.cpp
@@ -0,0 +1,1255 @@
+///|/ Copyright (c) Prusa Research 2022 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#include "libslic3r/libslic3r.h"
+#include "Measure.hpp"
+#include "MeasureUtils.hpp"
+
+#include "libslic3r/Geometry/Circle.hpp"
+#include "libslic3r/SurfaceMesh.hpp"
+
+
+#include
+#include
+
+#define DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE 0
+
+namespace Slic3r {
+namespace Measure {
+
+
+constexpr double feature_hover_limit = 0.5; // how close to a feature the mouse must be to highlight it
+
+static std::tuple get_center_and_radius(const std::vector& points, const Transform3d& trafo, const Transform3d& trafo_inv)
+{
+ Vec2ds out;
+ double z = 0.;
+ for (const Vec3d& pt : points) {
+ Vec3d pt_transformed = trafo * pt;
+ z = pt_transformed.z();
+ out.emplace_back(pt_transformed.x(), pt_transformed.y());
+ }
+
+ const int iter = points.size() < 10 ? 2 :
+ points.size() < 100 ? 4 :
+ 6;
+
+ double error = std::numeric_limits::max();
+ auto circle = Geometry::circle_ransac(out, iter, &error);
+
+ return std::make_tuple(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius, error);
+}
+
+
+
+static std::array orthonormal_basis(const Vec3d& v)
+{
+ std::array ret;
+ ret[2] = v.normalized();
+ int index;
+ ret[2].cwiseAbs().maxCoeff(&index);
+ switch (index)
+ {
+ case 0: { ret[0] = Vec3d(ret[2].y(), -ret[2].x(), 0.0).normalized(); break; }
+ case 1: { ret[0] = Vec3d(0.0, ret[2].z(), -ret[2].y()).normalized(); break; }
+ case 2: { ret[0] = Vec3d(-ret[2].z(), 0.0, ret[2].x()).normalized(); break; }
+ }
+ ret[1] = ret[2].cross(ret[0]).normalized();
+ return ret;
+}
+
+
+
+class MeasuringImpl {
+public:
+ explicit MeasuringImpl(const indexed_triangle_set& its);
+ struct PlaneData {
+ std::vector facets;
+ std::vector> borders; // FIXME: should be in fact local in update_planes()
+ std::vector surface_features;
+ Vec3d normal;
+ float area;
+ bool features_extracted = false;
+ };
+
+ std::optional get_feature(size_t face_idx, const Vec3d& point);
+ int get_num_of_planes() const;
+ const std::vector& get_plane_triangle_indices(int idx) const;
+ const std::vector& get_plane_features(unsigned int plane_id);
+ const indexed_triangle_set& get_its() const;
+
+private:
+ void update_planes();
+ void extract_features(int plane_idx);
+
+ std::vector m_planes;
+ std::vector m_face_to_plane;
+ indexed_triangle_set m_its;
+};
+
+
+
+
+
+
+MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
+: m_its(its)
+{
+ update_planes();
+
+ // Extracting features will be done as needed.
+ // To extract all planes at once, run the following:
+#if DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE
+ for (int i=0; i face_normals = its_face_normals(m_its);
+ const std::vector face_neighbors = its_face_neighbors(m_its);
+ std::vector facet_queue(num_of_facets, 0);
+ int facet_queue_cnt = 0;
+ const stl_normal* normal_ptr = nullptr;
+ size_t seed_facet_idx = 0;
+
+ auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool {
+ return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001);
+ };
+
+ m_planes.clear();
+ m_planes.reserve(num_of_facets / 5); // empty plane data object is quite lightweight, let's save the initial reallocations
+
+
+ // First go through all the triangles and fill in m_planes vector. For each "plane"
+ // detected on the model, it will contain list of facets that are part of it.
+ // We will also fill in m_face_to_plane, which contains index into m_planes
+ // for each of the source facets.
+ while (1) {
+ // Find next unvisited triangle:
+ for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx)
+ if (m_face_to_plane[seed_facet_idx] == size_t(-1)) {
+ facet_queue[facet_queue_cnt ++] = seed_facet_idx;
+ normal_ptr = &face_normals[seed_facet_idx];
+ m_face_to_plane[seed_facet_idx] = m_planes.size();
+ m_planes.emplace_back();
+ break;
+ }
+ if (seed_facet_idx == num_of_facets)
+ break; // Everything was visited already
+
+ while (facet_queue_cnt > 0) {
+ int facet_idx = facet_queue[-- facet_queue_cnt];
+ const stl_normal& this_normal = face_normals[facet_idx];
+ if (is_same_normal(this_normal, *normal_ptr)) {
+// const Vec3i& face = m_its.indices[facet_idx];
+
+ m_face_to_plane[facet_idx] = m_planes.size() - 1;
+ m_planes.back().facets.emplace_back(facet_idx);
+ for (int j = 0; j < 3; ++ j)
+ if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && m_face_to_plane[neighbor_idx] == size_t(-1))
+ facet_queue[facet_queue_cnt ++] = neighbor_idx;
+ }
+ }
+
+ m_planes.back().normal = normal_ptr->cast();
+ std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end());
+ }
+
+ // Check that each facet is part of one of the planes.
+ assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); }));
+
+ // Now we will walk around each of the planes and save vertices which form the border.
+ const SurfaceMesh sm(m_its);
+
+ const auto& face_to_plane = m_face_to_plane;
+ auto& planes = m_planes;
+
+ tbb::parallel_for(tbb::blocked_range(0, m_planes.size()),
+ [&planes, &face_to_plane, &face_neighbors, &sm](const tbb::blocked_range& range) {
+ for (size_t plane_id = range.begin(); plane_id != range.end(); ++plane_id) {
+
+ const auto& facets = planes[plane_id].facets;
+ planes[plane_id].borders.clear();
+ std::vector> visited(facets.size(), {false, false, false});
+
+ for (int face_id=0; face_id& last_border = planes[plane_id].borders.back();
+ last_border.reserve(4);
+ last_border.emplace_back(sm.point(sm.source(he)).cast());
+ //Vertex_index target = sm.target(he);
+ const Halfedge_index he_start = he;
+
+ Face_index fi = he.face();
+ auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi));
+ assert(face_it != facets.end());
+ assert(*face_it == int(fi));
+ visited[face_it - facets.begin()][he.side()] = true;
+
+ do {
+ const Halfedge_index he_orig = he;
+ he = sm.next_around_target(he);
+ if (he.is_invalid())
+ goto PLANE_FAILURE;
+
+ // For broken meshes, the iteration might never get back to he_orig.
+ // Remember all halfedges we saw to break out of such infinite loops.
+ boost::container::small_vector he_seen;
+
+ while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
+ he_seen.emplace_back(he);
+ he = sm.next_around_target(he);
+ if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end())
+ goto PLANE_FAILURE;
+ }
+ he = sm.opposite(he);
+ if (he.is_invalid())
+ goto PLANE_FAILURE;
+
+ Face_index fi = he.face();
+ auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi));
+ if (face_it == facets.end() || *face_it != int(fi)) // This indicates a broken mesh.
+ goto PLANE_FAILURE;
+
+ if (visited[face_it - facets.begin()][he.side()] && he != he_start) {
+ last_border.resize(1);
+ break;
+ }
+ visited[face_it - facets.begin()][he.side()] = true;
+
+ last_border.emplace_back(sm.point(sm.source(he)).cast());
+
+ // In case of broken meshes, this loop might be infinite. Break
+ // out in case it is clearly going bad.
+ if (last_border.size() > 3*facets.size()+1)
+ goto PLANE_FAILURE;
+
+ } while (he != he_start);
+
+ if (last_border.size() == 1)
+ planes[plane_id].borders.pop_back();
+ else {
+ assert(last_border.front() == last_border.back());
+ last_border.pop_back();
+ }
+ }
+ }
+ continue; // There was no failure.
+
+ PLANE_FAILURE:
+ planes[plane_id].borders.clear();
+ }});
+ m_planes.shrink_to_fit();
+}
+
+
+
+
+
+
+void MeasuringImpl::extract_features(int plane_idx)
+{
+ assert(! m_planes[plane_idx].features_extracted);
+
+ PlaneData& plane = m_planes[plane_idx];
+ plane.surface_features.clear();
+ const Vec3d& normal = plane.normal;
+
+ Eigen::Quaterniond q;
+ q.setFromTwoVectors(plane.normal, Vec3d::UnitZ());
+ Transform3d trafo = Transform3d::Identity();
+ trafo.rotate(q);
+ const Transform3d trafo_inv = trafo.inverse();
+
+ std::vector angles; // placed in outer scope to prevent reallocations
+ std::vector lengths;
+
+ for (const std::vector& border : plane.borders) {
+ if (border.size() <= 1)
+ continue;
+
+ bool done = false;
+
+ if (border.size() > 4) {
+ const auto& [center, radius, err] = get_center_and_radius(border, trafo, trafo_inv);
+
+ if (err < 0.05) {
+ // The whole border is one circle. Just add it into the list of features
+ // and we are done.
+
+ bool is_polygon = border.size()>4 && border.size()<=8;
+ bool lengths_match = std::all_of(border.begin()+2, border.end(), [is_polygon](const Vec3d& pt) {
+ return Slic3r::is_approx((pt - *((&pt)-1)).squaredNorm(), (*((&pt)-1) - *((&pt)-2)).squaredNorm(), is_polygon ? 0.01 : 0.01);
+ });
+
+ if (lengths_match && (is_polygon || border.size() > 8)) {
+ if (is_polygon) {
+ // This is a polygon, add the separate edges with the center.
+ for (int j=0; j int {
+ assert(std::abs(offset) < border_size);
+ int out = idx+offset;
+ if (out >= border_size)
+ out = out - border_size;
+ else if (out < 0)
+ out = border_size + out;
+
+ return out;
+ };
+
+ // First calculate angles at all the vertices.
+ angles.clear();
+ lengths.clear();
+ int first_different_angle_idx = 0;
+ for (int i=0; i M_PI)
+ angle = 2*M_PI - angle;
+
+ angles.push_back(angle);
+ lengths.push_back(v2.norm());
+ if (first_different_angle_idx == 0 && angles.size() > 1) {
+ if (! are_angles_same(angles.back(), angles[angles.size()-2]))
+ first_different_angle_idx = angles.size()-1;
+ }
+ }
+ assert(border.size() == angles.size());
+ assert(border.size() == lengths.size());
+
+ // First go around the border and pick what might be circular segments.
+ // Save pair of indices to where such potential segments start and end.
+ // Also remember the length of these segments.
+ int start_idx = -1;
+ bool circle = false;
+ bool first_iter = true;
+ std::vector circles;
+ std::vector edges;
+ std::vector> circles_idxs;
+ //std::vector circles_lengths;
+ std::vector single_circle; // could be in loop-scope, but reallocations
+ double single_circle_length = 0.;
+ int first_pt_idx = offset_to_index(first_different_angle_idx, 1);
+ int i = first_pt_idx;
+ while (i != first_pt_idx || first_iter) {
+ if (are_angles_same(angles[i], angles[offset_to_index(i,-1)])
+ && i != offset_to_index(first_pt_idx, -1) // not the last point
+ && i != start_idx ) {
+ // circle
+ if (! circle) {
+ circle = true;
+ single_circle.clear();
+ single_circle_length = 0.;
+ start_idx = offset_to_index(i, -2);
+ single_circle = { border[start_idx], border[offset_to_index(start_idx,1)] };
+ single_circle_length += lengths[offset_to_index(i, -1)];
+ }
+ single_circle.emplace_back(border[i]);
+ single_circle_length += lengths[i];
+ } else {
+ if (circle && single_circle.size() >= 5) { // Less than 5 vertices? Not a circle.
+ single_circle.emplace_back(border[i]);
+ single_circle_length += lengths[i];
+
+ bool accept_circle = true;
+ {
+ // Check that lengths of internal (!!!) edges match.
+ int j = offset_to_index(start_idx, 3);
+ while (j != i) {
+ if (! are_lengths_same(lengths[offset_to_index(j,-1)], lengths[j])) {
+ accept_circle = false;
+ break;
+ }
+ j = offset_to_index(j, 1);
+ }
+ }
+
+ if (accept_circle) {
+ const auto& [center, radius, err] = get_center_and_radius(single_circle, trafo, trafo_inv);
+
+ // Check that the fit went well. The tolerance is high, only to
+ // reject complete failures.
+ accept_circle &= err < 0.05;
+
+ // If the segment subtends less than 90 degrees, throw it away.
+ accept_circle &= single_circle_length / radius > 0.9*M_PI/2.;
+
+ if (accept_circle) {
+ // Add the circle and remember indices into borders.
+ circles_idxs.emplace_back(start_idx, i);
+ circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius));
+ }
+ }
+ }
+ circle = false;
+ }
+ // Take care of the wrap around.
+ first_iter = false;
+ i = offset_to_index(i, 1);
+ }
+
+ // We have the circles. Now go around again and pick edges, while jumping over circles.
+ if (circles_idxs.empty()) {
+ // Just add all edges.
+ for (int i=1; i 1 || circles_idxs.front().first != circles_idxs.front().second) {
+ // There is at least one circular segment. Start at its end and add edges until the start of the next one.
+ int i = circles_idxs.front().second;
+ int circle_idx = 1;
+ while (true) {
+ i = offset_to_index(i, 1);
+ edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[offset_to_index(i,-1)], border[i]));
+ if (circle_idx < int(circles_idxs.size()) && i == circles_idxs[circle_idx].first) {
+ i = circles_idxs[circle_idx].second;
+ ++circle_idx;
+ }
+ if (i == circles_idxs.front().first)
+ break;
+ }
+ }
+
+ // Merge adjacent edges where needed.
+ assert(std::all_of(edges.begin(), edges.end(),
+ [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; }));
+ for (int i=edges.size()-1; i>=0; --i) {
+ const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge();
+ const auto& [second_start, second_end] = edges[i].get_edge();
+
+ if (Slic3r::is_approx(first_end, second_start)
+ && Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) {
+ // The edges have the same direction and share a point. Merge them.
+ edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end);
+ edges.erase(edges.begin() + i);
+ }
+ }
+
+ // Now move the circles and edges into the feature list for the plane.
+ assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) {
+ return f.get_type() == SurfaceFeatureType::Circle;
+ }));
+ assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) {
+ return f.get_type() == SurfaceFeatureType::Edge;
+ }));
+ plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()),
+ std::make_move_iterator(circles.end()));
+ plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()),
+ std::make_move_iterator(edges.end()));
+ }
+ }
+
+ // The last surface feature is the plane itself.
+ Vec3d cog = Vec3d::Zero();
+ size_t counter = 0;
+ for (const std::vector& b : plane.borders) {
+ for (size_t i = 1; i < b.size(); ++i) {
+ cog += b[i];
+ ++counter;
+ }
+ }
+ cog /= double(counter);
+ plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Plane,
+ plane.normal, cog, std::optional(), plane_idx + 0.0001));
+
+ plane.borders.clear();
+ plane.borders.shrink_to_fit();
+
+ plane.features_extracted = true;
+}
+
+
+
+
+
+
+
+
+std::optional MeasuringImpl::get_feature(size_t face_idx, const Vec3d& point)
+{
+ if (face_idx >= m_face_to_plane.size())
+ return std::optional();
+
+ const PlaneData& plane = m_planes[m_face_to_plane[face_idx]];
+
+ if (! plane.features_extracted)
+ extract_features(m_face_to_plane[face_idx]);
+
+ size_t closest_feature_idx = size_t(-1);
+ double min_dist = std::numeric_limits::max();
+
+ MeasurementResult res;
+ SurfaceFeature point_sf(point);
+
+ assert(plane.surface_features.empty() || plane.surface_features.back().get_type() == SurfaceFeatureType::Plane);
+
+ for (size_t i=0; idist;
+ if (dist < feature_hover_limit && dist < min_dist) {
+ min_dist = std::min(dist, min_dist);
+ closest_feature_idx = i;
+ }
+ }
+ }
+
+ if (closest_feature_idx != size_t(-1)) {
+ const SurfaceFeature& f = plane.surface_features[closest_feature_idx];
+ if (f.get_type() == SurfaceFeatureType::Edge) {
+ // If this is an edge, check if we are not close to the endpoint. If so,
+ // we will include the endpoint as well. Close = 10% of the lenghth of
+ // the edge, clamped between 0.025 and 0.5 mm.
+ const auto& [sp, ep] = f.get_edge();
+ double len_sq = (ep-sp).squaredNorm();
+ double limit_sq = std::max(0.025*0.025, std::min(0.5*0.5, 0.1 * 0.1 * len_sq));
+
+ if ((point-sp).squaredNorm() < limit_sq)
+ return std::make_optional(SurfaceFeature(sp));
+ if ((point-ep).squaredNorm() < limit_sq)
+ return std::make_optional(SurfaceFeature(ep));
+ }
+ return std::make_optional(f);
+ }
+
+ // Nothing detected, return the plane as a whole.
+ assert(plane.surface_features.back().get_type() == SurfaceFeatureType::Plane);
+ return std::make_optional(plane.surface_features.back());
+}
+
+
+
+
+
+int MeasuringImpl::get_num_of_planes() const
+{
+ return (m_planes.size());
+}
+
+
+
+const std::vector& MeasuringImpl::get_plane_triangle_indices(int idx) const
+{
+ assert(idx >= 0 && idx < int(m_planes.size()));
+ return m_planes[idx].facets;
+}
+
+const std::vector& MeasuringImpl::get_plane_features(unsigned int plane_id)
+{
+ assert(plane_id < m_planes.size());
+ if (! m_planes[plane_id].features_extracted)
+ extract_features(plane_id);
+ return m_planes[plane_id].surface_features;
+}
+
+const indexed_triangle_set& MeasuringImpl::get_its() const
+{
+ return this->m_its;
+}
+
+
+
+
+
+
+
+
+
+
+
+Measuring::Measuring(const indexed_triangle_set& its)
+: priv{std::make_unique(its)}
+{}
+
+Measuring::~Measuring() {}
+
+
+
+std::optional Measuring::get_feature(size_t face_idx, const Vec3d& point) const
+{
+ return priv->get_feature(face_idx, point);
+}
+
+
+int Measuring::get_num_of_planes() const
+{
+ return priv->get_num_of_planes();
+}
+
+
+const std::vector& Measuring::get_plane_triangle_indices(int idx) const
+{
+ return priv->get_plane_triangle_indices(idx);
+}
+
+const std::vector& Measuring::get_plane_features(unsigned int plane_id) const
+{
+ return priv->get_plane_features(plane_id);
+}
+
+const indexed_triangle_set& Measuring::get_its() const
+{
+ return priv->get_its();
+}
+
+const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true };
+
+static AngleAndEdges angle_edge_edge(const std::pair& e1, const std::pair& e2)
+{
+ if (are_parallel(e1, e2))
+ return AngleAndEdges::Dummy;
+
+ Vec3d e1_unit = edge_direction(e1.first, e1.second);
+ Vec3d e2_unit = edge_direction(e2.first, e2.second);
+
+ // project edges on the plane defined by them
+ Vec3d normal = e1_unit.cross(e2_unit).normalized();
+ const Eigen::Hyperplane plane(normal, e1.first);
+ Vec3d e11_proj = plane.projection(e1.first);
+ Vec3d e12_proj = plane.projection(e1.second);
+ Vec3d e21_proj = plane.projection(e2.first);
+ Vec3d e22_proj = plane.projection(e2.second);
+
+ const bool coplanar = (e2.first - e21_proj).norm() < EPSILON && (e2.second - e22_proj).norm() < EPSILON;
+
+ // rotate the plane to become the XY plane
+ auto qp = Eigen::Quaternion::FromTwoVectors(normal, Vec3d::UnitZ());
+ auto qp_inverse = qp.inverse();
+ const Vec3d e11_rot = qp * e11_proj;
+ const Vec3d e12_rot = qp * e12_proj;
+ const Vec3d e21_rot = qp * e21_proj;
+ const Vec3d e22_rot = qp * e22_proj;
+
+ // discard Z
+ const Vec2d e11_rot_2d = Vec2d(e11_rot.x(), e11_rot.y());
+ const Vec2d e12_rot_2d = Vec2d(e12_rot.x(), e12_rot.y());
+ const Vec2d e21_rot_2d = Vec2d(e21_rot.x(), e21_rot.y());
+ const Vec2d e22_rot_2d = Vec2d(e22_rot.x(), e22_rot.y());
+
+ // find intersection (arc center) of edges in XY plane
+ const Eigen::Hyperplane e1_rot_2d_line = Eigen::Hyperplane::Through(e11_rot_2d, e12_rot_2d);
+ const Eigen::Hyperplane e2_rot_2d_line = Eigen::Hyperplane::Through(e21_rot_2d, e22_rot_2d);
+ const Vec2d center_rot_2d = e1_rot_2d_line.intersection(e2_rot_2d_line);
+
+ // arc center in original coordinate
+ const Vec3d center = qp_inverse * Vec3d(center_rot_2d.x(), center_rot_2d.y(), e11_rot.z());
+
+ // ensure the edges are pointing away from the center
+ std::pair out_e1 = e1;
+ std::pair out_e2 = e2;
+ if ((center_rot_2d - e11_rot_2d).squaredNorm() > (center_rot_2d - e12_rot_2d).squaredNorm()) {
+ std::swap(e11_proj, e12_proj);
+ std::swap(out_e1.first, out_e1.second);
+ e1_unit = -e1_unit;
+ }
+ if ((center_rot_2d - e21_rot_2d).squaredNorm() > (center_rot_2d - e22_rot_2d).squaredNorm()) {
+ std::swap(e21_proj, e22_proj);
+ std::swap(out_e2.first, out_e2.second);
+ e2_unit = -e2_unit;
+ }
+
+ // arc angle
+ const double angle = std::acos(std::clamp(e1_unit.dot(e2_unit), -1.0, 1.0));
+ // arc radius
+ const Vec3d e1_proj_mid = 0.5 * (e11_proj + e12_proj);
+ const Vec3d e2_proj_mid = 0.5 * (e21_proj + e22_proj);
+ const double radius = std::min((center - e1_proj_mid).norm(), (center - e2_proj_mid).norm());
+
+ return { angle, center, out_e1, out_e2, radius, coplanar };
+}
+
+static AngleAndEdges angle_edge_plane(const std::pair& e, const std::tuple& p)
+{
+ const auto& [idx, normal, origin] = p;
+ Vec3d e1e2_unit = edge_direction(e);
+ if (are_perpendicular(e1e2_unit, normal))
+ return AngleAndEdges::Dummy;
+
+ // ensure the edge is pointing away from the intersection
+ // 1st calculate instersection between edge and plane
+ const Eigen::Hyperplane plane(normal, origin);
+ const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second);
+ const Vec3d inters = line.intersectionPoint(plane);
+
+ // then verify edge direction and revert it, if needed
+ Vec3d e1 = e.first;
+ Vec3d e2 = e.second;
+ if ((e1 - inters).squaredNorm() > (e2 - inters).squaredNorm()) {
+ std::swap(e1, e2);
+ e1e2_unit = -e1e2_unit;
+ }
+
+ if (are_parallel(e1e2_unit, normal)) {
+ const std::array basis = orthonormal_basis(e1e2_unit);
+ const double radius = (0.5 * (e1 + e2) - inters).norm();
+ const Vec3d edge_on_plane_dir = (basis[1].dot(origin - inters) >= 0.0) ? basis[1] : -basis[1];
+ std::pair edge_on_plane = std::make_pair(inters, inters + radius * edge_on_plane_dir);
+ if (!inters.isApprox(e1)) {
+ edge_on_plane.first += radius * edge_on_plane_dir;
+ edge_on_plane.second += radius * edge_on_plane_dir;
+ }
+ return AngleAndEdges(0.5 * double(PI), inters, std::make_pair(e1, e2), edge_on_plane, radius, inters.isApprox(e1));
+ }
+
+ const Vec3d e1e2 = e2 - e1;
+ const double e1e2_len = e1e2.norm();
+
+ // calculate 2nd edge (on the plane)
+ const Vec3d temp = normal.cross(e1e2);
+ const Vec3d edge_on_plane_unit = normal.cross(temp).normalized();
+ std::pair edge_on_plane = { origin, origin + e1e2_len * edge_on_plane_unit };
+
+ // ensure the 2nd edge is pointing in the correct direction
+ const Vec3d test_edge = (edge_on_plane.second - edge_on_plane.first).cross(e1e2);
+ if (test_edge.dot(temp) < 0.0)
+ edge_on_plane = { origin, origin - e1e2_len * edge_on_plane_unit };
+
+ AngleAndEdges ret = angle_edge_edge({ e1, e2 }, edge_on_plane);
+ ret.radius = (inters - 0.5 * (e1 + e2)).norm();
+ return ret;
+}
+
+static AngleAndEdges angle_plane_plane(const std::tuple& p1, const std::tuple& p2)
+{
+ const auto& [idx1, normal1, origin1] = p1;
+ const auto& [idx2, normal2, origin2] = p2;
+
+ // are planes parallel ?
+ if (are_parallel(normal1, normal2))
+ return AngleAndEdges::Dummy;
+
+ auto intersection_plane_plane = [](const Vec3d& n1, const Vec3d& o1, const Vec3d& n2, const Vec3d& o2) {
+ Eigen::MatrixXd m(2, 3);
+ m << n1.x(), n1.y(), n1.z(), n2.x(), n2.y(), n2.z();
+ Eigen::VectorXd b(2);
+ b << o1.dot(n1), o2.dot(n2);
+ Eigen::VectorXd x = m.colPivHouseholderQr().solve(b);
+ return std::make_pair(n1.cross(n2).normalized(), Vec3d(x(0), x(1), x(2)));
+ };
+
+ // Calculate intersection line between planes
+ const auto [intersection_line_direction, intersection_line_origin] = intersection_plane_plane(normal1, origin1, normal2, origin2);
+
+ // Project planes' origin on intersection line
+ const Eigen::ParametrizedLine intersection_line = Eigen::ParametrizedLine(intersection_line_origin, intersection_line_direction);
+ const Vec3d origin1_proj = intersection_line.projection(origin1);
+ const Vec3d origin2_proj = intersection_line.projection(origin2);
+
+ // Calculate edges on planes
+ const Vec3d edge_on_plane1_unit = (origin1 - origin1_proj).normalized();
+ const Vec3d edge_on_plane2_unit = (origin2 - origin2_proj).normalized();
+ const double radius = std::max(10.0, std::max((origin1 - origin1_proj).norm(), (origin2 - origin2_proj).norm()));
+ const std::pair edge_on_plane1 = { origin1_proj + radius * edge_on_plane1_unit, origin1_proj + 2.0 * radius * edge_on_plane1_unit };
+ const std::pair edge_on_plane2 = { origin2_proj + radius * edge_on_plane2_unit, origin2_proj + 2.0 * radius * edge_on_plane2_unit };
+
+ AngleAndEdges ret = angle_edge_edge(edge_on_plane1, edge_on_plane2);
+ ret.radius = radius;
+ return ret;
+}
+
+
+
+
+
+
+
+MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring)
+{
+ assert(a.get_type() != SurfaceFeatureType::Undef && b.get_type() != SurfaceFeatureType::Undef);
+
+ const bool swap = int(a.get_type()) > int(b.get_type());
+ const SurfaceFeature& f1 = swap ? b : a;
+ const SurfaceFeature& f2 = swap ? a : b;
+
+ MeasurementResult result;
+
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ if (f1.get_type() == SurfaceFeatureType::Point) {
+ if (f2.get_type() == SurfaceFeatureType::Point) {
+ Vec3d diff = (f2.get_point() - f1.get_point());
+ result.distance_strict = std::make_optional(DistAndPoints{diff.norm(), f1.get_point(), f2.get_point()});
+ result.distance_xyz = diff.cwiseAbs();
+
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Edge) {
+ const auto [s,e] = f2.get_edge();
+ const Eigen::ParametrizedLine line(s, (e-s).normalized());
+ const double dist_inf = line.distance(f1.get_point());
+ const Vec3d proj = line.projection(f1.get_point());
+ const double len_sq = (e-s).squaredNorm();
+ const double dist_start_sq = (proj-s).squaredNorm();
+ const double dist_end_sq = (proj-e).squaredNorm();
+ if (dist_start_sq < len_sq && dist_end_sq < len_sq) {
+ // projection falls on the line - the strict distance is the same as infinite
+ result.distance_strict = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj});
+ } else { // the result is the closer of the endpoints
+ const bool s_is_closer = dist_start_sq < dist_end_sq;
+ result.distance_strict = std::make_optional(DistAndPoints{std::sqrt(std::min(dist_start_sq, dist_end_sq) + sqr(dist_inf)), f1.get_point(), s_is_closer ? s : e});
+ }
+ result.distance_infinite = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj});
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Circle) {
+ // Find a plane containing normal, center and the point.
+ const auto [c, radius, n] = f2.get_circle();
+ const Eigen::Hyperplane circle_plane(n, c);
+ const Vec3d proj = circle_plane.projection(f1.get_point());
+ if (proj.isApprox(c)) {
+ const Vec3d p_on_circle = c + radius * get_orthogonal(n, true);
+ result.distance_strict = std::make_optional(DistAndPoints{ radius, c, p_on_circle });
+ }
+ else {
+ const Eigen::Hyperplane circle_plane(n, c);
+ const Vec3d proj = circle_plane.projection(f1.get_point());
+ const double dist = std::sqrt(std::pow((proj - c).norm() - radius, 2.) +
+ (f1.get_point() - proj).squaredNorm());
+
+ const Vec3d p_on_circle = c + radius * (proj - c).normalized();
+ result.distance_strict = std::make_optional(DistAndPoints{ dist, f1.get_point(), p_on_circle }); // TODO
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ const auto [idx, normal, pt] = f2.get_plane();
+ Eigen::Hyperplane plane(normal, pt);
+ result.distance_infinite = std::make_optional(DistAndPoints{plane.absDistance(f1.get_point()), f1.get_point(), plane.projection(f1.get_point())}); // TODO
+ // TODO: result.distance_strict =
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ }
+ else if (f1.get_type() == SurfaceFeatureType::Edge) {
+ if (f2.get_type() == SurfaceFeatureType::Edge) {
+ std::vector distances;
+
+ auto add_point_edge_distance = [&distances](const Vec3d& v, const std::pair& e) {
+ const MeasurementResult res = get_measurement(SurfaceFeature(v), SurfaceFeature(SurfaceFeatureType::Edge, e.first, e.second));
+ double distance = res.distance_strict->dist;
+ Vec3d v2 = res.distance_strict->to;
+
+ const Vec3d e1e2 = e.second - e.first;
+ const Vec3d e1v2 = v2 - e.first;
+ if (e1v2.dot(e1e2) >= 0.0 && e1v2.norm() < e1e2.norm())
+ distances.emplace_back(distance, v, v2);
+ };
+
+ std::pair e1 = f1.get_edge();
+ std::pair e2 = f2.get_edge();
+
+ distances.emplace_back((e2.first - e1.first).norm(), e1.first, e2.first);
+ distances.emplace_back((e2.second - e1.first).norm(), e1.first, e2.second);
+ distances.emplace_back((e2.first - e1.second).norm(), e1.second, e2.first);
+ distances.emplace_back((e2.second - e1.second).norm(), e1.second, e2.second);
+ add_point_edge_distance(e1.first, e2);
+ add_point_edge_distance(e1.second, e2);
+ add_point_edge_distance(e2.first, e1);
+ add_point_edge_distance(e2.second, e1);
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(*it);
+
+ result.angle = angle_edge_edge(f1.get_edge(), f2.get_edge());
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Circle) {
+ const std::pair e = f1.get_edge();
+ const auto& [center, radius, normal] = f2.get_circle();
+ const Vec3d e1e2 = (e.second - e.first);
+ const Vec3d e1e2_unit = e1e2.normalized();
+
+ std::vector distances;
+ distances.emplace_back(*get_measurement(SurfaceFeature(e.first), f2).distance_strict);
+ distances.emplace_back(*get_measurement(SurfaceFeature(e.second), f2).distance_strict);
+
+ const Eigen::Hyperplane plane(e1e2_unit, center);
+ const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second);
+ const Vec3d inter = line.intersectionPoint(plane);
+ const Vec3d e1inter = inter - e.first;
+ if (e1inter.dot(e1e2) >= 0.0 && e1inter.norm() < e1e2.norm())
+ distances.emplace_back(*get_measurement(SurfaceFeature(inter), f2).distance_strict);
+
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{it->dist, it->from, it->to});
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ assert(measuring != nullptr);
+
+ const auto [from, to] = f1.get_edge();
+ const auto [idx, normal, origin] = f2.get_plane();
+
+ const Vec3d edge_unit = (to - from).normalized();
+ if (are_perpendicular(edge_unit, normal)) {
+ std::vector distances;
+ const Eigen::Hyperplane plane(normal, origin);
+ distances.push_back(DistAndPoints{ plane.absDistance(from), from, plane.projection(from) });
+ distances.push_back(DistAndPoints{ plane.absDistance(to), to, plane.projection(to) });
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ else {
+ const std::vector& plane_features = measuring->get_plane_features(idx);
+ std::vector distances;
+ for (const SurfaceFeature& sf : plane_features) {
+ if (sf.get_type() == SurfaceFeatureType::Edge) {
+ const auto m = get_measurement(sf, f1);
+ if (!m.distance_infinite.has_value()) {
+ distances.clear();
+ break;
+ }
+ else
+ distances.push_back(*m.distance_infinite);
+ }
+ }
+ if (!distances.empty()) {
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ }
+ result.angle = angle_edge_plane(f1.get_edge(), f2.get_plane());
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f1.get_type() == SurfaceFeatureType::Circle) {
+ if (f2.get_type() == SurfaceFeatureType::Circle) {
+ const auto [c0, r0, n0] = f1.get_circle();
+ const auto [c1, r1, n1] = f2.get_circle();
+
+ // The following code is an adaptation of the algorithm found in:
+ // https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/DistCircle3Circle3.h
+ // and described in:
+ // https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf
+
+ struct ClosestInfo
+ {
+ double sqrDistance{ 0.0 };
+ Vec3d circle0Closest{ Vec3d::Zero() };
+ Vec3d circle1Closest{ Vec3d::Zero() };
+
+ inline bool operator < (const ClosestInfo& other) const { return sqrDistance < other.sqrDistance; }
+ };
+ std::array candidates{};
+
+ const double zero = 0.0;
+
+ const Vec3d D = c1 - c0;
+
+ if (!are_parallel(n0, n1)) {
+ // Get parameters for constructing the degree-8 polynomial phi.
+ const double one = 1.0;
+ const double two = 2.0;
+ const double r0sqr = sqr(r0);
+ const double r1sqr = sqr(r1);
+
+ // Compute U1 and V1 for the plane of circle1.
+ const std::array basis = orthonormal_basis(n1);
+ const Vec3d U1 = basis[0];
+ const Vec3d V1 = basis[1];
+
+ // Construct the polynomial phi(cos(theta)).
+ const Vec3d N0xD = n0.cross(D);
+ const Vec3d N0xU1 = n0.cross(U1);
+ const Vec3d N0xV1 = n0.cross(V1);
+ const double a0 = r1 * D.dot(U1);
+ const double a1 = r1 * D.dot(V1);
+ const double a2 = N0xD.dot(N0xD);
+ const double a3 = r1 * N0xD.dot(N0xU1);
+ const double a4 = r1 * N0xD.dot(N0xV1);
+ const double a5 = r1sqr * N0xU1.dot(N0xU1);
+ const double a6 = r1sqr * N0xU1.dot(N0xV1);
+ const double a7 = r1sqr * N0xV1.dot(N0xV1);
+ Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 };
+ Polynomial1 p1{ two * a4, two * a6 };
+ Polynomial1 p2{ zero, a1 };
+ Polynomial1 p3{ -a0 };
+ Polynomial1 p4{ -a6, a4, two * a6 };
+ Polynomial1 p5{ -a3, a7 - a5 };
+ Polynomial1 tmp0{ one, zero, -one };
+ Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3;
+ Polynomial1 tmp2 = two * p2 * p3;
+ Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5;
+ Polynomial1 tmp4 = two * p4 * p5;
+ Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3;
+ Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4;
+
+ // Parameters for polynomial root finding. The roots[] array
+ // stores the roots. We need only the unique ones, which is
+ // the responsibility of the set uniqueRoots. The pairs[]
+ // array stores the (cosine,sine) information mentioned in the
+ // PDF. TODO: Choose the maximum number of iterations for root
+ // finding based on specific polynomial data?
+ const uint32_t maxIterations = 128;
+ int32_t degree = 0;
+ size_t numRoots = 0;
+ std::array roots{};
+ std::set uniqueRoots{};
+ size_t numPairs = 0;
+ std::array, 16> pairs{};
+ double temp = zero;
+ double sn = zero;
+
+ if (p7.GetDegree() > 0 || p7[0] != zero) {
+ // H(cs,sn) = p6(cs) + sn * p7(cs)
+ Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7;
+ degree = static_cast(phi.GetDegree());
+ assert(degree > 0);
+ numRoots = RootsPolynomial::Find(degree, &phi[0], maxIterations, roots.data());
+ for (size_t i = 0; i < numRoots; ++i) {
+ uniqueRoots.insert(roots[i]);
+ }
+
+ for (auto const& cs : uniqueRoots) {
+ if (std::fabs(cs) <= one) {
+ temp = p7(cs);
+ if (temp != zero) {
+ sn = -p6(cs) / temp;
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ }
+ else {
+ temp = std::max(one - sqr(cs), zero);
+ sn = std::sqrt(temp);
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ if (sn != zero)
+ pairs[numPairs++] = std::make_pair(cs, -sn);
+ }
+ }
+ }
+ }
+ else {
+ // H(cs,sn) = p6(cs)
+ degree = static_cast(p6.GetDegree());
+ assert(degree > 0);
+ numRoots = RootsPolynomial::Find(degree, &p6[0], maxIterations, roots.data());
+ for (size_t i = 0; i < numRoots; ++i) {
+ uniqueRoots.insert(roots[i]);
+ }
+
+ for (auto const& cs : uniqueRoots) {
+ if (std::fabs(cs) <= one) {
+ temp = std::max(one - sqr(cs), zero);
+ sn = std::sqrt(temp);
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ if (sn != zero)
+ pairs[numPairs++] = std::make_pair(cs, -sn);
+ }
+ }
+ }
+
+ for (size_t i = 0; i < numPairs; ++i) {
+ ClosestInfo& info = candidates[i];
+ Vec3d delta = D + r1 * (pairs[i].first * U1 + pairs[i].second * V1);
+ info.circle1Closest = c0 + delta;
+ const double N0dDelta = n0.dot(delta);
+ const double lenN0xDelta = n0.cross(delta).norm();
+ if (lenN0xDelta > 0.0) {
+ const double diff = lenN0xDelta - r0;
+ info.sqrDistance = sqr(N0dDelta) + sqr(diff);
+ delta -= N0dDelta * n0;
+ delta.normalize();
+ info.circle0Closest = c0 + r0 * delta;
+ }
+ else {
+ const Vec3d r0U0 = r0 * get_orthogonal(n0, true);
+ const Vec3d diff = delta - r0U0;
+ info.sqrDistance = diff.dot(diff);
+ info.circle0Closest = c0 + r0U0;
+ }
+ }
+
+ std::sort(candidates.begin(), candidates.begin() + numPairs);
+ }
+ else {
+ ClosestInfo& info = candidates[0];
+
+ const double N0dD = n0.dot(D);
+ const Vec3d normProj = N0dD * n0;
+ const Vec3d compProj = D - normProj;
+ Vec3d U = compProj;
+ const double d = U.norm();
+ U.normalize();
+
+ // The configuration is determined by the relative location of the
+ // intervals of projection of the circles on to the D-line.
+ // Circle0 projects to [-r0,r0] and circle1 projects to
+ // [d-r1,d+r1].
+ const double dmr1 = d - r1;
+ double distance;
+ if (dmr1 >= r0) {
+ // d >= r0 + r1
+ // The circles are separated (d > r0 + r1) or tangent with one
+ // outside the other (d = r0 + r1).
+ distance = dmr1 - r0;
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 - r1 * U;
+ }
+ else {
+ // d < r0 + r1
+ // The cases implicitly use the knowledge that d >= 0.
+ const double dpr1 = d + r1;
+ if (dpr1 <= r0) {
+ // Circle1 is inside circle0.
+ distance = r0 - dpr1;
+ if (d > 0.0) {
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ else {
+ // The circles are concentric, so U = (0,0,0).
+ // Construct a vector perpendicular to N0 to use for
+ // closest points.
+ U = get_orthogonal(n0, true);
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ }
+ else if (dmr1 <= -r0) {
+ // Circle0 is inside circle1.
+ distance = -r0 - dmr1;
+ if (d > 0.0) {
+ info.circle0Closest = c0 - r0 * U;
+ info.circle1Closest = c1 - r1 * U;
+ }
+ else {
+ // The circles are concentric, so U = (0,0,0).
+ // Construct a vector perpendicular to N0 to use for
+ // closest points.
+ U = get_orthogonal(n0, true);
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ }
+ else {
+ distance = (c1 - c0).norm();
+ info.circle0Closest = c0;
+ info.circle1Closest = c1;
+ }
+ }
+
+ info.sqrDistance = distance * distance + N0dD * N0dD;
+ }
+
+ result.distance_infinite = std::make_optional(DistAndPoints{ std::sqrt(candidates[0].sqrDistance), candidates[0].circle0Closest, candidates[0].circle1Closest }); // TODO
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ assert(measuring != nullptr);
+
+ const auto [center, radius, normal1] = f1.get_circle();
+ const auto [idx2, normal2, origin2] = f2.get_plane();
+
+ const bool coplanar = are_parallel(normal1, normal2) && Eigen::Hyperplane(normal1, center).absDistance(origin2) < EPSILON;
+ if (!coplanar) {
+ const std::vector& plane_features = measuring->get_plane_features(idx2);
+ std::vector distances;
+ for (const SurfaceFeature& sf : plane_features) {
+ if (sf.get_type() == SurfaceFeatureType::Edge) {
+ const auto m = get_measurement(sf, f1);
+ if (!m.distance_infinite.has_value()) {
+ distances.clear();
+ break;
+ }
+ else
+ distances.push_back(*m.distance_infinite);
+ }
+ }
+ if (!distances.empty()) {
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ }
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f1.get_type() == SurfaceFeatureType::Plane) {
+ const auto [idx1, normal1, pt1] = f1.get_plane();
+ const auto [idx2, normal2, pt2] = f2.get_plane();
+
+ if (are_parallel(normal1, normal2)) {
+ // The planes are parallel, calculate distance.
+ const Eigen::Hyperplane plane(normal1, pt1);
+ result.distance_infinite = std::make_optional(DistAndPoints{ plane.absDistance(pt2), pt2, plane.projection(pt2) }); // TODO
+ }
+ else
+ result.angle = angle_plane_plane(f1.get_plane(), f2.get_plane());
+ }
+
+ return result;
+}
+
+
+
+
+
+
+
+
+} // namespace Measure
+} // namespace Slic3r
+
diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp
new file mode 100644
index 00000000000..dcccafb70d6
--- /dev/null
+++ b/src/libslic3r/Measure.hpp
@@ -0,0 +1,200 @@
+///|/ Copyright (c) Prusa Research 2022 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#ifndef Slic3r_Measure_hpp_
+#define Slic3r_Measure_hpp_
+
+#include
+#include
+
+#include "Point.hpp"
+
+
+struct indexed_triangle_set;
+
+
+
+namespace Slic3r {
+
+class TriangleMesh;
+
+namespace Measure {
+
+
+enum class SurfaceFeatureType : int {
+ Undef = 0,
+ Point = 1 << 0,
+ Edge = 1 << 1,
+ Circle = 1 << 2,
+ Plane = 1 << 3
+};
+
+class SurfaceFeature {
+public:
+ SurfaceFeature(SurfaceFeatureType type, const Vec3d& pt1, const Vec3d& pt2, std::optional pt3 = std::nullopt, double value = 0.0)
+ : m_type(type), m_pt1(pt1), m_pt2(pt2), m_pt3(pt3), m_value(value) {}
+
+ explicit SurfaceFeature(const Vec3d& pt)
+ : m_type{SurfaceFeatureType::Point}, m_pt1{pt} {}
+
+ // Get type of this feature.
+ SurfaceFeatureType get_type() const { return m_type; }
+
+ // For points, return the point.
+ Vec3d get_point() const { assert(m_type == SurfaceFeatureType::Point); return m_pt1; }
+
+ // For edges, return start and end.
+ std::pair get_edge() const { assert(m_type == SurfaceFeatureType::Edge); return std::make_pair(m_pt1, m_pt2); }
+
+ // For circles, return center, radius and normal.
+ std::tuple get_circle() const { assert(m_type == SurfaceFeatureType::Circle); return std::make_tuple(m_pt1, m_value, m_pt2); }
+
+ // For planes, return index into vector provided by Measuring::get_plane_triangle_indices, normal and point.
+ std::tuple get_plane() const { assert(m_type == SurfaceFeatureType::Plane); return std::make_tuple(int(m_value), m_pt1, m_pt2); }
+
+ // For anything, return an extra point that should also be considered a part of this.
+ std::optional get_extra_point() const { assert(m_type != SurfaceFeatureType::Undef); return m_pt3; }
+
+ bool operator == (const SurfaceFeature& other) const {
+ if (this->m_type != other.m_type) return false;
+ switch (this->m_type)
+ {
+ case SurfaceFeatureType::Undef: { break; }
+ case SurfaceFeatureType::Point: { return (this->m_pt1.isApprox(other.m_pt1)); }
+ case SurfaceFeatureType::Edge: {
+ return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2)) ||
+ (this->m_pt1.isApprox(other.m_pt2) && this->m_pt2.isApprox(other.m_pt1));
+ }
+ case SurfaceFeatureType::Plane:
+ case SurfaceFeatureType::Circle: {
+ return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2) && std::abs(this->m_value - other.m_value) < EPSILON);
+ }
+ }
+
+ return false;
+ }
+
+ bool operator != (const SurfaceFeature& other) const {
+ return !operator == (other);
+ }
+
+private:
+ SurfaceFeatureType m_type{ SurfaceFeatureType::Undef };
+ Vec3d m_pt1{ Vec3d::Zero() };
+ Vec3d m_pt2{ Vec3d::Zero() };
+ std::optional m_pt3;
+ double m_value{ 0.0 };
+};
+
+
+
+class MeasuringImpl;
+
+
+class Measuring {
+public:
+ // Construct the measurement object on a given its.
+ explicit Measuring(const indexed_triangle_set& its);
+ ~Measuring();
+
+
+ // Given a face_idx where the mouse cursor points, return a feature that
+ // should be highlighted (if any).
+ std::optional get_feature(size_t face_idx, const Vec3d& point) const;
+
+ // Return total number of planes.
+ int get_num_of_planes() const;
+
+ // Returns a list of triangle indices for given plane.
+ const std::vector& get_plane_triangle_indices(int idx) const;
+
+ // Returns the surface features of the plane with the given index
+ const std::vector& get_plane_features(unsigned int plane_id) const;
+
+ // Returns the mesh used for measuring
+ const indexed_triangle_set& get_its() const;
+
+private:
+ std::unique_ptr priv;
+};
+
+
+struct DistAndPoints {
+ DistAndPoints(double dist_, Vec3d from_, Vec3d to_) : dist(dist_), from(from_), to(to_) {}
+ double dist;
+ Vec3d from;
+ Vec3d to;
+};
+
+struct AngleAndEdges {
+ AngleAndEdges(double angle_, const Vec3d& center_, const std::pair& e1_, const std::pair& e2_, double radius_, bool coplanar_)
+ : angle(angle_), center(center_), e1(e1_), e2(e2_), radius(radius_), coplanar(coplanar_) {}
+ double angle;
+ Vec3d center;
+ std::pair e1;
+ std::pair e2;
+ double radius;
+ bool coplanar;
+
+ static const AngleAndEdges Dummy;
+};
+
+struct MeasurementResult {
+ std::optional angle;
+ std::optional distance_infinite;
+ std::optional distance_strict;
+ std::optional distance_xyz;
+
+ bool has_distance_data() const {
+ return distance_infinite.has_value() || distance_strict.has_value();
+ }
+
+ bool has_any_data() const {
+ return angle.has_value() || distance_infinite.has_value() || distance_strict.has_value() || distance_xyz.has_value();
+ }
+};
+
+// Returns distance/angle between two SurfaceFeatures.
+MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring = nullptr);
+
+inline Vec3d edge_direction(const Vec3d& from, const Vec3d& to) { return (to - from).normalized(); }
+inline Vec3d edge_direction(const std::pair& e) { return edge_direction(e.first, e.second); }
+inline Vec3d edge_direction(const SurfaceFeature& edge) {
+ assert(edge.get_type() == SurfaceFeatureType::Edge);
+ return edge_direction(edge.get_edge());
+}
+
+inline Vec3d plane_normal(const SurfaceFeature& plane) {
+ assert(plane.get_type() == SurfaceFeatureType::Plane);
+ return std::get<1>(plane.get_plane());
+}
+
+inline bool are_parallel(const Vec3d& v1, const Vec3d& v2) { return std::abs(std::abs(v1.dot(v2)) - 1.0) < EPSILON; }
+inline bool are_perpendicular(const Vec3d& v1, const Vec3d& v2) { return std::abs(v1.dot(v2)) < EPSILON; }
+
+inline bool are_parallel(const std::pair& e1, const std::pair& e2) {
+ return are_parallel(e1.second - e1.first, e2.second - e2.first);
+}
+inline bool are_parallel(const SurfaceFeature& f1, const SurfaceFeature& f2) {
+ if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge)
+ return are_parallel(edge_direction(f1), edge_direction(f2));
+ else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane)
+ return are_perpendicular(edge_direction(f1), plane_normal(f2));
+ else
+ return false;
+}
+
+inline bool are_perpendicular(const SurfaceFeature& f1, const SurfaceFeature& f2) {
+ if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge)
+ return are_perpendicular(edge_direction(f1), edge_direction(f2));
+ else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane)
+ return are_parallel(edge_direction(f1), plane_normal(f2));
+ else
+ return false;
+}
+
+} // namespace Measure
+} // namespace Slic3r
+
+#endif // Slic3r_Measure_hpp_
diff --git a/src/libslic3r/MeasureUtils.hpp b/src/libslic3r/MeasureUtils.hpp
new file mode 100644
index 00000000000..8a63de5a1f4
--- /dev/null
+++ b/src/libslic3r/MeasureUtils.hpp
@@ -0,0 +1,390 @@
+///|/ Copyright (c) Prusa Research 2022 Enrico Turri @enricoturri1966
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#ifndef Slic3r_MeasureUtils_hpp_
+#define Slic3r_MeasureUtils_hpp_
+
+#include
+
+namespace Slic3r {
+namespace Measure {
+
+// Utility class used to calculate distance circle-circle
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Polynomial1.h
+
+class Polynomial1
+{
+public:
+ Polynomial1(std::initializer_list values)
+ {
+ // C++ 11 will call the default constructor for
+ // Polynomial1 p{}, so it is guaranteed that
+ // values.size() > 0.
+ m_coefficient.resize(values.size());
+ std::copy(values.begin(), values.end(), m_coefficient.begin());
+ EliminateLeadingZeros();
+ }
+
+ // Construction and destruction. The first constructor creates a
+ // polynomial of the specified degree but sets all coefficients to
+ // zero (to ensure initialization). You are responsible for setting
+ // the coefficients, presumably with the degree-term set to a nonzero
+ // number. In the second constructor, the degree is the number of
+ // initializers plus 1, but then adjusted so that coefficient[degree]
+ // is not zero (unless all initializer values are zero).
+ explicit Polynomial1(uint32_t degree)
+ : m_coefficient(static_cast(degree) + 1, 0.0)
+ {}
+
+ // Eliminate any leading zeros in the polynomial, except in the case
+ // the degree is 0 and the coefficient is 0. The elimination is
+ // necessary when arithmetic operations cause a decrease in the degree
+ // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) =
+ // (2 + 3*x). The inputs both have degree 2, so the result is created
+ // with degree 2. After the addition we find that the degree is in
+ // fact 1 and resize the array of coefficients. This function is
+ // called internally by the arithmetic operators, but it is exposed in
+ // the public interface in case you need it for your own purposes.
+ void EliminateLeadingZeros()
+ {
+ const size_t size = m_coefficient.size();
+ if (size > 1) {
+ const double zero = 0.0;
+ int32_t leading;
+ for (leading = static_cast(size) - 1; leading > 0; --leading) {
+ if (m_coefficient[leading] != zero)
+ break;
+ }
+
+ m_coefficient.resize(++leading);
+ }
+ }
+
+ // Set all coefficients to the specified value.
+ void SetCoefficients(double value)
+ {
+ std::fill(m_coefficient.begin(), m_coefficient.end(), value);
+ }
+
+ inline uint32_t GetDegree() const
+ {
+ // By design, m_coefficient.size() > 0.
+ return static_cast(m_coefficient.size() - 1);
+ }
+
+ inline const double& operator[](uint32_t i) const { return m_coefficient[i]; }
+ inline double& operator[](uint32_t i) { return m_coefficient[i]; }
+
+ // Evaluate the polynomial. If the polynomial is invalid, the
+ // function returns zero.
+ double operator()(double t) const
+ {
+ int32_t i = static_cast(m_coefficient.size());
+ double result = m_coefficient[--i];
+ for (--i; i >= 0; --i) {
+ result *= t;
+ result += m_coefficient[i];
+ }
+ return result;
+ }
+
+protected:
+ // The class is designed so that m_coefficient.size() >= 1.
+ std::vector m_coefficient;
+};
+
+inline Polynomial1 operator * (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ Polynomial1 result(p0Degree + p1Degree);
+ result.SetCoefficients(0.0);
+ for (uint32_t i0 = 0; i0 <= p0Degree; ++i0) {
+ for (uint32_t i1 = 0; i1 <= p1Degree; ++i1) {
+ result[i0 + i1] += p0[i0] * p1[i1];
+ }
+ }
+ return result;
+}
+
+inline Polynomial1 operator + (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ uint32_t i;
+ if (p0Degree >= p1Degree) {
+ Polynomial1 result(p0Degree);
+ for (i = 0; i <= p1Degree; ++i) {
+ result[i] = p0[i] + p1[i];
+ }
+ for (/**/; i <= p0Degree; ++i) {
+ result[i] = p0[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+ else {
+ Polynomial1 result(p1Degree);
+ for (i = 0; i <= p0Degree; ++i) {
+ result[i] = p0[i] + p1[i];
+ }
+ for (/**/; i <= p1Degree; ++i) {
+ result[i] = p1[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+}
+
+inline Polynomial1 operator - (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ uint32_t i;
+ if (p0Degree >= p1Degree) {
+ Polynomial1 result(p0Degree);
+ for (i = 0; i <= p1Degree; ++i) {
+ result[i] = p0[i] - p1[i];
+ }
+ for (/**/; i <= p0Degree; ++i) {
+ result[i] = p0[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+ else {
+ Polynomial1 result(p1Degree);
+ for (i = 0; i <= p0Degree; ++i) {
+ result[i] = p0[i] - p1[i];
+ }
+ for (/**/; i <= p1Degree; ++i) {
+ result[i] = -p1[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+}
+
+inline Polynomial1 operator * (double scalar, const Polynomial1& p)
+{
+ const uint32_t degree = p.GetDegree();
+ Polynomial1 result(degree);
+ for (uint32_t i = 0; i <= degree; ++i) {
+ result[i] = scalar * p[i];
+ }
+ return result;
+}
+
+// Utility class used to calculate distance circle-circle
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/RootsPolynomial.h
+
+class RootsPolynomial
+{
+public:
+ // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c'
+ // must have at least d+1 elements and the output array 'root' must
+ // have at least d elements.
+
+ // Find the roots on (-infinity,+infinity).
+ static int32_t Find(int32_t degree, const double* c, uint32_t maxIterations, double* roots)
+ {
+ if (degree >= 0 && c != nullptr) {
+ const double zero = 0.0;
+ while (degree >= 0 && c[degree] == zero) {
+ --degree;
+ }
+
+ if (degree > 0) {
+ // Compute the Cauchy bound.
+ const double one = 1.0;
+ const double invLeading = one / c[degree];
+ double maxValue = zero;
+ for (int32_t i = 0; i < degree; ++i) {
+ const double value = std::fabs(c[i] * invLeading);
+ if (value > maxValue)
+ maxValue = value;
+ }
+ const double bound = one + maxValue;
+
+ return FindRecursive(degree, c, -bound, bound, maxIterations, roots);
+ }
+ else if (degree == 0)
+ // The polynomial is a nonzero constant.
+ return 0;
+ else {
+ // The polynomial is identically zero.
+ roots[0] = zero;
+ return 1;
+ }
+ }
+ else
+ // Invalid degree or c.
+ return 0;
+ }
+
+ // If you know that p(tmin) * p(tmax) <= 0, then there must be at
+ // least one root in [tmin, tmax]. Compute it using bisection.
+ static bool Find(int32_t degree, const double* c, double tmin, double tmax, uint32_t maxIterations, double& root)
+ {
+ const double zero = 0.0;
+ double pmin = Evaluate(degree, c, tmin);
+ if (pmin == zero) {
+ root = tmin;
+ return true;
+ }
+ double pmax = Evaluate(degree, c, tmax);
+ if (pmax == zero) {
+ root = tmax;
+ return true;
+ }
+
+ if (pmin * pmax > zero)
+ // It is not known whether the interval bounds a root.
+ return false;
+
+ if (tmin >= tmax)
+ // Invalid ordering of interval endpoitns.
+ return false;
+
+ for (uint32_t i = 1; i <= maxIterations; ++i) {
+ root = 0.5 * (tmin + tmax);
+
+ // This test is designed for 'float' or 'double' when tmin
+ // and tmax are consecutive floating-point numbers.
+ if (root == tmin || root == tmax)
+ break;
+
+ const double p = Evaluate(degree, c, root);
+ const double product = p * pmin;
+ if (product < zero) {
+ tmax = root;
+ pmax = p;
+ }
+ else if (product > zero) {
+ tmin = root;
+ pmin = p;
+ }
+ else
+ break;
+ }
+
+ return true;
+ }
+
+ // Support for the Find functions.
+ static int32_t FindRecursive(int32_t degree, double const* c, double tmin, double tmax, uint32_t maxIterations, double* roots)
+ {
+ // The base of the recursion.
+ const double zero = 0.0;
+ double root = zero;
+ if (degree == 1) {
+ int32_t numRoots;
+ if (c[1] != zero) {
+ root = -c[0] / c[1];
+ numRoots = 1;
+ }
+ else if (c[0] == zero) {
+ root = zero;
+ numRoots = 1;
+ }
+ else
+ numRoots = 0;
+
+ if (numRoots > 0 && tmin <= root && root <= tmax) {
+ roots[0] = root;
+ return 1;
+ }
+ return 0;
+ }
+
+ // Find the roots of the derivative polynomial scaled by 1/degree.
+ // The scaling avoids the factorial growth in the coefficients;
+ // for example, without the scaling, the high-order term x^d
+ // becomes (d!)*x through multiple differentiations. With the
+ // scaling we instead get x. This leads to better numerical
+ // behavior of the root finder.
+ const int32_t derivDegree = degree - 1;
+ std::vector derivCoeff(static_cast(derivDegree) + 1);
+ std::vector derivRoots(derivDegree);
+ for (int32_t i = 0, ip1 = 1; i <= derivDegree; ++i, ++ip1) {
+ derivCoeff[i] = c[ip1] * (double)(ip1) / (double)degree;
+ }
+ const int32_t numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, maxIterations, &derivRoots[0]);
+
+ int32_t numRoots = 0;
+ if (numDerivRoots > 0) {
+ // Find root on [tmin,derivRoots[0]].
+ if (Find(degree, c, tmin, derivRoots[0], maxIterations, root))
+ roots[numRoots++] = root;
+
+ // Find root on [derivRoots[i],derivRoots[i+1]].
+ for (int32_t i = 0, ip1 = 1; i <= numDerivRoots - 2; ++i, ++ip1) {
+ if (Find(degree, c, derivRoots[i], derivRoots[ip1], maxIterations, root))
+ roots[numRoots++] = root;
+ }
+
+ // Find root on [derivRoots[numDerivRoots-1],tmax].
+ if (Find(degree, c, derivRoots[static_cast(numDerivRoots) - 1], tmax, maxIterations, root))
+ roots[numRoots++] = root;
+ }
+ else {
+ // The polynomial is monotone on [tmin,tmax], so has at most one root.
+ if (Find(degree, c, tmin, tmax, maxIterations, root))
+ roots[numRoots++] = root;
+ }
+ return numRoots;
+ }
+
+ static double Evaluate(int32_t degree, const double* c, double t)
+ {
+ int32_t i = degree;
+ double result = c[i];
+ while (--i >= 0) {
+ result = t * result + c[i];
+ }
+ return result;
+ }
+};
+
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Vector.h
+
+// Construct a single vector orthogonal to the nonzero input vector. If
+// the maximum absolute component occurs at index i, then the orthogonal
+// vector U has u[i] = v[i+1], u[i+1] = -v[i], and all other components
+// zero. The index addition i+1 is computed modulo N.
+inline Vec3d get_orthogonal(const Vec3d& v, bool unitLength)
+{
+ double cmax = std::fabs(v[0]);
+ int32_t imax = 0;
+ for (int32_t i = 1; i < 3; ++i) {
+ double c = std::fabs(v[i]);
+ if (c > cmax) {
+ cmax = c;
+ imax = i;
+ }
+ }
+
+ Vec3d result = Vec3d::Zero();
+ int32_t inext = imax + 1;
+ if (inext == 3)
+ inext = 0;
+
+ result[imax] = v[inext];
+ result[inext] = -v[imax];
+ if (unitLength) {
+ const double sqrDistance = result[imax] * result[imax] + result[inext] * result[inext];
+ const double invLength = 1.0 / std::sqrt(sqrDistance);
+ result[imax] *= invLength;
+ result[inext] *= invLength;
+ }
+ return result;
+}
+
+} // namespace Slic3r
+} // namespace Measure
+
+#endif // Slic3r_MeasureUtils_hpp_
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 90879d49c21..1cd76fc2c5e 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1,3 +1,16 @@
+///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Král @vojtechkral
+///|/ Copyright (c) 2021 Boleslaw Ciesielski
+///|/ Copyright (c) 2019 John Drake @foxox
+///|/ Copyright (c) 2019 Sijmen Schoon
+///|/ Copyright (c) Slic3r 2014 - 2016 Alessandro Ranellucci @alranel
+///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard
+///|/
+///|/ ported from lib/Slic3r/Model.pm:
+///|/ Copyright (c) Prusa Research 2016 - 2022 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
+///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#include "Model.hpp"
#include "libslic3r.h"
#include "BuildVolume.hpp"
@@ -1693,68 +1706,6 @@ bool ModelObject::has_connectors() const
return false;
}
-indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
-{
- indexed_triangle_set connector_mesh;
-
- int sectorCount {1};
- switch (CutConnectorShape(connector_attributes.shape)) {
- case CutConnectorShape::Triangle:
- sectorCount = 3;
- break;
- case CutConnectorShape::Square:
- sectorCount = 4;
- break;
- case CutConnectorShape::Circle:
- sectorCount = 360;
- break;
- case CutConnectorShape::Hexagon:
- sectorCount = 6;
- break;
- default:
- break;
- }
-
- if (connector_attributes.style == CutConnectorStyle::Prizm)
- connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
- else if (connector_attributes.type == CutConnectorType::Plug)
- connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
- else
- connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
-
- return connector_mesh;
-}
-
-void ModelObject::apply_cut_connectors(const std::string &name)
-{
- if (cut_connectors.empty())
- return;
-
- using namespace Geometry;
-
- size_t connector_id = cut_id.connectors_cnt();
- for (const CutConnector &connector : cut_connectors) {
- TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
- // Mesh will be centered when loading.
- ModelVolume *new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
-
- Transform3d translate_transform = Transform3d::Identity();
- translate_transform.translate(connector.pos);
- Transform3d scale_transform = Transform3d::Identity();
- scale_transform.scale(Vec3f(connector.radius, connector.radius, connector.height).cast());
-
- // Transform the new modifier to be aligned inside the instance
- new_volume->set_transformation(translate_transform * connector.rotation_m * scale_transform);
-
- new_volume->cut_info = {connector.attribs.type, connector.radius, connector.height, connector.radius_tolerance, connector.height_tolerance};
- new_volume->name = name + "-" + std::to_string(++connector_id);
- }
- cut_id.increase_connectors_cnt(cut_connectors.size());
-
- // delete all connectors
- cut_connectors.clear();
-}
-
void ModelObject::invalidate_cut()
{
this->cut_id.invalidate();
@@ -1770,43 +1721,10 @@ void ModelObject::delete_connectors()
}
}
-void ModelObject::synchronize_model_after_cut()
-{
- for (ModelObject *obj : m_model->objects) {
- if (obj == this || obj->cut_id.is_equal(this->cut_id)) continue;
- if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
- obj->cut_id.copy(this->cut_id);
- }
-}
-
-void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
-{
- // we don't save cut information, if result will not contains all parts of initial object
- if (!attributes.has(ModelObjectCutAttribute::KeepUpper) ||
- !attributes.has(ModelObjectCutAttribute::KeepLower) ||
- attributes.has(ModelObjectCutAttribute::InvalidateCutInfo))
- return;
-
- if (cut_id.id().invalid())
- cut_id.init();
-
- {
- int cut_obj_cnt = -1;
- if (attributes.has(ModelObjectCutAttribute::KeepUpper))
- cut_obj_cnt++;
- if (attributes.has(ModelObjectCutAttribute::KeepLower))
- cut_obj_cnt++;
- if (attributes.has(ModelObjectCutAttribute::CreateDowels))
- cut_obj_cnt++;
- if (cut_obj_cnt > 0)
- cut_id.increase_check_sum(size_t(cut_obj_cnt));
- }
-}
-
void ModelObject::clone_for_cut(ModelObject **obj)
{
(*obj) = ModelObject::new_clone(*this);
- (*obj)->set_model(nullptr);
+ (*obj)->set_model(this->get_model());
(*obj)->sla_support_points.clear();
(*obj)->sla_drain_holes.clear();
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
@@ -1814,189 +1732,11 @@ void ModelObject::clone_for_cut(ModelObject **obj)
(*obj)->input_file.clear();
}
-Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array& plane_points)
+void ModelVolume::reset_extra_facets()
{
- Vec3d mid_point = {0.0, 0.0, 0.0};
- for (auto pt : plane_points)
- mid_point += pt;
- mid_point /= (double) plane_points.size();
-
- Vec3d movement = -mid_point;
-
- Vec3d v01 = plane_points[1] - plane_points[0];
- Vec3d v12 = plane_points[2] - plane_points[1];
-
- Vec3d plane_normal = v01.cross(v12);
- plane_normal.normalize();
-
- Vec3d axis = {0.0, 0.0, 0.0};
- double phi = 0.0;
- Matrix3d matrix;
- matrix.setIdentity();
- Geometry::rotation_from_two_vectors(plane_normal, {0.0, 0.0, 1.0}, axis, phi, &matrix);
- Vec3d angles = Geometry::extract_euler_angles(matrix);
-
- movement = matrix * movement;
- Transform3d transfo;
- transfo.setIdentity();
- transfo.translate(movement);
- transfo.rotate(Eigen::AngleAxisd(angles(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(angles(1), Vec3d::UnitY()) * Eigen::AngleAxisd(angles(0), Vec3d::UnitX()));
- return transfo;
-}
-
-void ModelObject::process_connector_cut(
- ModelVolume *volume,
- const Transform3d & instance_matrix,
- const Transform3d& cut_matrix,
- ModelObjectCutAttributes attributes,
- ModelObject *upper, ModelObject *lower,
- std::vector