Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_world_coordinates

This commit is contained in:
enricoturri1966 2021-10-19 10:04:32 +02:00
commit ae91741d9d
48 changed files with 1044 additions and 584 deletions

View File

@ -32,6 +32,8 @@ struct SlopeDetection
uniform vec4 uniform_color; uniform vec4 uniform_color;
uniform SlopeDetection slope; uniform SlopeDetection slope;
uniform bool offset_depth_buffer;
#ifdef ENABLE_ENVIRONMENT_MAP #ifdef ENABLE_ENVIRONMENT_MAP
uniform sampler2D environment_tex; uniform sampler2D environment_tex;
uniform bool use_environment_tex; uniform bool use_environment_tex;
@ -50,8 +52,6 @@ varying float world_pos_z;
varying float world_normal_z; varying float world_normal_z;
varying vec3 eye_normal; varying vec3 eye_normal;
uniform bool compute_triangle_normals_in_fs;
void main() void main()
{ {
if (any(lessThan(clipping_planes_dots, ZERO))) if (any(lessThan(clipping_planes_dots, ZERO)))
@ -59,36 +59,7 @@ void main()
vec3 color = uniform_color.rgb; vec3 color = uniform_color.rgb;
float alpha = uniform_color.a; float alpha = uniform_color.a;
vec2 intensity_fs = intensity; if (slope.actived && world_normal_z < slope.normal_z - EPSILON) {
vec3 eye_normal_fs = eye_normal;
float world_normal_z_fs = world_normal_z;
if (compute_triangle_normals_in_fs) {
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
#ifdef FLIP_TRIANGLE_NORMALS
triangle_normal = -triangle_normal;
#endif
// First transform the normal into camera space and normalize the result.
eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0);
intensity_fs = vec2(0.0, 0.0);
intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec3 position = (gl_ModelViewMatrix * model_pos).xyz;
intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0);
intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE;
// z component of normal vector in world coordinate used for slope shading
world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0;
}
if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) {
color = vec3(0.7, 0.7, 1.0); color = vec3(0.7, 0.7, 1.0);
alpha = 1.0; alpha = 1.0;
} }
@ -96,8 +67,13 @@ void main()
color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color; color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color;
#ifdef ENABLE_ENVIRONMENT_MAP #ifdef ENABLE_ENVIRONMENT_MAP
if (use_environment_tex) if (use_environment_tex)
gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha); gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha);
else else
#endif #endif
gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha); gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
// In the support painting gizmo and the seam painting gizmo are painted triangles rendered over the already
// rendered object. To resolved z-fighting between previously rendered object and painted triangles, values
// inside the depth buffer are offset by small epsilon for painted triangles inside those gizmos.
gl_FragDepth = gl_FragCoord.z - (offset_depth_buffer ? EPSILON : 0.0);
} }

View File

@ -54,26 +54,22 @@ varying float world_pos_z;
varying float world_normal_z; varying float world_normal_z;
varying vec3 eye_normal; varying vec3 eye_normal;
uniform bool compute_triangle_normals_in_fs;
void main() void main()
{ {
if (!compute_triangle_normals_in_fs) { // First transform the normal into camera space and normalize the result.
// First transform the normal into camera space and normalize the result. eye_normal = normalize(gl_NormalMatrix * gl_Normal);
eye_normal = normalize(gl_NormalMatrix * gl_Normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // 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. // 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); float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz;
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); intensity.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). // Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
}
model_pos = gl_Vertex; model_pos = gl_Vertex;
// Point in homogenous coordinates. // Point in homogenous coordinates.
@ -90,8 +86,7 @@ void main()
} }
// z component of normal vector in world coordinate used for slope shading // z component of normal vector in world coordinate used for slope shading
if (!compute_triangle_normals_in_fs) world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0;
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0;
gl_Position = ftransform(); gl_Position = ftransform();
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.

View File

@ -0,0 +1,55 @@
#version 110
#define INTENSITY_CORRECTION 0.6
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SHININESS 20.0
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
#define INTENSITY_AMBIENT 0.3
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
const float EPSILON = 0.0001;
uniform vec4 uniform_color;
varying vec3 clipping_planes_dots;
varying vec4 model_pos;
void main()
{
if (any(lessThan(clipping_planes_dots, ZERO)))
discard;
vec3 color = uniform_color.rgb;
float alpha = uniform_color.a;
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
#ifdef FLIP_TRIANGLE_NORMALS
triangle_normal = -triangle_normal;
#endif
// First transform the normal into camera space and normalize the result.
vec3 eye_normal = normalize(gl_NormalMatrix * triangle_normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
// x = diffuse, y = specular;
vec2 intensity = vec2(0.0, 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec3 position = (gl_ModelViewMatrix * model_pos).xyz;
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
}

View File

@ -0,0 +1,23 @@
#version 110
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
uniform mat4 volume_world_matrix;
// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane.
uniform vec2 z_range;
// Clipping plane - general orientation. Used by the SLA gizmo.
uniform vec4 clipping_plane;
varying vec3 clipping_planes_dots;
varying vec4 model_pos;
void main()
{
model_pos = gl_Vertex;
// Point in homogenous coordinates.
vec4 world_pos = volume_world_matrix * gl_Vertex;
gl_Position = ftransform();
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
}

View File

@ -50,7 +50,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr
{ {
ExPolygons ex_polygons; ExPolygons ex_polygons;
for (LayerRegion *region : print_object.layers().front()->regions()) for (LayerRegion *region : print_object.layers().front()->regions())
Slic3r::append(ex_polygons, offset_ex(offset_ex(region->slices.surfaces, float(SCALED_EPSILON)), -float(SCALED_EPSILON))); Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON)));
return ex_polygons; return ex_polygons;
} }

View File

@ -452,6 +452,11 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const fl
{ {
return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
ExPolygons offset2_ex(const Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(surfaces, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
// Offset outside, then inside produces morphological closing. All deltas should be positive. // Offset outside, then inside produces morphological closing. All deltas should be positive.
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
@ -466,6 +471,13 @@ Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delt
assert(delta2 > 0); assert(delta2 > 0);
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
assert(delta1 > 0);
assert(delta2 > 0);
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
// Offset inside, then outside produces morphological opening. All deltas should be positive. // Offset inside, then outside produces morphological opening. All deltas should be positive.
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
@ -474,6 +486,19 @@ Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, c
assert(delta2 > 0); assert(delta2 > 0);
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
assert(delta1 > 0);
assert(delta2 > 0);
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::ExPolygonsProvider(expolygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
assert(delta1 > 0);
assert(delta2 > 0);
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
// Fix of #117: A large fractal pyramid takes ages to slice // Fix of #117: A large fractal pyramid takes ages to slice
// The Clipper library has difficulties processing overlapping polygons. // The Clipper library has difficulties processing overlapping polygons.
@ -525,6 +550,8 @@ Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)

View File

@ -345,19 +345,35 @@ inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const floa
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
// Offset outside, then inside produces morphological closing. All deltas should be positive. // Offset outside, then inside produces morphological closing. All deltas should be positive.
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing(polygons, delta, delta, joinType, miterLimit); } inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return closing(polygons, delta, delta, joinType, miterLimit); }
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing_ex(polygons, delta, delta, joinType, miterLimit); } inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, delta, - delta, joinType, miterLimit); } { return closing_ex(polygons, delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); }
inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); }
// Offset inside, then outside produces morphological opening. All deltas should be positive. // Offset inside, then outside produces morphological opening. All deltas should be positive.
// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons. // Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return opening(polygons, delta, delta, joinType, miterLimit); } Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, - delta, delta, joinType, miterLimit); } Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(polygons, delta, delta, joinType, miterLimit); }
inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(expolygons, delta, delta, joinType, miterLimit); }
inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(surfaces, delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); }
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
@ -366,6 +382,7 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);

View File

@ -11,6 +11,7 @@
#include <numeric> #include <numeric>
#include <unordered_set> #include <unordered_set>
#include <boost/range/adaptor/reversed.hpp>
namespace Slic3r { namespace Slic3r {
@ -33,6 +34,16 @@ struct Intersection
float distance; float distance;
}; };
struct ClosestLine
{
// Index of the polygon containing this line.
size_t border_idx;
// Index of this line on the polygon containing it.
size_t line_idx;
// Closest point on the line.
Point point;
};
// Finding all intersections of a set of contours with a line segment. // Finding all intersections of a set of contours with a line segment.
struct AllIntersectionsVisitor struct AllIntersectionsVisitor
{ {
@ -53,7 +64,7 @@ struct AllIntersectionsVisitor
bool operator()(coord_t iy, coord_t ix) bool operator()(coord_t iy, coord_t ix)
{ {
// Called with a row and colum of the grid cell, which is intersected by a line. // Called with a row and column of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix); auto cell_data_range = grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
Point intersection_point; Point intersection_point;
@ -82,7 +93,7 @@ struct FirstIntersectionVisitor
{ {
assert(pt_current != nullptr); assert(pt_current != nullptr);
assert(pt_next != nullptr); assert(pt_next != nullptr);
// Called with a row and colum of the grid cell, which is intersected by a line. // Called with a row and column of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix); auto cell_data_range = grid.cell_data_range(iy, ix);
this->intersect = false; this->intersect = false;
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
@ -103,6 +114,180 @@ struct FirstIntersectionVisitor
bool intersect = false; bool intersect = false;
}; };
// Visitor to create a list of closet lines to a defined point.
struct MinDistanceVisitor
{
explicit MinDistanceVisitor(const EdgeGrid::Grid &grid, const Point &center, double max_distance_squared)
: grid(grid), center(center), max_distance_squared(max_distance_squared)
{}
void init()
{
this->closest_lines.clear();
this->closest_lines_set.clear();
}
bool operator()(coord_t iy, coord_t ix)
{
// Called with a row and column of the grid cell, which is inside a bounding box.
auto cell_data_range = grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = grid.segment(*it_contour_and_segment);
Point closest_point;
if (closest_lines_set.find(*it_contour_and_segment) == closest_lines_set.end() &&
line_alg::distance_to_squared(Line(segment.first, segment.second), center, &closest_point) <= this->max_distance_squared) {
closest_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, closest_point});
closest_lines_set.insert(*it_contour_and_segment);
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid & grid;
const Slic3r::Point center;
std::vector<ClosestLine> closest_lines;
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> closest_lines_set;
double max_distance_squared = std::numeric_limits<double>::max();
};
// Returns sorted list of closest lines to a passed point within a passed radius
static std::vector<ClosestLine> get_closest_lines_in_radius(const EdgeGrid::Grid &grid, const Point &center, float search_radius)
{
Point radius_vector(search_radius, search_radius);
MinDistanceVisitor visitor(grid, center, search_radius * search_radius);
grid.visit_cells_intersecting_box(BoundingBox(center - radius_vector, center + radius_vector), visitor);
std::sort(visitor.closest_lines.begin(), visitor.closest_lines.end(), [&center](const auto &l, const auto &r) {
return (center - l.point).template cast<double>().squaredNorm() < (center - r.point).template cast<double>().squaredNorm();
});
return visitor.closest_lines;
}
// When the offset is too big, then original travel doesn't have to cross created boundaries.
// For these cases, this function adds another intersection with lines around the start and the end point of the original travel.
static std::vector<Intersection> extend_for_closest_lines(const std::vector<Intersection> &intersections,
const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start,
const Point &end,
const float search_radius)
{
const std::vector<ClosestLine> start_lines = get_closest_lines_in_radius(boundary.grid, start, search_radius);
const std::vector<ClosestLine> end_lines = get_closest_lines_in_radius(boundary.grid, end, search_radius);
// Compute distance to the closest point in the ClosestLine from begin of contour.
auto compute_distance = [&boundary](const ClosestLine &closest_line) -> float {
float dist_from_line_begin = (closest_line.point - boundary.boundaries[closest_line.border_idx][closest_line.line_idx]).cast<float>().norm();
return boundary.boundaries_params[closest_line.border_idx][closest_line.line_idx] + dist_from_line_begin;
};
// It tries to find closest lines for both start point and end point of the travel which has the same border_idx
auto endpoints_close_to_same_boundary = [&start_lines, &end_lines]() -> std::pair<size_t, size_t> {
std::unordered_set<size_t> boundaries_from_start;
for (const ClosestLine &cl_start : start_lines)
boundaries_from_start.insert(cl_start.border_idx);
for (const ClosestLine &cl_end : end_lines)
if (boundaries_from_start.find(cl_end.border_idx) != boundaries_from_start.end())
for (const ClosestLine &cl_start : start_lines)
if (cl_start.border_idx == cl_end.border_idx) {
size_t cl_start_idx = &cl_start - &start_lines.front();
size_t cl_end_idx = &cl_end - &end_lines.front();
return std::make_pair(cl_start_idx, cl_end_idx);
}
return std::make_pair(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max());
};
// If the existing two lines within the search radius start and end point belong to the same boundary,
// discard all intersection points because the whole detour could be on one boundary.
if (!start_lines.empty() && !end_lines.empty()) {
std::pair<size_t, size_t> cl_indices = endpoints_close_to_same_boundary();
if (cl_indices.first != std::numeric_limits<size_t>::max()) {
assert(cl_indices.second != std::numeric_limits<size_t>::max());
const ClosestLine &cl_start = start_lines[cl_indices.first];
const ClosestLine &cl_end = end_lines[cl_indices.second];
std::vector<Intersection> new_intersections;
new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
return new_intersections;
}
}
// Returns ClosestLine which is closer to the point "close_to" then point inside passed Intersection.
auto get_closer = [&search_radius](const std::vector<ClosestLine> &closest_lines, const Intersection &intersection,
const Point &close_to) -> size_t {
for (const ClosestLine &cl : closest_lines) {
double old_dist = (close_to - intersection.point).cast<float>().squaredNorm();
if (cl.border_idx == intersection.border_idx && old_dist <= (search_radius * search_radius) &&
(close_to - cl.point).cast<float>().squaredNorm() < old_dist)
return &cl - &closest_lines.front();
}
return std::numeric_limits<size_t>::max();
};
// Try to find ClosestLine with same boundary_idx as any existing Intersection
auto find_closest_line_with_same_boundary_idx = [](const std::vector<ClosestLine> & closest_lines,
const std::vector<Intersection> &intersections, const bool reverse) -> size_t {
std::unordered_set<size_t> boundaries_indices;
for (const ClosestLine &closest_line : closest_lines)
boundaries_indices.insert(closest_line.border_idx);
// This function must be called only in the case that exists closest_line with boundary_idx equals to intersection.border_idx
auto find_closest_line_index = [&closest_lines](const Intersection &intersection) -> size_t {
for (const ClosestLine &closest_line : closest_lines)
if (closest_line.border_idx == intersection.border_idx) return &closest_line - &closest_lines.front();
// This is an invalid state.
assert(false);
return std::numeric_limits<size_t>::max();
};
if (reverse) {
for (const Intersection &intersection : boost::adaptors::reverse(intersections))
if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end())
return find_closest_line_index(intersection);
} else {
for (const Intersection &intersection : intersections)
if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end())
return find_closest_line_index(intersection);
}
return std::numeric_limits<size_t>::max();
};
std::vector<Intersection> new_intersections = intersections;
if (!intersections.empty() && !start_lines.empty()) {
size_t cl_start_idx = get_closer(start_lines, new_intersections.front(), start);
if (cl_start_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine.
const ClosestLine &cl_start = start_lines[cl_start_idx];
new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)};
} else {
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
// vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
// use the first one, which is the closest one to the start point.
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, intersections, true);
const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits<size_t>::max()) ? start_lines[start_closest_lines_idx] : start_lines.front();
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
}
} else if (!intersections.empty() && !end_lines.empty()) {
size_t cl_end_idx = get_closer(end_lines, new_intersections.back(), end);
if (cl_end_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine.
const ClosestLine &cl_end = end_lines[cl_end_idx];
new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)};
} else {
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
// vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
// use the first one, which is the closest one to the end point.
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, intersections, false);
const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits<size_t>::max()) ? end_lines[end_closest_lines_idx] : end_lines.front();
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
}
}
return new_intersections;
}
// point_idx is the index from which is different vertex is searched. // point_idx is the index from which is different vertex is searched.
template<bool forward> template<bool forward>
static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point)
@ -268,10 +453,63 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
return simplified_path; return simplified_path;
} }
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
static inline float get_default_perimeter_spacing(const PrintObject &print_object)
{
std::vector<unsigned int> printing_extruders = print_object.object_extruders();
assert(!printing_extruders.empty());
float avg_extruder = 0;
for(unsigned int extruder_id : printing_extruders)
avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)));
avg_extruder /= printing_extruders.size();
return avg_extruder;
}
// called by get_boundary() / avoid_perimeters_inner()
static float get_perimeter_spacing(const Layer &layer)
{
size_t regions_count = 0;
float perimeter_spacing = 0.f;
for (const LayerRegion *layer_region : layer.regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++regions_count;
}
assert(perimeter_spacing >= 0.f);
if (regions_count != 0)
perimeter_spacing /= float(regions_count);
else
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
return perimeter_spacing;
}
// called by get_boundary_external()
static float get_perimeter_spacing_external(const Layer &layer)
{
size_t regions_count = 0;
float perimeter_spacing = 0.f;
for (const PrintObject *object : layer.object()->print()->objects())
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
for (const LayerRegion *layer_region : l->regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++ regions_count;
}
assert(perimeter_spacing >= 0.f);
if (regions_count != 0)
perimeter_spacing /= float(regions_count);
else
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
return perimeter_spacing;
}
// Called by avoid_perimeters() and by simplify_travel_heuristics(). // Called by avoid_perimeters() and by simplify_travel_heuristics().
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start, const Point &start,
const Point &end, const Point &end,
const Layer &layer,
std::vector<TravelPoint> &result_out) std::vector<TravelPoint> &result_out)
{ {
const Polygons &boundaries = boundary.boundaries; const Polygons &boundaries = boundary.boundaries;
@ -288,23 +526,31 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin; intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin;
} }
std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast<double>().dot(dir) > 0.; }); std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast<double>().dot(dir) > 0.; });
// Search radius should always be at least equals to the value of offset used for computing boundaries.
const float search_radius = 2.f * get_perimeter_spacing(layer);
// When the offset is too big, then original travel doesn't have to cross created boundaries.
// These cases are fixed by calling extend_for_closest_lines.
intersections = extend_for_closest_lines(intersections, boundary, start, end, search_radius);
} }
std::vector<TravelPoint> result; std::vector<TravelPoint> result;
result.push_back({start, -1}); result.push_back({start, -1});
#if 0
auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) { auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) {
const Polygon &poly = boundary.boundaries[intersection.border_idx]; const Polygon &poly = boundary.boundaries[intersection.border_idx];
Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast<double>(); Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast<double>();
Vec2d intersection_vec = (intersection.point - start).cast<double>(); Vec2d intersection_vec = (intersection.point - start).cast<double>();
return poly_line.normalized().dot(intersection_vec.normalized()) >= 0; return poly_line.normalized().dot(intersection_vec.normalized()) >= 0;
}; };
#endif
for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) {
// The entry point to the boundary polygon // The entry point to the boundary polygon
const Intersection &intersection_first = *it_first; const Intersection &intersection_first = *it_first;
if(!crossing_boundary_from_inside(start, intersection_first)) // if(!crossing_boundary_from_inside(start, intersection_first))
continue; // continue;
// Skip the it_first from the search for the farthest exit point from the boundary polygon // Skip the it_first from the search for the farthest exit point from the boundary polygon
auto it_last_item = std::make_reverse_iterator(it_first) - 1; auto it_last_item = std::make_reverse_iterator(it_first) - 1;
// Search for the farthest intersection different from it_first but with the same border_idx // Search for the farthest intersection different from it_first but with the same border_idx
@ -353,8 +599,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{ {
static int iRun = 0; static int iRun = 0;
export_travel_to_svg(boundaries, Line(start, end), result, intersections, export_travel_to_svg(boundaries, Line(start, end), result, intersections, debug_out_path("AvoidCrossingPerimetersInner-initial-%d-%d.svg", layer.id(), iRun++));
debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++));
} }
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
@ -365,7 +610,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
{ {
static int iRun = 0; static int iRun = 0;
export_travel_to_svg(boundaries, Line(start, end), result, intersections, export_travel_to_svg(boundaries, Line(start, end), result, intersections,
debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); debug_out_path("AvoidCrossingPerimetersInner-final-%d-%d.svg", layer.id(), iRun++));
} }
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
@ -377,17 +622,18 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start, const Point &start,
const Point &end, const Point &end,
const Layer &layer,
Polyline &result_out) Polyline &result_out)
{ {
// Travel line is completely or partially inside the bounding box. // Travel line is completely or partially inside the bounding box.
std::vector<TravelPoint> path; std::vector<TravelPoint> path;
size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); size_t num_intersections = avoid_perimeters_inner(boundary, start, end, layer, path);
result_out = to_polyline(path); result_out = to_polyline(path);
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{ {
static int iRun = 0; static int iRun = 0;
export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d-%d.svg", layer.id(), iRun ++));
} }
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
@ -482,58 +728,6 @@ static bool need_wipe(const GCode &gcodegen,
return wipe_needed; return wipe_needed;
} }
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
static inline float get_default_perimeter_spacing(const PrintObject &print_object)
{
std::vector<unsigned int> printing_extruders = print_object.object_extruders();
assert(!printing_extruders.empty());
float avg_extruder = 0;
for(unsigned int extruder_id : printing_extruders)
avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)));
avg_extruder /= printing_extruders.size();
return avg_extruder;
}
// called by get_boundary()
static float get_perimeter_spacing(const Layer &layer)
{
size_t regions_count = 0;
float perimeter_spacing = 0.f;
for (const LayerRegion *layer_region : layer.regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++regions_count;
}
assert(perimeter_spacing >= 0.f);
if (regions_count != 0)
perimeter_spacing /= float(regions_count);
else
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
return perimeter_spacing;
}
// called by get_boundary_external()
static float get_perimeter_spacing_external(const Layer &layer)
{
size_t regions_count = 0;
float perimeter_spacing = 0.f;
for (const PrintObject *object : layer.object()->print()->objects())
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
for (const LayerRegion *layer_region : l->regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++ regions_count;
}
assert(perimeter_spacing >= 0.f);
if (regions_count != 0)
perimeter_spacing /= float(regions_count);
else
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
return perimeter_spacing;
}
// Adds points around all vertices so that the offset affects only small sections around these vertices. // Adds points around all vertices so that the offset affects only small sections around these vertices.
static void resample_polygon(Polygon &polygon, double dist_from_vertex) static void resample_polygon(Polygon &polygon, double dist_from_vertex)
{ {
@ -795,14 +989,14 @@ static ExPolygons get_boundary(const Layer &layer)
const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_spacing = get_perimeter_spacing(layer);
const float perimeter_offset = perimeter_spacing / 2.f; const float perimeter_offset = perimeter_spacing / 2.f;
auto const *support_layer = dynamic_cast<const SupportLayer *>(&layer); auto const *support_layer = dynamic_cast<const SupportLayer *>(&layer);
ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); ExPolygons boundary = union_ex(inner_offset(layer.lslices, 1.5 * perimeter_spacing));
if(support_layer) { if(support_layer) {
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY #ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset)); append(boundary, inner_offset(support_layer->support_islands.expolygons, 1.5 * perimeter_spacing));
#endif #endif
auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON); auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON);
if (layer_below) if (layer_below)
append(boundary, inner_offset(layer_below->lslices, perimeter_offset)); append(boundary, inner_offset(layer_below->lslices, 1.5 * perimeter_spacing));
// After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons // After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons
boundary = union_ex(boundary); boundary = union_ex(boundary);
} }
@ -925,7 +1119,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
// Trim the travel line by the bounding box. // Trim the travel line by the bounding box.
if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) {
travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl); travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), *gcodegen.layer(), result_pl);
result_pl.points.front() = start; result_pl.points.front() = start;
result_pl.points.back() = end; result_pl.points.back() = end;
} }
@ -936,7 +1130,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
// Trim the travel line by the bounding box. // Trim the travel line by the bounding box.
if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) {
travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl); travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), *gcodegen.layer(), result_pl);
result_pl.points.front() = start; result_pl.points.front() = start;
result_pl.points.back() = end; result_pl.points.back() = end;
} }

View File

@ -35,13 +35,13 @@ public:
struct Boundary { struct Boundary {
// Collection of boundaries used for detection of crossing perimeters for travels // Collection of boundaries used for detection of crossing perimeters for travels
Polygons boundaries; Polygons boundaries;
// Bounding box of boundaries // Bounding box of boundaries
BoundingBoxf bbox; BoundingBoxf bbox;
// Precomputed distances of all points in boundaries // Precomputed distances of all points in boundaries
std::vector<std::vector<float>> boundaries_params; std::vector<std::vector<float>> boundaries_params;
// Used for detection of intersection between line and any polygon from boundaries // Used for detection of intersection between line and any polygon from boundaries
EdgeGrid::Grid grid; EdgeGrid::Grid grid;
void clear() void clear()
{ {

View File

@ -35,6 +35,7 @@ void CoolingBuffer::reset(const Vec3d &position)
m_current_pos[1] = float(position.y()); m_current_pos[1] = float(position.y());
m_current_pos[2] = float(position.z()); m_current_pos[2] = float(position.z());
m_current_pos[4] = float(m_config.travel_speed.value); m_current_pos[4] = float(m_config.travel_speed.value);
m_fan_speed = -1;
} }
struct CoolingLine struct CoolingLine
@ -689,10 +690,9 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Second generate the adjusted G-code. // Second generate the adjusted G-code.
std::string new_gcode; std::string new_gcode;
new_gcode.reserve(gcode.size() * 2); new_gcode.reserve(gcode.size() * 2);
int fan_speed = -1;
bool bridge_fan_control = false; bool bridge_fan_control = false;
int bridge_fan_speed = 0; int bridge_fan_speed = 0;
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() {
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
@ -733,9 +733,9 @@ std::string CoolingBuffer::apply_layer_cooldown(
bridge_fan_speed = 0; bridge_fan_speed = 0;
fan_speed_new = 0; fan_speed_new = 0;
} }
if (fan_speed_new != fan_speed) { if (fan_speed_new != m_fan_speed) {
fan_speed = fan_speed_new; m_fan_speed = fan_speed_new;
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed);
} }
}; };
@ -759,7 +759,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed);
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) {
if (bridge_fan_control) if (bridge_fan_control)
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed);
} else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
// Just remove this comment. // Just remove this comment.
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {

View File

@ -41,6 +41,8 @@ private:
// X,Y,Z,E,F // X,Y,Z,E,F
std::vector<char> m_axis; std::vector<char> m_axis;
std::vector<float> m_current_pos; std::vector<float> m_current_pos;
// Current known fan speed or -1 if not known yet.
int m_fan_speed;
// Cached from GCodeWriter. // Cached from GCodeWriter.
// Printing extruder IDs, zero based. // Printing extruder IDs, zero based.
std::vector<unsigned int> m_extruder_ids; std::vector<unsigned int> m_extruder_ids;

View File

@ -1251,7 +1251,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
cancel_callback(); cancel_callback();
} }
this->process_gcode_line(line, true); this->process_gcode_line(line, true);
}); }, m_result.lines_ends);
// Don't post-process the G-code to update time stamps. // Don't post-process the G-code to update time stamps.
this->finalize(false); this->finalize(false);

View File

@ -152,7 +152,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine
auto it_end = it; auto it_end = it;
for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end)
if (*it_end == '\n') if (*it_end == '\n')
line_end_callback((it_end - buffer.begin()) + 1); line_end_callback(file_pos + (it_end - buffer.begin()) + 1);
// End of line is indicated also if end of file was reached. // End of line is indicated also if end of file was reached.
eol |= eof && it_end == it_bufend; eol |= eof && it_end == it_bufend;
if (eol) { if (eol) {
@ -173,7 +173,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine
if (it != it_bufend && *it == '\r') if (it != it_bufend && *it == '\r')
++ it; ++ it;
if (it != it_bufend && *it == '\n') { if (it != it_bufend && *it == '\n') {
line_end_callback((it - buffer.begin()) + 1); line_end_callback(file_pos + (it - buffer.begin()) + 1);
++ it; ++ it;
} }
} }

View File

@ -431,9 +431,8 @@ void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_comp
for (const Surface &surface : this->slices.surfaces) for (const Surface &surface : this->slices.surfaces)
assert(surface.surface_type == stInternal); assert(surface.surface_type == stInternal);
#endif /* NDEBUG */ #endif /* NDEBUG */
ExPolygons surfaces = to_expolygons(std::move(this->slices.surfaces)); Polygons tmp = intersection(this->slices.surfaces, trimming_polygons);
Polygons tmp = intersection(surfaces, trimming_polygons); append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step)));
append(tmp, diff(surfaces, offset(offset_ex(surfaces, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step)));
this->slices.set(union_ex(tmp), stInternal); this->slices.set(union_ex(tmp), stInternal);
} }

View File

@ -40,23 +40,42 @@ template<class L> auto get_b(L &&l) { return Traits<remove_cvref_t<L>>::get_b(l)
// Distance to the closest point of line. // Distance to the closest point of line.
template<class L> template<class L>
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point) double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point)
{ {
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>(); const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>(); const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
const double l2 = v.squaredNorm(); // avoid a sqrt const double l2 = v.squaredNorm(); // avoid a sqrt
if (l2 == 0.0) if (l2 == 0.0) {
// a == b case // a == b case
*nearest_point = get_a(line);
return va.squaredNorm(); return va.squaredNorm();
}
// Consider the line extending the segment, parameterized as a + t (b - a). // Consider the line extending the segment, parameterized as a + t (b - a).
// We find projection of this point onto the line. // We find projection of this point onto the line.
// It falls where t = [(this-a) . (b-a)] / |b-a|^2 // It falls where t = [(this-a) . (b-a)] / |b-a|^2
const double t = va.dot(v) / l2; const double t = va.dot(v) / l2;
if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment if (t < 0.0) {
else if (t > 1.0) return (point - get_b(line)).template cast<double>().squaredNorm(); // beyond the 'b' end of the segment // beyond the 'a' end of the segment
*nearest_point = get_a(line);
return va.squaredNorm();
} else if (t > 1.0) {
// beyond the 'b' end of the segment
*nearest_point = get_b(line);
return (point - get_b(line)).template cast<double>().squaredNorm();
}
*nearest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
return (t * v - va).squaredNorm(); return (t * v - va).squaredNorm();
} }
// Distance to the closest point of line.
template<class L>
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
{
Vec<Dim<L>, Scalar<L>> nearest_point;
return distance_to_squared<L>(line, point, &nearest_point);
}
template<class L> template<class L>
double distance_to(const L &line, const Vec<Dim<L>, Scalar<L>> &point) double distance_to(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
{ {
@ -81,6 +100,7 @@ public:
bool intersection_infinite(const Line &other, Point* point) const; bool intersection_infinite(const Line &other, Point* point) const;
bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; }
double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); } double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); }
double distance_to_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); }
double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); } double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); }
double perp_distance_to(const Point &point) const; double perp_distance_to(const Point &point) const;
bool parallel_to(double angle) const; bool parallel_to(double angle) const;

View File

@ -159,8 +159,9 @@ template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
int i = 0; int i = 0;
Vec3i facet; Vec3i facet;
for (auto v : vtc) { for (auto v : vtc) {
if (i > 2) { i = 0; break; } int iv = v;
facet(i++) = v; if (i > 2 || iv < 0 || iv >= int(cgalmesh.vertices().size())) { i = 0; break; }
facet(i++) = iv;
} }
if (i == 3) if (i == 3)

View File

@ -1853,7 +1853,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters " def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters "
"(and thus any ooze will be probably invisible)."); "(and thus any ooze will be probably invisible).");
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("ooze_prevention", coBool); def = this->add("ooze_prevention", coBool);
def->label = L("Enable"); def->label = L("Enable");

View File

@ -1088,7 +1088,7 @@ void PrintObject::discover_vertical_shells()
// For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
if (perimeter_offset > 0.) { if (perimeter_offset > 0.) {
// The layer.lslices are forced to merge by expanding them first. // The layer.lslices are forced to merge by expanding them first.
polygons_append(cache.holes, offset(offset_ex(layer.lslices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing)); polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{ {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices));
@ -1325,7 +1325,7 @@ void PrintObject::discover_vertical_shells()
#if 1 #if 1
// Intentionally inflate a bit more than how much the region has been shrunk, // Intentionally inflate a bit more than how much the region has been shrunk,
// so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill).
shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
if (shell.empty()) if (shell.empty())
continue; continue;
#else #else

View File

@ -1703,15 +1703,18 @@ static inline std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupport
// Don't want to print a layer below the first layer height as it may not stick well. // Don't want to print a layer below the first layer height as it may not stick well.
//FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
// and it may actually make sense to do it with a thinner layer than the first layer height. // and it may actually make sense to do it with a thinner layer than the first layer height.
const coordf_t min_print_z = slicing_params.raft_layers() > 1 ? slicing_params.raft_interface_top_z + support_layer_height_min + EPSILON : slicing_params.first_print_layer_height - EPSILON; if (print_z < slicing_params.first_print_layer_height - EPSILON) {
if (print_z < min_print_z) {
// This contact layer is below the first layer height, therefore not printable. Don't support this surface. // This contact layer is below the first layer height, therefore not printable. Don't support this surface.
return std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupportMaterial::MyLayer*>(nullptr, nullptr); return std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupportMaterial::MyLayer*>(nullptr, nullptr);
} else if (print_z < slicing_params.first_print_layer_height + EPSILON) { }
// Align the layer with the 1st layer height. const bool has_raft = slicing_params.raft_layers() > 1;
print_z = slicing_params.first_print_layer_height; const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height;
bottom_z = 0; if (print_z < min_print_z + support_layer_height_min) {
height = slicing_params.first_print_layer_height; // Align the layer with the 1st layer height or the raft contact layer.
// With raft active, any contact layer below the raft_contact_top_z will be brought to raft_contact_top_z to extend the raft area.
print_z = min_print_z;
bottom_z = has_raft ? slicing_params.raft_interface_top_z : 0;
height = has_raft ? slicing_params.contact_raft_layer_height : min_print_z;
} else { } else {
// Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and
// its height will be set adaptively later on. // its height will be set adaptively later on.
@ -1727,9 +1730,9 @@ static inline std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupport
coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object; coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object;
if (bridging_print_z >= min_print_z) { if (bridging_print_z >= min_print_z) {
// Not below the first layer height means this layer is printable. // Not below the first layer height means this layer is printable.
if (print_z < slicing_params.first_print_layer_height + EPSILON) { if (print_z < min_print_z + support_layer_height_min) {
// Align the layer with the 1st layer height. // Align the layer with the 1st layer height or the raft contact layer.
bridging_print_z = slicing_params.first_print_layer_height; bridging_print_z = min_print_z;
} }
if (bridging_print_z < print_z - EPSILON) { if (bridging_print_z < print_z - EPSILON) {
// Allocate the new layer. // Allocate the new layer.
@ -3108,7 +3111,7 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
intermediate_layer.polygons); intermediate_layer.polygons);
if (! bottom.empty()) { if (! bottom.empty()) {
//FIXME Remove non-printable tiny islands, let them be printed using the base support. //FIXME Remove non-printable tiny islands, let them be printed using the base support.
//bottom = offset(offset_ex(std::move(bottom), - minimum_island_radius), minimum_island_radius); //bottom = opening(std::move(bottom), minimum_island_radius);
if (! bottom.empty()) { if (! bottom.empty()) {
MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type);
layer_new.polygons = std::move(bottom); layer_new.polygons = std::move(bottom);

View File

@ -208,7 +208,7 @@ void name_tbb_thread_pool_threads_set_locale()
nthreads = 1; nthreads = 1;
#endif #endif
std::atomic<size_t> nthreads_running(0); size_t nthreads_running(0);
std::condition_variable cv; std::condition_variable cv;
std::mutex cv_m; std::mutex cv_m;
auto master_thread_id = std::this_thread::get_id(); auto master_thread_id = std::this_thread::get_id();
@ -216,13 +216,13 @@ void name_tbb_thread_pool_threads_set_locale()
tbb::blocked_range<size_t>(0, nthreads, 1), tbb::blocked_range<size_t>(0, nthreads, 1),
[&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) { [&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) {
assert(range.begin() + 1 == range.end()); assert(range.begin() + 1 == range.end());
if (nthreads_running.fetch_add(1) + 1 == nthreads) { if (std::unique_lock<std::mutex> lk(cv_m); ++nthreads_running == nthreads) {
lk.unlock();
// All threads are spinning. // All threads are spinning.
// Wake them up. // Wake them up.
cv.notify_all(); cv.notify_all();
} else { } else {
// Wait for the last thread to wake the others. // Wait for the last thread to wake the others.
std::unique_lock<std::mutex> lk(cv_m);
cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;}); cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;});
} }
auto thread_id = std::this_thread::get_id(); auto thread_id = std::this_thread::get_id();

View File

@ -127,7 +127,8 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
const Vec3f& source, float radius, const Vec3f& source, float radius,
CursorType cursor_type, EnforcerBlockerType new_state, CursorType cursor_type, EnforcerBlockerType new_state,
const Transform3d& trafo, bool triangle_splitting) const Transform3d& trafo, const Transform3d& trafo_no_translate,
bool triangle_splitting, float highlight_by_angle_deg)
{ {
assert(facet_start < m_orig_size_indices); assert(facet_start < m_orig_size_indices);
@ -143,6 +144,9 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
m_old_cursor_radius_sqr = m_cursor.radius_sqr; m_old_cursor_radius_sqr = m_cursor.radius_sqr;
} }
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
// Now start with the facet the pointer points to and check all adjacent facets. // Now start with the facet the pointer points to and check all adjacent facets.
std::vector<int> facets_to_check; std::vector<int> facets_to_check;
facets_to_check.reserve(16); facets_to_check.reserve(16);
@ -153,14 +157,14 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
// Head of the bread-first facets_to_check FIFO. // Head of the bread-first facets_to_check FIFO.
int facet_idx = 0; int facet_idx = 0;
while (facet_idx < int(facets_to_check.size())) { while (facet_idx < int(facets_to_check.size())) {
int facet = facets_to_check[facet_idx]; int facet = facets_to_check[facet_idx];
if (! visited[facet]) { const Vec3f &facet_normal = m_face_normals[m_triangles[facet].source_triangle];
if (!visited[facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) {
if (select_triangle(facet, new_state, triangle_splitting)) { if (select_triangle(facet, new_state, triangle_splitting)) {
// add neighboring facets to list to be proccessed later // add neighboring facets to list to be processed later
for (int neighbor_idx : m_neighbors[facet]) { for (int neighbor_idx : m_neighbors[facet])
if (neighbor_idx >=0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx)))
facets_to_check.push_back(neighbor_idx); facets_to_check.push_back(neighbor_idx);
}
} }
} }
visited[facet] = true; visited[facet] = true;
@ -168,7 +172,10 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
} }
} }
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle, bool force_reselection) void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start,
const Transform3d& trafo_no_translate,
float seed_fill_angle, float highlight_by_angle_deg,
bool force_reselection)
{ {
assert(facet_start < m_orig_size_indices); assert(facet_start < m_orig_size_indices);
@ -182,14 +189,17 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
std::queue<int> facet_queue; std::queue<int> facet_queue;
facet_queue.push(facet_start); facet_queue.push(facet_start);
const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
// Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor.
while (!facet_queue.empty()) { while (!facet_queue.empty()) {
int current_facet = facet_queue.front(); int current_facet = facet_queue.front();
facet_queue.pop(); facet_queue.pop();
if (!visited[current_facet]) { const Vec3f &facet_normal = m_face_normals[m_triangles[current_facet].source_triangle];
if (!visited[current_facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) {
if (m_triangles[current_facet].is_split()) { if (m_triangles[current_facet].is_split()) {
for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) { for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) {
assert(split_triangle_idx < int(m_triangles[current_facet].children.size())); assert(split_triangle_idx < int(m_triangles[current_facet].children.size()));

View File

@ -45,12 +45,16 @@ public:
CursorType type, // current type of cursor CursorType type, // current type of cursor
EnforcerBlockerType new_state, // enforcer or blocker? EnforcerBlockerType new_state, // enforcer or blocker?
const Transform3d &trafo, // matrix to get from mesh to world const Transform3d &trafo, // matrix to get from mesh to world
bool triangle_splitting); // If triangles will be split base on the cursor or not const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
bool triangle_splitting, // If triangles will be split base on the cursor or not
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
void seed_fill_select_triangles(const Vec3f &hit, // point where to start void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to

View File

@ -319,19 +319,7 @@ void GLVolume::SinkingContours::update()
MeshSlicingParams slicing_params; MeshSlicingParams slicing_params;
slicing_params.trafo = m_parent.world_matrix(); slicing_params.trafo = m_parent.world_matrix();
Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params));
for (Polygon& polygon : polygons) { for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) {
if (polygon.is_clockwise())
polygon.reverse();
Polygons outer_polys = offset(polygon, float(scale_(HalfWidth)));
assert(outer_polys.size() == 1);
if (outer_polys.empty())
// no outer contour, skip
continue;
ExPolygon expoly(std::move(outer_polys.front()));
expoly.holes = offset(polygon, -float(scale_(HalfWidth)));
polygons_reverse(expoly.holes);
GUI::GLModel::InitializationData::Entity entity; GUI::GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles; entity.type = GUI::GLModel::PrimitiveType::Triangles;
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly); const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);

View File

@ -456,7 +456,7 @@ private:
GLGizmosManager m_gizmos; GLGizmosManager m_gizmos;
GLToolbar m_main_toolbar; GLToolbar m_main_toolbar;
GLToolbar m_undoredo_toolbar; GLToolbar m_undoredo_toolbar;
ClippingPlane m_clipping_planes[2]; std::array<ClippingPlane, 2> m_clipping_planes;
ClippingPlane m_camera_clipping_plane; ClippingPlane m_camera_clipping_plane;
bool m_use_clipping_planes; bool m_use_clipping_planes;
SlaCap m_sla_caps[2]; SlaCap m_sla_caps[2];
@ -651,6 +651,9 @@ public:
void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); } void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); }
void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; } void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; }
bool get_use_clipping_planes() const { return m_use_clipping_planes; }
const std::array<ClippingPlane, 2> &get_clipping_planes() const { return m_clipping_planes; };
void set_color_by(const std::string& value); void set_color_by(const std::string& value);
void refresh_camera_scene_box(); void refresh_camera_scene_box();

View File

@ -61,25 +61,23 @@ std::pair<bool, std::string> GLShadersManager::init()
// used to render extrusion and travel paths as lines in gcode preview // used to render extrusion and travel paths as lines in gcode preview
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
// used to render objects in 3d editor // used to render objects in 3d editor
// For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
// Because of this, objects had darker colors inside the multi-material gizmo.
// Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU.
if (platform_flavor() == PlatformFlavor::OSXOnArm)
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv
#if ENABLE_ENVIRONMENT_MAP #if ENABLE_ENVIRONMENT_MAP
, "ENABLE_ENVIRONMENT_MAP"sv , { "ENABLE_ENVIRONMENT_MAP"sv }
#endif
});
else
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
#endif #endif
); );
// used to render variable layers heights in 3d editor // used to render variable layers heights in 3d editor
valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" });
// used to render highlight contour around selected triangles inside the multi-material gizmo // used to render highlight contour around selected triangles inside the multi-material gizmo
valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" }); valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" });
// Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader.
// For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction.
// Because of this, objects had darker colors inside the multi-material gizmo.
// Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU.
if (platform_flavor() == PlatformFlavor::OSXOnArm)
valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv});
else
valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"});
return { valid, error }; return { valid, error };
} }

View File

@ -676,16 +676,18 @@ void GUI_App::post_init()
if (this->preset_updater) { if (this->preset_updater) {
this->check_updates(false); this->check_updates(false);
CallAfter([this] { CallAfter([this] {
this->config_wizard_startup(); bool cw_showed = this->config_wizard_startup();
this->preset_updater->slic3r_update_notify(); this->preset_updater->slic3r_update_notify();
this->preset_updater->sync(preset_bundle); this->preset_updater->sync(preset_bundle);
if (! cw_showed) {
// The CallAfter is needed as well, without it, GL extensions did not show.
// Also, we only want to show this when the wizard does not, so the new user
// sees something else than "we want something" on the first start.
show_send_system_info_dialog_if_needed();
}
}); });
} }
// 'Send system info' dialog. Again, a CallAfter is needed on mac.
// Without it, GL extensions did not show.
CallAfter([] { show_send_system_info_dialog_if_needed(); });
#ifdef _WIN32 #ifdef _WIN32
// Sets window property to mainframe so other instances can indentify it. // Sets window property to mainframe so other instances can indentify it.
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);

View File

@ -8,6 +8,7 @@
#include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/UndoRedo.hpp"
@ -20,7 +21,7 @@ namespace Slic3r::GUI {
void GLGizmoFdmSupports::on_shutdown() void GLGizmoFdmSupports::on_shutdown()
{ {
m_angle_threshold_deg = 0.f; m_highlight_by_angle_threshold_deg = 0.f;
m_parent.use_slope(false); m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true); m_parent.toggle_model_objects_visibility(true);
} }
@ -52,7 +53,7 @@ bool GLGizmoFdmSupports::on_init()
m_desc["circle"] = _L("Circle"); m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere"); m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles"); m_desc["pointer"] = _L("Triangles");
m_desc["highlight_by_angle"] = _L("Highlight by angle"); m_desc["highlight_by_angle"] = _L("Highlight overhang by angle");
m_desc["enforce_button"] = _L("Enforce"); m_desc["enforce_button"] = _L("Enforce");
m_desc["cancel"] = _L("Cancel"); m_desc["cancel"] = _L("Cancel");
@ -62,6 +63,9 @@ bool GLGizmoFdmSupports::on_init()
m_desc["smart_fill_angle"] = _L("Smart fill angle"); m_desc["smart_fill_angle"] = _L("Smart fill angle");
m_desc["split_triangles"] = _L("Split triangles");
m_desc["on_overhangs_only"] = _L("On overhangs only");
return true; return true;
} }
@ -89,18 +93,19 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (! m_c->selection_info()->model_object()) if (! m_c->selection_info()->model_object())
return; return;
const float approx_height = m_imgui->scaled(20.5f); const float approx_height = m_imgui->scaled(23.f);
y = std::min(y, bottom_limit - approx_height); y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.f); const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); const float autoset_slider_label_max_width = m_imgui->scaled(7.5f);
const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f);
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
@ -116,6 +121,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f; float caption_max = 0.f;
float total_text_max = 0.f; float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"}) { for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"}) {
@ -125,10 +133,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
total_text_max += caption_max + m_imgui->scaled(1.f); total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f);
float sliders_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left));
float window_width = minimal_slider_width + sliders_width; float window_width = minimal_slider_width + sliders_left_width;
window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width); window_width = std::max(window_width, button_width);
window_width = std::max(window_width, split_triangles_checkbox_width);
window_width = std::max(window_width, on_overhangs_only_checkbox_width);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
@ -144,38 +154,53 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::Separator(); ImGui::Separator();
float position_before_text_y = ImGui::GetCursorPos().y;
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["highlight_by_angle"] + ":"); m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width);
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
float position_after_text_y = ImGui::GetCursorPos().y;
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", std::string format_str = std::string("%.f") + I18N::translate_utf8("°",
"Degree sign to use in the respective slider in FDM supports gizmo," "Degree sign to use in the respective slider in FDM supports gizmo,"
"placed after the number with no whitespace in between."); "placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_width); ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_width); ImGui::PushItemWidth(window_width - sliders_left_width);
if (m_imgui->slider_float("##angle_threshold_deg", &m_angle_threshold_deg, 0.f, 90.f, format_str.data())) {
m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg); float slider_height = m_imgui->get_slider_float_height();
// Makes slider to be aligned to bottom of the multi-line text.
float slider_start_position = std::max(position_before_text_y, position_after_text_y - slider_height);
ImGui::SetCursorPosY(slider_start_position);
if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) {
m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
if (! m_parent.is_using_slope()) { if (! m_parent.is_using_slope()) {
m_parent.use_slope(true); m_parent.use_slope(true);
m_parent.set_as_dirty(); m_parent.set_as_dirty();
} }
} }
m_imgui->disabled_begin(m_angle_threshold_deg == 0.f); // Restores the cursor position to be below the multi-line text.
ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y));
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
if (ImGui::IsItemHovered())
m_imgui->tooltip(format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
"the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]), max_tooltip_width);
m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f);
ImGui::NewLine(); ImGui::NewLine();
ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f));
if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) {
select_facets_by_angle(m_angle_threshold_deg, false); select_facets_by_angle(m_highlight_by_angle_threshold_deg, false);
m_angle_threshold_deg = 0.f; m_highlight_by_angle_threshold_deg = 0.f;
m_parent.use_slope(false); m_parent.use_slope(false);
} }
ImGui::SameLine(window_width - buttons_width); ImGui::SameLine(window_width - buttons_width);
if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) {
m_angle_threshold_deg = 0.f; m_highlight_by_angle_threshold_deg = 0.f;
m_parent.use_slope(false); m_parent.use_slope(false);
} }
m_imgui->disabled_end(); m_imgui->disabled_end();
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::Separator(); ImGui::Separator();
@ -188,26 +213,20 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH))
m_tool_type = ToolType::BRUSH; m_tool_type = ToolType::BRUSH;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
ImGui::PushItemWidth(tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_smart_fill);
if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL))
m_tool_type = ToolType::SMART_FILL; m_tool_type = ToolType::SMART_FILL;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data()); m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only);
ImGui::PopTextWrapPos(); if (ImGui::IsItemHovered())
ImGui::EndTooltip(); m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width);
}
ImGui::Separator(); ImGui::Separator();
@ -221,13 +240,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
m_cursor_type = TriangleSelector::CursorType::SPHERE; m_cursor_type = TriangleSelector::CursorType::SPHERE;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
ImGui::PushItemWidth(cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_circle);
@ -235,13 +249,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
m_cursor_type = TriangleSelector::CursorType::CIRCLE; m_cursor_type = TriangleSelector::CursorType::CIRCLE;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
ImGui::PushItemWidth(cursor_type_radio_pointer); ImGui::PushItemWidth(cursor_type_radio_pointer);
@ -249,61 +258,40 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
m_cursor_type = TriangleSelector::CursorType::POINTER; m_cursor_type = TriangleSelector::CursorType::POINTER;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size")); m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_width); ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_width); ImGui::PushItemWidth(window_width - sliders_left_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_end(); m_imgui->disabled_end();
} else { } else {
assert(m_tool_type == ToolType::SMART_FILL); assert(m_tool_type == ToolType::SMART_FILL);
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["smart_fill_angle"] + ":"); m_imgui->text(m_desc["smart_fill_angle"] + ":");
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
"placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width);
ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_width);
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data()))
for (auto &triangle_selector : m_triangle_selectors) { for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data(); triangle_selector->request_update_render_data();
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
} }
ImGui::Separator(); ImGui::Separator();
@ -319,19 +307,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
} }
} }
ImGui::SameLine(sliders_width); ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_width); ImGui::PushItemWidth(window_width - sliders_left_width);
auto clp_dist = float(m_c->object_clipper()->get_position()); auto clp_dist = float(m_c->object_clipper()->get_position());
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator(); ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) { if (m_imgui->button(m_desc.at("remove_all"))) {

View File

@ -35,7 +35,6 @@ private:
PainterGizmoType get_painter_type() const override; PainterGizmoType get_painter_type() const override;
void select_facets_by_angle(float threshold, bool block); void select_facets_by_angle(float threshold, bool block);
float m_angle_threshold_deg = 0.f;
// This map holds all translated description texts, so they can be easily referenced during layout calculations // This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.

View File

@ -547,13 +547,9 @@ RENDER_AGAIN:
ImGui::SameLine(settings_sliders_left); ImGui::SameLine(settings_sliders_left);
ImGui::PushItemWidth(window_width - settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left);
m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm"); m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted((_utf8(opts[0].second->tooltip)).c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider
bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider
bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider
@ -563,13 +559,9 @@ RENDER_AGAIN:
m_imgui->text(m_desc.at("quality")); m_imgui->text(m_desc.at("quality"));
ImGui::SameLine(settings_sliders_left); ImGui::SameLine(settings_sliders_left);
m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f"); m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted((_utf8(opts[1].second->tooltip)).c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
slider_clicked |= ImGui::IsItemClicked(); slider_clicked |= ImGui::IsItemClicked();
slider_edited |= ImGui::IsItemEdited(); slider_edited |= ImGui::IsItemEdited();
slider_released |= ImGui::IsItemDeactivatedAfterEdit(); slider_released |= ImGui::IsItemDeactivatedAfterEdit();
@ -580,13 +572,9 @@ RENDER_AGAIN:
m_imgui->text(m_desc.at("closing_distance")); m_imgui->text(m_desc.at("closing_distance"));
ImGui::SameLine(settings_sliders_left); ImGui::SameLine(settings_sliders_left);
m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted((_utf8(opts[2].second->tooltip)).c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
slider_clicked |= ImGui::IsItemClicked(); slider_clicked |= ImGui::IsItemClicked();
slider_edited |= ImGui::IsItemEdited(); slider_edited |= ImGui::IsItemEdited();
slider_released |= ImGui::IsItemDeactivatedAfterEdit(); slider_released |= ImGui::IsItemDeactivatedAfterEdit();

View File

@ -129,6 +129,7 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["tool_bucket_fill"] = _L("Bucket fill"); m_desc["tool_bucket_fill"] = _L("Bucket fill");
m_desc["smart_fill_angle"] = _L("Smart fill angle"); m_desc["smart_fill_angle"] = _L("Smart fill angle");
m_desc["split_triangles"] = _L("Split triangles");
init_extruders_data(); init_extruders_data();
@ -142,7 +143,7 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const
glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection, false); render_triangles(selection);
m_c->object_clipper()->render_cut(); m_c->object_clipper()->render_cut();
m_c->instances_hider()->render_cut(); m_c->instances_hider()->render_cut();
@ -173,6 +174,43 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
} }
} }
void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const
{
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
auto *shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const ModelObject *mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
shader->set_uniform("volume_world_matrix", trafo_matrix);
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
static void render_extruders_combo(const std::string &label, static void render_extruders_combo(const std::string &label,
const std::vector<std::string> &extruders, const std::vector<std::string> &extruders,
const std::vector<std::array<float, 4>> &extruders_colors, const std::vector<std::array<float, 4>> &extruders_colors,
@ -261,6 +299,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f; float caption_max = 0.f;
float total_text_max = 0.f; float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) { for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
@ -274,6 +314,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
float window_width = minimal_slider_width + sliders_width; float window_width = minimal_slider_width + sliders_width;
window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width); window_width = std::max(window_width, button_width);
window_width = std::max(window_width, split_triangles_checkbox_width);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill); window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
@ -331,13 +372,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
} }
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
ImGui::PushItemWidth(tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_smart_fill);
@ -349,13 +385,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
} }
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill); ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill);
ImGui::PushItemWidth(tool_type_radio_bucket_fill); ImGui::PushItemWidth(tool_type_radio_bucket_fill);
@ -367,13 +398,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
} }
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints neighboring facets that have the same color."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator(); ImGui::Separator();
@ -387,13 +413,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
m_cursor_type = TriangleSelector::CursorType::SPHERE; m_cursor_type = TriangleSelector::CursorType::SPHERE;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
ImGui::PushItemWidth(cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_circle);
@ -401,13 +422,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
m_cursor_type = TriangleSelector::CursorType::CIRCLE; m_cursor_type = TriangleSelector::CursorType::CIRCLE;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
ImGui::PushItemWidth(cursor_type_radio_pointer); ImGui::PushItemWidth(cursor_type_radio_pointer);
@ -415,13 +431,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
m_cursor_type = TriangleSelector::CursorType::POINTER; m_cursor_type = TriangleSelector::CursorType::POINTER;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
@ -430,23 +441,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::SameLine(sliders_width); ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width); ImGui::PushItemWidth(window_width - sliders_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_end(); m_imgui->disabled_end();
@ -464,13 +465,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
triangle_selector->request_update_render_data(); triangle_selector->request_update_render_data();
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator(); ImGui::Separator();
} }
@ -490,13 +486,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator(); ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) { if (m_imgui->button(m_desc.at("remove_all"))) {
@ -600,9 +591,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
auto *shader = wxGetApp().get_current_shader(); auto *shader = wxGetApp().get_current_shader();
if (!shader) if (!shader)
return; return;
assert(shader->get_name() == "gouraud"); assert(shader->get_name() == "mm_gouraud");
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);});
shader->set_uniform("compute_triangle_normals_in_fs", true);
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx) for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
if (m_gizmo_scene.has_VBOs(color_idx)) { if (m_gizmo_scene.has_VBOs(color_idx)) {
@ -615,7 +604,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
} }
if (m_paint_contour.has_VBO()) { if (m_paint_contour.has_VBO()) {
ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); });
shader->stop_using(); shader->stop_using();
auto *contour_shader = wxGetApp().get_shader("mm_contour"); auto *contour_shader = wxGetApp().get_shader("mm_contour");

View File

@ -89,6 +89,8 @@ public:
void set_painter_gizmo_data(const Selection& selection) override; void set_painter_gizmo_data(const Selection& selection) override;
void render_triangles(const Selection& selection) const override;
// TriangleSelector::serialization/deserialization has a limit to store 19 different states. // TriangleSelector::serialization/deserialization has a limit to store 19 different states.
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization // When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization

View File

@ -43,42 +43,40 @@ void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection)
} }
} }
GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const
void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool use_polygon_offset_fill) const
{ {
const ModelObject* mo = m_c->selection_info()->model_object(); ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}};
ScopeGuard offset_fill_guard([&use_polygon_offset_fill]() {
if (use_polygon_offset_fill)
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
});
if (use_polygon_offset_fill) {
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(-5.0, -5.0));
}
// Take care of the clipping plane. The normal of the clipping plane is // Take care of the clipping plane. The normal of the clipping plane is
// saved with opposite sign than we need to pass to OpenGL (FIXME) // saved with opposite sign than we need to pass to OpenGL (FIXME)
bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) {
float clp_dataf[4] = {0.f, 0.f, 1.f, FLT_MAX}; const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane();
if (clipping_plane_active) { for (size_t i = 0; i < 3; ++i)
const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]);
for (size_t i=0; i<3; ++i) clp_data_out.clp_dataf[3] = float(clp->get_data()[3]);
clp_dataf[i] = -1.f * float(clp->get_data()[i]);
clp_dataf[3] = float(clp->get_data()[3]);
} }
// z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
if (m_c->get_canvas()->get_use_clipping_planes()) {
const std::array<ClippingPlane, 2> &clps = m_c->get_canvas()->get_clipping_planes();
clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])};
}
return clp_data_out;
}
void GLGizmoPainterBase::render_triangles(const Selection& selection) const
{
auto *shader = wxGetApp().get_shader("gouraud"); auto *shader = wxGetApp().get_shader("gouraud");
if (! shader) if (! shader)
return; return;
shader->start_using(); shader->start_using();
shader->set_uniform("slope.actived", false); shader->set_uniform("slope.actived", false);
shader->set_uniform("print_box.actived", false); shader->set_uniform("print_box.actived", false);
shader->set_uniform("clipping_plane", clp_dataf, 4); shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
int mesh_id = -1; const ModelObject *mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) { for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part()) if (! mv->is_model_part())
continue; continue;
@ -253,7 +251,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
: std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
m_parent.set_as_dirty(); m_parent.set_as_dirty();
if (m_rr.mesh_id != -1) { if (m_rr.mesh_id != -1) {
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); const Selection &selection = m_parent.get_selection();
const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true);
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id; m_seed_fill_last_mesh_id = m_rr.mesh_id;
} }
@ -284,11 +287,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type();
} }
const Camera &camera = wxGetApp().plater()->get_camera(); const Camera &camera = wxGetApp().plater()->get_camera();
const Selection &selection = m_parent.get_selection(); const Selection &selection = m_parent.get_selection();
const ModelObject *mo = m_c->selection_info()->model_object(); const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d &instance_trafo = mi->get_transformation().get_matrix(); const Transform3d instance_trafo = mi->get_transformation().get_matrix();
const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
// List of mouse positions that will be used as seeds for painting. // List of mouse positions that will be used as seeds for painting.
std::vector<Vec2d> mouse_positions{mouse_position}; std::vector<Vec2d> mouse_positions{mouse_position};
@ -314,10 +318,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
// Precalculate transformations of individual meshes. // Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices; std::vector<Transform3d> trafo_matrices;
for (const ModelVolume* mv : mo->volumes) { std::vector<Transform3d> trafo_matrices_not_translate;
if (mv->is_model_part()) for (const ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
} trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
}
// Now "click" into all the prepared points and spill paint around them. // Now "click" into all the prepared points and spill paint around them.
for (const Vec2d& mp : mouse_positions) { for (const Vec2d& mp : mouse_positions) {
@ -339,7 +345,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
return dragging_while_painting; return dragging_while_painting;
} }
const Transform3d& trafo_matrix = trafo_matrices[m_rr.mesh_id]; const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
// Calculate direction from camera to the hit (in mesh coords): // Calculate direction from camera to the hit (in mesh coords):
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>(); Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
@ -348,7 +355,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state); m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
if (m_tool_type == ToolType::SMART_FILL) if (m_tool_type == ToolType::SMART_FILL)
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true); m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true);
else if (m_tool_type == ToolType::BUCKET_FILL) else if (m_tool_type == ToolType::BUCKET_FILL)
@ -357,7 +365,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
m_seed_fill_last_mesh_id = -1; m_seed_fill_last_mesh_id = -1;
} else if (m_tool_type == ToolType::BRUSH) } else if (m_tool_type == ToolType::BRUSH)
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type, m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
new_state, trafo_matrix, m_triangle_splitting_enabled); new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_last_mouse_click = mouse_position; m_last_mouse_click = mouse_position;
@ -370,17 +379,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (m_triangle_selectors.empty()) if (m_triangle_selectors.empty())
return false; return false;
const Camera & camera = wxGetApp().plater()->get_camera(); const Camera &camera = wxGetApp().plater()->get_camera();
const Selection & selection = m_parent.get_selection(); const Selection &selection = m_parent.get_selection();
const ModelObject * mo = m_c->selection_info()->model_object(); const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d & instance_trafo = mi->get_transformation().get_matrix(); const Transform3d instance_trafo = mi->get_transformation().get_matrix();
const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
// Precalculate transformations of individual meshes. // Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices; std::vector<Transform3d> trafo_matrices;
std::vector<Transform3d> trafo_matrices_not_translate;
for (const ModelVolume *mv : mo->volumes) for (const ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) if (mv->is_model_part()) {
trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
}
// Now "click" into all the prepared points and spill paint around them. // Now "click" into all the prepared points and spill paint around them.
update_raycast_cache(mouse_position, camera, trafo_matrices); update_raycast_cache(mouse_position, camera, trafo_matrices);
@ -405,9 +418,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if(m_rr.mesh_id != m_seed_fill_last_mesh_id) if(m_rr.mesh_id != m_seed_fill_last_mesh_id)
seed_fill_unselect_all(); seed_fill_unselect_all();
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
assert(m_rr.mesh_id < int(m_triangle_selectors.size())); assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
if (m_tool_type == ToolType::SMART_FILL) if (m_tool_type == ToolType::SMART_FILL)
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle); m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false); m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false);
else if (m_tool_type == ToolType::BUCKET_FILL) else if (m_tool_type == ToolType::BUCKET_FILL)
@ -560,7 +576,8 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
if (! shader) if (! shader)
return; return;
assert(shader->get_name() == "gouraud"); assert(shader->get_name() == "gouraud");
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);});
shader->set_uniform("offset_depth_buffer", true);
for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color),
std::make_pair(&m_iva_blockers, blockers_color)}) { std::make_pair(&m_iva_blockers, blockers_color)}) {
if (iva.first->has_VBOs()) { if (iva.first->has_VBOs()) {
@ -586,7 +603,7 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
auto *contour_shader = wxGetApp().get_shader("mm_contour"); auto *contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using(); contour_shader->start_using();
glsafe(::glDepthFunc(GL_GEQUAL)); glsafe(::glDepthFunc(GL_LEQUAL));
m_paint_contour.render(); m_paint_contour.render();
glsafe(::glDepthFunc(GL_LESS)); glsafe(::glDepthFunc(GL_LESS));

View File

@ -126,7 +126,7 @@ public:
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
protected: protected:
void render_triangles(const Selection& selection, const bool use_polygon_offset_fill = true) const; virtual void render_triangles(const Selection& selection) const;
void render_cursor() const; void render_cursor() const;
void render_cursor_circle() const; void render_cursor_circle() const;
void render_cursor_sphere(const Transform3d& trafo) const; void render_cursor_sphere(const Transform3d& trafo) const;
@ -159,6 +159,9 @@ protected:
ToolType m_tool_type = ToolType::BRUSH; ToolType m_tool_type = ToolType::BRUSH;
float m_smart_fill_angle = 30.f; float m_smart_fill_angle = 30.f;
bool m_paint_on_overhangs_only = false;
float m_highlight_by_angle_threshold_deg = 0.f;
static constexpr float SmartFillAngleMin = 0.0f; static constexpr float SmartFillAngleMin = 0.0f;
static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleMax = 90.f;
static constexpr float SmartFillAngleStep = 1.f; static constexpr float SmartFillAngleStep = 1.f;
@ -173,6 +176,14 @@ protected:
Right Right
}; };
struct ClippingPlaneDataWrapper
{
std::array<float, 4> clp_dataf;
std::array<float, 2> z_range;
};
ClippingPlaneDataWrapper get_clipping_plane_data() const;
private: private:
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
void update_raycast_cache(const Vec2d& mouse_position, void update_raycast_cache(const Vec2d& mouse_position,

View File

@ -129,13 +129,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
ImGui::SameLine(sliders_width); ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width); ImGui::PushItemWidth(window_width - sliders_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type")); m_imgui->text(m_desc.at("cursor_type"));
@ -146,26 +141,16 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
m_cursor_type = TriangleSelector::CursorType::SPHERE; m_cursor_type = TriangleSelector::CursorType::SPHERE;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
ImGui::PushItemWidth(cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_circle);
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
m_cursor_type = TriangleSelector::CursorType::CIRCLE; m_cursor_type = TriangleSelector::CursorType::CIRCLE;
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator(); ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) { if (m_c->object_clipper()->get_position() == 0.f) {
@ -186,13 +171,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered())
ImGui::BeginTooltip(); m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator(); ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) { if (m_imgui->button(m_desc.at("remove_all"))) {

View File

@ -24,15 +24,16 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
, m_obj_index(0) , m_obj_index(0)
, m_need_reload(false) , m_need_reload(false)
, m_show_wireframe(false) , m_show_wireframe(false)
// translation for GUI size
, tr_mesh_name(_u8L("Mesh name")) , tr_mesh_name(_u8L("Mesh name"))
, tr_triangles(_u8L("Triangles")) , tr_triangles(_u8L("Triangles"))
, tr_preview(_u8L("Preview")) , tr_preview(_u8L("Preview"))
, tr_detail_level(_u8L("Detail level")) , tr_detail_level(_u8L("Detail level"))
, tr_decimate_ratio(_u8L("Decimate ratio")) , tr_decimate_ratio(_u8L("Decimate ratio"))
// for wireframe
, m_wireframe_VBO_id(0) , m_wireframe_VBO_id(0)
, m_wireframe_IBO_id(0) , m_wireframe_IBO_id(0)
, m_wireframe_IBO_size(0)
{} {}
GLGizmoSimplify::~GLGizmoSimplify() { GLGizmoSimplify::~GLGizmoSimplify() {
@ -41,10 +42,11 @@ GLGizmoSimplify::~GLGizmoSimplify() {
free_gpu(); free_gpu();
} }
bool GLGizmoSimplify::on_init() bool GLGizmoSimplify::on_esc_key_down() {
{ if (m_state == State::settings || m_state == State::canceling)
//m_grabbers.emplace_back(); return false;
//m_shortcut_key = WXK_CONTROL_C;
m_state = State::canceling;
return true; return true;
} }
@ -53,10 +55,6 @@ std::string GLGizmoSimplify::on_get_name() const
return _u8L("Simplify"); return _u8L("Simplify");
} }
void GLGizmoSimplify::on_render() { }
void GLGizmoSimplify::on_render_for_picking() {}
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
{ {
create_gui_cfg(); create_gui_cfg();
@ -143,8 +141,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::Separator(); ImGui::Separator();
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) { if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
m_is_valid_result = false;
m_configuration.use_count = !m_configuration.use_count; m_configuration.use_count = !m_configuration.use_count;
live_preview();
} }
ImGui::SameLine(); ImGui::SameLine();
m_imgui->disabled_begin(m_configuration.use_count); m_imgui->disabled_begin(m_configuration.use_count);
@ -160,7 +158,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::SetNextItemWidth(m_gui_cfg->input_width); ImGui::SetNextItemWidth(m_gui_cfg->input_width);
static int reduction = 2; static int reduction = 2;
if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) { if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) {
m_is_valid_result = false;
if (reduction < 0) reduction = 0; if (reduction < 0) reduction = 0;
if (reduction > 4) reduction = 4; if (reduction > 4) reduction = 4;
switch (reduction) { switch (reduction) {
@ -170,12 +167,13 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
case 3: m_configuration.max_error = 0.5f; break; case 3: m_configuration.max_error = 0.5f; break;
case 4: m_configuration.max_error = 1.f; break; case 4: m_configuration.max_error = 1.f; break;
} }
live_preview();
} }
m_imgui->disabled_end(); // !use_count m_imgui->disabled_end(); // !use_count
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) { if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
m_is_valid_result = false;
m_configuration.use_count = !m_configuration.use_count; m_configuration.use_count = !m_configuration.use_count;
live_preview();
} }
ImGui::SameLine(); ImGui::SameLine();
@ -192,13 +190,14 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::SetNextItemWidth(m_gui_cfg->input_width); ImGui::SetNextItemWidth(m_gui_cfg->input_width);
const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%": const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%"); ((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) { if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) {
m_is_valid_result = false;
if (m_configuration.decimate_ratio < 0.f) if (m_configuration.decimate_ratio < 0.f)
m_configuration.decimate_ratio = 0.01f; m_configuration.decimate_ratio = 0.01f;
if (m_configuration.decimate_ratio > 100.f) if (m_configuration.decimate_ratio > 100.f)
m_configuration.decimate_ratio = 100.f; m_configuration.decimate_ratio = 100.f;
m_configuration.fix_count_by_ratio(triangle_count); m_configuration.fix_count_by_ratio(triangle_count);
live_preview();
} }
ImGui::NewLine(); ImGui::NewLine();
@ -206,43 +205,48 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count); ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count);
m_imgui->disabled_end(); // use_count m_imgui->disabled_end(); // use_count
if (ImGui::Checkbox(_L("Show wireframe").c_str(), &m_show_wireframe)) { if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) {
if (m_show_wireframe) init_wireframe(); if (m_show_wireframe) init_wireframe();
else free_gpu(); else free_gpu();
} }
if (m_state == State::settings) { bool is_canceling = m_state == State::canceling;
if (m_imgui->button(_L("Cancel"))) { m_imgui->disabled_begin(is_canceling);
if (m_original_its.has_value()) { if (m_imgui->button(_L("Cancel"))) {
if (m_state == State::settings) {
if (m_original_its.has_value()) {
set_its(*m_original_its); set_its(*m_original_its);
m_state = State::close_on_end; m_state = State::close_on_end;
} else { } else {
close(); close();
} }
} else {
m_state = State::canceling;
} }
ImGui::SameLine(m_gui_cfg->bottom_left_width); } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling)
if (m_imgui->button(_L("Preview"))) { ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str());
m_state = State::preview; m_imgui->disabled_end(); // state canceling
// simplify but not apply on mesh
process();
}
ImGui::SameLine();
if (m_imgui->button(_L("Apply"))) {
if (!m_is_valid_result) {
m_state = State::close_on_end;
process();
} else if (m_exist_preview) {
// use preview and close
after_apply();
} else { // no changes made
close();
}
}
} else {
m_imgui->disabled_begin(m_state == State::canceling);
if (m_imgui->button(_L("Cancel"))) m_state = State::canceling;
m_imgui->disabled_end();
ImGui::SameLine();
bool is_processing = m_state != State::settings;
m_imgui->disabled_begin(is_processing);
if (m_imgui->button(_L("Apply"))) {
if (!m_is_valid_result) {
m_state = State::close_on_end;
process();
} else if (m_exist_preview) {
// use preview and close
after_apply();
} else { // no changes made
close();
}
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing)
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
m_imgui->disabled_end(); // state !settings
// draw progress bar
if (is_processing) { // apply or preview
ImGui::SameLine(m_gui_cfg->bottom_left_width); ImGui::SameLine(m_gui_cfg->bottom_left_width);
// draw progress bar // draw progress bar
char buf[32]; char buf[32];
@ -251,6 +255,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
} }
m_imgui->end(); m_imgui->end();
// refresh view when needed
if (m_need_reload) { if (m_need_reload) {
m_need_reload = false; m_need_reload = false;
bool close_on_end = (m_state == State::close_on_end); bool close_on_end = (m_state == State::close_on_end);
@ -280,6 +285,22 @@ void GLGizmoSimplify::close() {
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
} }
void GLGizmoSimplify::live_preview() {
m_is_valid_result = false;
if (m_state != State::settings) {
// already canceling process
if (m_state == State::canceling) return;
// wait until cancel
if (m_worker.joinable()) {
m_state = State::canceling;
m_worker.join();
}
}
m_state = State::preview;
process();
}
void GLGizmoSimplify::process() void GLGizmoSimplify::process()
{ {
@ -408,6 +429,7 @@ void GLGizmoSimplify::create_gui_cfg() {
cfg.input_width = cfg.bottom_left_width * 1.5; cfg.input_width = cfg.bottom_left_width * 1.5;
cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2; cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2;
cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5; cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5;
m_gui_cfg = cfg; m_gui_cfg = cfg;
} }

View File

@ -24,21 +24,25 @@ class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GL
public: public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoSimplify(); virtual ~GLGizmoSimplify();
bool on_esc_key_down();
protected: protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override; virtual std::string on_get_name() const override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual void on_render_input_window(float x, float y, float bottom_limit) override;
virtual bool on_is_activable() const override; virtual bool on_is_activable() const override;
virtual bool on_is_selectable() const override { return false; } virtual bool on_is_selectable() const override { return false; }
virtual void on_set_state() override; virtual void on_set_state() override;
// must implement
virtual bool on_init() override { return true;};
virtual void on_render() override{};
virtual void on_render_for_picking() override{};
// GLGizmoPainterBase // GLGizmoPainterBase
virtual void render_painter_gizmo() const override{ render_wireframe(); } virtual void render_painter_gizmo() const override{ render_wireframe(); }
private: private:
void after_apply(); void after_apply();
void close(); void close();
void live_preview();
void process(); void process();
void set_its(indexed_triangle_set &its); void set_its(indexed_triangle_set &its);
void create_gui_cfg(); void create_gui_cfg();

View File

@ -935,6 +935,10 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; }
default: { break; } default: { break; }
} }
} else if (m_current == Simplify && keyCode == WXK_ESCAPE) {
GLGizmoSimplify *simplify = dynamic_cast<GLGizmoSimplify *>(get_current());
if (simplify != nullptr)
processed = simplify->on_esc_key_down();
} }
} }

View File

@ -280,10 +280,10 @@ void ImGuiWrapper::render()
m_new_frame_open = false; m_new_frame_open = false;
} }
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width)
{ {
auto text_utf8 = into_u8(text); auto text_utf8 = into_u8(text);
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str()); ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width);
/*#ifdef __linux__ /*#ifdef __linux__
size.x *= m_style_scaling; size.x *= m_style_scaling;
@ -293,6 +293,13 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text)
return size; return size;
} }
float ImGuiWrapper::get_slider_float_height() const
{
const ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
return g.FontSize + style.FramePadding.y * 2.0f + style.ItemSpacing.y;
}
void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y)
{ {
ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y));
@ -425,6 +432,42 @@ void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label)
this->text_colored(color, label_utf8.c_str()); this->text_colored(color, label_utf8.c_str());
} }
void ImGuiWrapper::text_wrapped(const char *label, float wrap_width)
{
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
this->text(label);
ImGui::PopTextWrapPos();
}
void ImGuiWrapper::text_wrapped(const std::string &label, float wrap_width)
{
this->text_wrapped(label.c_str(), wrap_width);
}
void ImGuiWrapper::text_wrapped(const wxString &label, float wrap_width)
{
auto label_utf8 = into_u8(label);
this->text_wrapped(label_utf8.c_str(), wrap_width);
}
void ImGuiWrapper::tooltip(const char *label, float wrap_width)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(wrap_width);
ImGui::TextUnformatted(label);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
void ImGuiWrapper::tooltip(const wxString &label, float wrap_width)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(wrap_width);
ImGui::TextUnformatted(label.ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/) bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/)
{ {
bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power); bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power);

View File

@ -53,7 +53,9 @@ public:
float scaled(float x) const { return x * m_font_size; } float scaled(float x) const { return x * m_font_size; }
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
ImVec2 calc_text_size(const wxString &text); ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f);
float get_slider_float_height() const;
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
void set_next_window_bg_alpha(float alpha); void set_next_window_bg_alpha(float alpha);
@ -79,6 +81,11 @@ public:
void text_colored(const ImVec4& color, const char* label); void text_colored(const ImVec4& color, const char* label);
void text_colored(const ImVec4& color, const std::string& label); void text_colored(const ImVec4& color, const std::string& label);
void text_colored(const ImVec4& color, const wxString& label); void text_colored(const ImVec4& color, const wxString& label);
void text_wrapped(const char *label, float wrap_width);
void text_wrapped(const std::string &label, float wrap_width);
void text_wrapped(const wxString &label, float wrap_width);
void tooltip(const char *label, float wrap_width);
void tooltip(const wxString &label, float wrap_width);
// Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true). // Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true).
bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true); bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true);

View File

@ -60,6 +60,7 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat
_u8L("Undo desktop integration was successful.") }, _u8L("Undo desktop integration was successful.") },
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
_u8L("Undo desktop integration failed.") }, _u8L("Undo desktop integration failed.") },
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") }, //{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") },
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification //{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
@ -214,7 +215,8 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
} }
if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) {
ImGui::SetNextWindowFocus(); // Uncomment if imgui window focus is needed on hover. I cant find any case.
//ImGui::SetNextWindowFocus();
set_hovered(); set_hovered();
} }
@ -1151,6 +1153,8 @@ bool NotificationManager::SlicingProgressNotification::set_progress_state(Notifi
m_sp_state = state; m_sp_state = state;
return true; return true;
case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED:
if (m_sp_state != SlicingProgressState::SP_BEGAN && m_sp_state != SlicingProgressState::SP_PROGRESS)
return false;
set_percentage(1); set_percentage(1);
m_has_cancel_button = false; m_has_cancel_button = false;
m_has_print_info = false; m_has_print_info = false;
@ -1508,6 +1512,16 @@ void NotificationManager::push_notification(NotificationType type,
int duration = get_standart_duration(level); int duration = get_standart_duration(level);
push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp); push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp);
} }
void NotificationManager::push_delayed_notification(const NotificationType type, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval)
{
auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications),
boost::bind(&NotificationData::type, boost::placeholders::_1) == type);
assert(it != std::end(basic_notifications));
if (it != std::end(basic_notifications))
push_delayed_notification_data(std::make_unique<PopNotification>(*it, m_id_provider, m_evt_handler), condition_callback, initial_delay, delay_interval);
}
void NotificationManager::push_validate_error_notification(const std::string& text) void NotificationManager::push_validate_error_notification(const std::string& text)
{ {
push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0);
@ -1911,7 +1925,7 @@ void NotificationManager::push_hint_notification(bool open_next)
auto condition = [&self = std::as_const(*this)]() { auto condition = [&self = std::as_const(*this)]() {
return self.get_notification_count() == 0; return self.get_notification_count() == 0;
}; };
push_delayed_notification(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); push_delayed_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000);
} }
} }
@ -1974,7 +1988,7 @@ bool NotificationManager::push_notification_data(std::unique_ptr<NotificationMan
return retval; return retval;
} }
void NotificationManager::push_delayed_notification(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval) void NotificationManager::push_delayed_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval)
{ {
if (initial_delay == 0 && condition_callback()) { if (initial_delay == 0 && condition_callback()) {
if( push_notification_data(std::move(notification), 0)) if( push_notification_data(std::move(notification), 0))

View File

@ -109,8 +109,10 @@ enum class NotificationType
// Give user advice to simplify object with big amount of triangles // Give user advice to simplify object with big amount of triangles
// Contains ObjectID for closing when object is deleted // Contains ObjectID for closing when object is deleted
SimplifySuggestion, SimplifySuggestion,
// information about netfabb is finished repairing model (blocking proccess) // information about netfabb is finished repairing model (blocking proccess)
NetfabbFinished NetfabbFinished,
// Short meesage to fill space between start and finish of export
ExportOngoing
}; };
class NotificationManager class NotificationManager
@ -151,6 +153,10 @@ public:
// ErrorNotificationLevel and ImportantNotificationLevel are never faded out. // ErrorNotificationLevel and ImportantNotificationLevel are never faded out.
void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "",
std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>(), int timestamp = 0); std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>(), int timestamp = 0);
// Pushes basic_notification with delay. See push_delayed_notification_data.
void push_delayed_notification(const NotificationType type, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval);
// Removes all notifications of type from m_waiting_notifications
void stop_delayed_notifications_of_type(const NotificationType type);
// Creates Validate Error notification with a custom text and no fade out. // Creates Validate Error notification with a custom text and no fade out.
void push_validate_error_notification(const std::string& text); void push_validate_error_notification(const std::string& text);
// Creates Slicing Error notification with a custom text and no fade out. // Creates Slicing Error notification with a custom text and no fade out.
@ -699,10 +705,8 @@ private:
// and condition callback is success, notification is regular pushed from update function. // and condition callback is success, notification is regular pushed from update function.
// Otherwise another delay interval waiting. Timestamp is 0. // Otherwise another delay interval waiting. Timestamp is 0.
// Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push. // Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push.
// Also note that no control of same notification is done during push_delayed_notification but if waiting notif fails to push, it continues waiting. // Also note that no control of same notification is done during push_delayed_notification_data but if waiting notif fails to push, it continues waiting.
void push_delayed_notification(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval); void push_delayed_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval);
// Removes all notifications of type from m_waiting_notifications
void stop_delayed_notifications_of_type(const NotificationType type);
//finds older notification of same type and moves it to the end of queue. returns true if found //finds older notification of same type and moves it to the end of queue. returns true if found
bool activate_existing(const NotificationManager::PopNotification* notification); bool activate_existing(const NotificationManager::PopNotification* notification);
// Put the more important notifications to the bottom of the list. // Put the more important notifications to the bottom of the list.

View File

@ -4025,6 +4025,7 @@ void Plater::priv::on_export_began(wxCommandEvent& evt)
{ {
if (show_warning_dialog) if (show_warning_dialog)
warnings_dialog(); warnings_dialog();
notification_manager->push_delayed_notification(NotificationType::ExportOngoing, [](){return true;}, 1000, 1000);
} }
void Plater::priv::on_slicing_began() void Plater::priv::on_slicing_began()
{ {
@ -4157,6 +4158,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
if(wxGetApp().get_mode() == comSimple) { if(wxGetApp().get_mode() == comSimple) {
show_action_buttons(false); show_action_buttons(false);
} }
if (exporting_status != ExportingStatus::NOT_EXPORTING && !has_error) {
notification_manager->stop_delayed_notifications_of_type(NotificationType::ExportOngoing);
notification_manager->close_notification_of_type(NotificationType::ExportOngoing);
}
// If writing to removable drive was scheduled, show notification with eject button // If writing to removable drive was scheduled, show notification with eject button
if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) {
show_action_buttons(false); show_action_buttons(false);

View File

@ -452,8 +452,10 @@ void PreferencesDialog::build(size_t selected_tab)
activate_options_tab(m_optgroup_gui); activate_options_tab(m_optgroup_gui);
// set Field for notify_release to its value to activate the object // set Field for notify_release to its value to activate the object
boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); if (is_editor) {
m_optgroup_gui->get_field("notify_release")->set_value(val, false); boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release"));
m_optgroup_gui->get_field("notify_release")->set_value(val, false);
}
if (is_editor) { if (is_editor) {
create_icon_size_slider(); create_icon_size_slider();

View File

@ -6,6 +6,7 @@
#include "slic3r/GUI/format.hpp" #include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
@ -17,6 +18,7 @@
#include <boost/algorithm/hex.hpp> #include <boost/algorithm/hex.hpp>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim_all.hpp> #include <boost/algorithm/string/trim_all.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/uuid/detail/md5.hpp> #include <boost/uuid/detail/md5.hpp>
@ -36,12 +38,17 @@
#include <Iphlpapi.h> #include <Iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "iphlpapi.lib")
#elif __APPLE__ #elif __APPLE__
#import <IOKit/IOKitLib.h> #import <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#else // Linux/BSD
#include <charconv>
#endif #endif
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
static const std::string SEND_SYSTEM_INFO_DOMAIN = "prusa3d.com";
static const std::string SEND_SYSTEM_INFO_URL = "https://files." + SEND_SYSTEM_INFO_DOMAIN + "/wp-json/v1/ps";
// Declaration of a free function defined in OpenGLManager.cpp: // Declaration of a free function defined in OpenGLManager.cpp:
@ -52,8 +59,8 @@ std::string gl_get_string_safe(GLenum param, const std::string& default_value);
class SendSystemInfoDialog : public DPIDialog class SendSystemInfoDialog : public DPIDialog
{ {
enum { enum {
MIN_WIDTH = 80, MIN_WIDTH = 70,
MIN_HEIGHT = 50 MIN_HEIGHT = 34
}; };
public: public:
@ -129,20 +136,36 @@ public:
// current version is newer. Only major and minor versions are compared. // current version is newer. Only major and minor versions are compared.
static bool should_dialog_be_shown() static bool should_dialog_be_shown()
{ {
return false;
std::string last_sent_version = wxGetApp().app_config->get("version_system_info_sent"); std::string last_sent_version = wxGetApp().app_config->get("version_system_info_sent");
Semver semver_current(SLIC3R_VERSION); Semver semver_current(SLIC3R_VERSION);
Semver semver_last_sent; Semver semver_last_sent;
if (! last_sent_version.empty()) if (! last_sent_version.empty())
semver_last_sent = Semver(last_sent_version); semver_last_sent = Semver(last_sent_version);
if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") // set whether to show in alpha builds, or only betas/rcs/finals:
return false; // Don't show in alphas. const bool show_in_alphas = true;
// Show the dialog if current > last, but they differ in more than just patch. if (! show_in_alphas && semver_current.prerelease()
return ((semver_current.maj() > semver_last_sent.maj()) && std::string(semver_current.prerelease()).find("alpha") != std::string::npos)
return false;
// New version means current > last, but they must differ in more than just patch.
bool new_version = ((semver_current.maj() > semver_last_sent.maj())
|| (semver_current.maj() == semver_last_sent.maj() && semver_current.min() > semver_last_sent.min() )); || (semver_current.maj() == semver_last_sent.maj() && semver_current.min() > semver_last_sent.min() ));
if (! new_version)
return false;
// We'll misuse the version check to check internet connection here.
bool is_internet = false;
Http::get(wxGetApp().app_config->version_check_url())
.size_limit(SLIC3R_VERSION_BODY_MAX)
.timeout_max(2)
.on_complete([&](std::string, unsigned) {
is_internet = true;
})
.perform_sync();
return is_internet;
} }
@ -162,9 +185,10 @@ static std::map<std::string, std::string> get_cpu_info_from_registry()
std::map<std::string, std::string> out; std::map<std::string, std::string> out;
int idx = -1; int idx = -1;
constexpr DWORD bufsize_ = 200; constexpr DWORD bufsize_ = 500;
DWORD bufsize = bufsize_; DWORD bufsize = bufsize_-1; // Ensure a terminating zero.
char buf[bufsize_] = ""; char buf[bufsize_] = "";
memset(buf, 0, bufsize_);
const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\"; const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\";
std::string reg_path = reg_dir; std::string reg_path = reg_dir;
@ -186,7 +210,7 @@ static std::map<std::string, std::string> get_cpu_info_from_registry()
} }
++idx; ++idx;
reg_path = reg_dir + std::to_string(idx) + "\\"; reg_path = reg_dir + std::to_string(idx) + "\\";
bufsize = bufsize_; bufsize = bufsize_-1;
} }
return out; return out;
} }
@ -194,7 +218,7 @@ static std::map<std::string, std::string> get_cpu_info_from_registry()
static std::map<std::string, std::string> parse_lscpu_etc(const std::string& name, char delimiter) static std::map<std::string, std::string> parse_lscpu_etc(const std::string& name, char delimiter)
{ {
std::map<std::string, std::string> out; std::map<std::string, std::string> out;
constexpr size_t max_len = 100; constexpr size_t max_len = 1000;
char cline[max_len] = ""; char cline[max_len] = "";
FILE* fp = popen(name.data(), "r"); FILE* fp = popen(name.data(), "r");
if (fp != NULL) { if (fp != NULL) {
@ -260,10 +284,12 @@ static std::string get_unique_id()
char buf[buf_size] = ""; char buf[buf_size] = "";
memset(&buf, 0, sizeof(buf)); memset(&buf, 0, sizeof(buf));
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); if (ioRegistryRoot != MACH_PORT_NULL) {
IOObjectRelease(ioRegistryRoot); CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0);
CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); IOObjectRelease(ioRegistryRoot);
CFRelease(uuidCf); CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman);
CFRelease(uuidCf);
}
// Now convert the string to std::vector<unsigned char>. // Now convert the string to std::vector<unsigned char>.
for (char* c = buf; *c != 0; ++c) for (char* c = buf; *c != 0; ++c)
unique.emplace_back((unsigned char)(*c)); unique.emplace_back((unsigned char)(*c));
@ -318,11 +344,20 @@ static std::string generate_system_info_json()
std::string unique_id = get_unique_id(); std::string unique_id = get_unique_id();
// Get system language. // Get system language.
std::string sys_language = "Unknown"; std::string sys_language = "Unknown"; // important to init, see the __APPLE__ block.
const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); #ifndef __APPLE__
if (lang_system != wxLANGUAGE_UNKNOWN) // Following apparently does not work on macOS.
sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage());
if (lang_system != wxLANGUAGE_UNKNOWN)
sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data();
#else // __APPLE__
CFLocaleRef cflocale = CFLocaleCopyCurrent();
CFStringRef value = (CFStringRef)CFLocaleGetValue(cflocale, kCFLocaleLanguageCode);
char temp[10] = "";
CFStringGetCString(value, temp, 10, kCFStringEncodingUTF8);
sys_language = temp;
CFRelease(cflocale);
#endif
// Build a property tree with all the information. // Build a property tree with all the information.
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
@ -364,9 +399,13 @@ static std::string generate_system_info_json()
data_node.put("SystemLanguage", sys_language); data_node.put("SystemLanguage", sys_language);
data_node.put("TranslationLanguage: ", wxGetApp().app_config->get("translation_language")); data_node.put("TranslationLanguage: ", wxGetApp().app_config->get("translation_language"));
pt::ptree hw_node; pt::ptree hw_node;
hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); {
hw_node.put("RAM_MB", size_t(Slic3r::total_physical_memory()/1000000)); hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName());
size_t num = std::round(Slic3r::total_physical_memory()/107374100.);
hw_node.put("RAM_GiB", std::to_string(num / 10) + "." + std::to_string(num % 10));
}
// Now get some CPU info: // Now get some CPU info:
pt::ptree cpu_node; pt::ptree cpu_node;
@ -381,31 +420,32 @@ static std::string generate_system_info_json()
cpu_node.put("Model", sysctl["machdep.cpu.brand_string"]); cpu_node.put("Model", sysctl["machdep.cpu.brand_string"]);
cpu_node.put("Vendor", sysctl["machdep.cpu.vendor"]); cpu_node.put("Vendor", sysctl["machdep.cpu.vendor"]);
#else // linux/BSD #else // linux/BSD
std::map<std::string, std::string> lscpu = parse_lscpu_etc("lscpu", ':'); std::map<std::string, std::string> lscpu = parse_lscpu_etc("cat /proc/cpuinfo", ':');
cpu_node.put("Arch", lscpu["Architecture"]); if (auto ncpu_it = lscpu.find("processor"); ncpu_it != lscpu.end()) {
cpu_node.put("Cores", lscpu["CPU(s)"]); std::string& ncpu = ncpu_it->second;
cpu_node.put("Model", lscpu["Model name"]); if (int num=0; std::from_chars(ncpu.data(), ncpu.data() + ncpu.size(), num).ec != std::errc::invalid_argument)
cpu_node.put("Vendor", lscpu["Vendor ID"]); ncpu = std::to_string(num + 1);
}
cpu_node.put("Cores", lscpu["processor"]);
cpu_node.put("Model", lscpu["model name"]);
cpu_node.put("Vendor", lscpu["vendor_id"]);
#endif #endif
hw_node.add_child("CPU", cpu_node); hw_node.add_child("CPU", cpu_node);
pt::ptree monitors_node; pt::ptree monitors_node;
for (int i=0; i<int(wxDisplay::GetCount()); ++i) { for (int i=0; i<int(wxDisplay::GetCount()); ++i) {
wxDisplay display(i); wxDisplay display(i);
double scaling = -1.;
#if wxCHECK_VERSION(3, 1, 2) // we have wxDisplag::GetPPI
int std_ppi = 96;
#ifdef __WXOSX__ // see impl of wxDisplay::GetStdPPIValue from 3.1.5
std_ppi = 72;
#endif
scaling = double(display.GetPPI().GetWidth()) / std_ppi;
#endif
pt::ptree monitor_node; // Create an unnamed node containing the value pt::ptree monitor_node; // Create an unnamed node containing the value
monitor_node.put("width", display.GetGeometry().GetWidth()); monitor_node.put("width", display.GetGeometry().GetWidth());
monitor_node.put("height", display.GetGeometry().GetHeight()); monitor_node.put("height", display.GetGeometry().GetHeight());
std::stringstream ss;
ss << std::setprecision(3) << scaling; // Only get the scaling on Win, it is not reliable on other platforms.
monitor_node.put("scaling", ss.str() ); #if defined(_WIN32) && wxCHECK_VERSION(3, 1, 2)
double scaling = display.GetPPI().GetWidth() / 96.;
std::stringstream ss;
ss << std::setprecision(3) << scaling;
monitor_node.put("scaling", ss.str() );
#endif
monitors_node.push_back(std::make_pair("", monitor_node)); monitors_node.push_back(std::make_pair("", monitor_node));
} }
hw_node.add_child("Monitors", monitors_node); hw_node.add_child("Monitors", monitors_node);
@ -435,15 +475,23 @@ static std::string generate_system_info_json()
pt::ptree root; pt::ptree root;
root.add_child("data", data_node); root.add_child("data", data_node);
// Now go through all the values and trim leading/trailing whitespace.
// Some CPU names etc apparently have trailing spaces...
std::function<void(pt::ptree&)> remove_whitespace;
remove_whitespace = [&remove_whitespace](pt::ptree& t) -> void
{
if (t.empty()) // Trim whitespace
boost::algorithm::trim(t.data());
else
for (auto it = t.begin(); it != t.end(); ++it)
remove_whitespace(it->second);
};
remove_whitespace(root);
// Serialize the tree into JSON and return it. // Serialize the tree into JSON and return it.
std::stringstream ss; std::stringstream ss;
pt::write_json(ss, root); pt::write_json(ss, root);
return ss.str(); return ss.str();
// FURTHER THINGS TO CONSIDER:
//std::cout << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << std::endl; // Unix
// ? CPU, GPU, UNKNOWN ?
// printers? will they be installed already?
} }
@ -453,6 +501,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
GUI::DPIDialog(parent, wxID_ANY, _L("Send system info"), wxDefaultPosition, wxDefaultSize, GUI::DPIDialog(parent, wxID_ANY, _L("Send system info"), wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE) wxDEFAULT_DIALOG_STYLE)
{ {
const int em = GUI::wxGetApp().em_unit();
// Get current PrusaSliver version info. // Get current PrusaSliver version info.
std::string app_name; std::string app_name;
{ {
@ -500,7 +550,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
std::string("<i>") + filename + "</i>"); std::string("<i>") + filename + "</i>");
wxString label3 = _L("Show verbatim data that will be sent"); wxString label3 = _L("Show verbatim data that will be sent");
auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(70*em, 34*em), wxHW_SCROLLBAR_NEVER);
wxString html = GUI::format_wxstr( wxString html = GUI::format_wxstr(
"<html><body bgcolor=%1%><font color=%2%>" "<html><body bgcolor=%1%><font color=%2%>"
"<table><tr><td>" "<table><tr><td>"
@ -514,7 +564,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
+ "<b><a href=\"show\">" + label3 + "</a></b><br />" + "<b><a href=\"show\">" + label3 + "</a></b><br />"
+ "</font></body></html>", bgr_clr_str, text_clr_str); + "</font></body></html>", bgr_clr_str, text_clr_str);
html_window->SetPage(html); html_window->SetPage(html);
html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent &evt) { html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent&) {
ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7)); ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7));
dlg.ShowModal(); dlg.ShowModal();
}); });
@ -526,7 +576,6 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info")); m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info"));
auto* hsizer = new wxBoxSizer(wxHORIZONTAL); auto* hsizer = new wxBoxSizer(wxHORIZONTAL);
const int em = GUI::wxGetApp().em_unit();
hsizer->Add(m_btn_ask_later); hsizer->Add(m_btn_ask_later);
hsizer->AddSpacer(em); hsizer->AddSpacer(em);
hsizer->Add(m_btn_dont_send); hsizer->Add(m_btn_dont_send);
@ -548,6 +597,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
SetSize(std::max(size.GetWidth(), MIN_WIDTH * em), SetSize(std::max(size.GetWidth(), MIN_WIDTH * em),
std::max(size.GetHeight(), MIN_HEIGHT * em)); std::max(size.GetHeight(), MIN_HEIGHT * em));
CenterOnParent();
m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&) m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&)
{ {
if (send_info()) { if (send_info()) {
@ -592,15 +643,16 @@ bool SendSystemInfoDialog::send_info()
} result; // No synchronization needed, UI thread reads only after worker is joined. } result; // No synchronization needed, UI thread reads only after worker is joined.
auto send = [&job_done, &result](const std::string& data) { auto send = [&job_done, &result](const std::string& data) {
const std::string url = "https://files.prusa3d.com/wp-json/v1/ps"; Http http = Http::post(SEND_SYSTEM_INFO_URL);
Http http = Http::post(url);
http.header("Content-Type", "application/json") http.header("Content-Type", "application/json")
.timeout_max(6) // seconds
.set_post_body(data) .set_post_body(data)
.on_complete([&result](std::string body, unsigned status) { .on_complete([&result](std::string body, unsigned status) {
result = { Result::Success, _L("System info sent successfully. Thank you.") }; result = { Result::Success, _L("System info sent successfully. Thank you.") };
}) })
.on_error([&result](std::string body, std::string error, unsigned status) { .on_error([&result](std::string body, std::string error, unsigned status) {
result = { Result::Error, GUI::format_wxstr(_L("Sending system info failed! Status: %1%"), status) }; result = { Result::Error, _L("Sending system info failed!") };
BOOST_LOG_TRIVIAL(error) << "Sending system info failed! STATUS: " << status;
}) })
.on_progress([&job_done, &result](Http::Progress, bool &cancel) { .on_progress([&job_done, &result](Http::Progress, bool &cancel) {
if (job_done) // UI thread wants us to cancel. if (job_done) // UI thread wants us to cancel.
@ -622,8 +674,10 @@ bool SendSystemInfoDialog::send_info()
job_done = true; // In case the user closed the dialog, let the other thread know job_done = true; // In case the user closed the dialog, let the other thread know
sending_thread.join(); // and wait until it terminates. sending_thread.join(); // and wait until it terminates.
InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); if (result.value != Result::Cancelled) { // user knows he cancelled, no need to tell him.
info_dlg.ShowModal(); InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str);
info_dlg.ShowModal();
}
return result.value == Result::Success; return result.value == Result::Success;
} }

View File

@ -104,6 +104,7 @@ struct Http::priv
{ {
enum { enum {
DEFAULT_TIMEOUT_CONNECT = 10, DEFAULT_TIMEOUT_CONNECT = 10,
DEFAULT_TIMEOUT_MAX = 0,
DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024, DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
}; };
@ -137,6 +138,7 @@ struct Http::priv
static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
void set_timeout_connect(long timeout); void set_timeout_connect(long timeout);
void set_timeout_max(long timeout);
void form_add_file(const char *name, const fs::path &path, const char* filename); void form_add_file(const char *name, const fs::path &path, const char* filename);
void set_post_body(const fs::path &path); void set_post_body(const fs::path &path);
void set_post_body(const std::string &body); void set_post_body(const std::string &body);
@ -163,6 +165,7 @@ Http::priv::priv(const std::string &url)
} }
set_timeout_connect(DEFAULT_TIMEOUT_CONNECT); set_timeout_connect(DEFAULT_TIMEOUT_CONNECT);
set_timeout_max(DEFAULT_TIMEOUT_MAX);
::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION); ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION);
::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front()); ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front());
@ -253,6 +256,11 @@ void Http::priv::set_timeout_connect(long timeout)
::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); ::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout);
} }
void Http::priv::set_timeout_max(long timeout)
{
::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
}
void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename)
{ {
// We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
@ -409,6 +417,13 @@ Http& Http::timeout_connect(long timeout)
return *this; return *this;
} }
Http& Http::timeout_max(long timeout)
{
if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_MAX; }
if (p) { p->set_timeout_max(timeout); }
return *this;
}
Http& Http::size_limit(size_t sizeLimit) Http& Http::size_limit(size_t sizeLimit)
{ {
if (p) { p->limit = sizeLimit; } if (p) { p->limit = sizeLimit; }

View File

@ -58,6 +58,8 @@ public:
// Sets a maximum connection timeout in seconds // Sets a maximum connection timeout in seconds
Http& timeout_connect(long timeout); Http& timeout_connect(long timeout);
// Sets a maximum total request timeout in seconds
Http& timeout_max(long timeout);
// Sets a maximum size of the data that can be received. // Sets a maximum size of the data that can be received.
// A value of zero sets the default limit, which is is 5MB. // A value of zero sets the default limit, which is is 5MB.
Http& size_limit(size_t sizeLimit); Http& size_limit(size_t sizeLimit);

View File

@ -46,10 +46,6 @@ using Slic3r::GUI::Config::SnapshotDB;
namespace Slic3r { namespace Slic3r {
enum {
SLIC3R_VERSION_BODY_MAX = 256,
};
static const char *INDEX_FILENAME = "index.idx"; static const char *INDEX_FILENAME = "index.idx";
static const char *TMP_EXTENSION = ".download"; static const char *TMP_EXTENSION = ".download";

View File

@ -13,6 +13,8 @@ class AppConfig;
class PresetBundle; class PresetBundle;
class Semver; class Semver;
const int SLIC3R_VERSION_BODY_MAX = 256;
class PresetUpdater class PresetUpdater
{ {
public: public: