Fixed conflicts after merge with master and ported changes into gouraud shaders to gouraud_mod shaders

This commit is contained in:
enricoturri1966 2021-10-19 11:27:11 +02:00
commit 2c0815f537
72 changed files with 1750 additions and 1043 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

@ -45,6 +45,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;
@ -62,8 +64,6 @@ varying vec4 world_pos;
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)))
@ -71,36 +71,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;
} }
@ -123,8 +94,13 @@ void main()
#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

@ -43,34 +43,29 @@ varying vec4 world_pos;
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.
world_pos = volume_world_matrix * gl_Vertex; world_pos = volume_world_matrix * gl_Vertex;
// 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

@ -155,7 +155,20 @@ bool PolyNode::IsHole() const
node = node->Parent; node = node->Parent;
} }
return result; return result;
} }
void PolyTree::RemoveOutermostPolygon()
{
if (this->ChildCount() == 1 && this->Childs[0]->ChildCount() > 0) {
PolyNode *outerNode = this->Childs[0];
this->Childs.reserve(outerNode->ChildCount());
this->Childs[0] = outerNode->Childs[0];
this->Childs[0]->Parent = outerNode->Parent;
for (int i = 1; i < outerNode->ChildCount(); ++i)
this->AddChild(*outerNode->Childs[i]);
} else
this->Clear();
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Miscellaneous global functions // Miscellaneous global functions
@ -3444,7 +3457,8 @@ void ClipperOffset::Execute(Paths& solution, double delta)
clpr.AddPath(outer, ptSubject, true); clpr.AddPath(outer, ptSubject, true);
clpr.ReverseSolution(true); clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative); clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
if (solution.size() > 0) solution.erase(solution.begin()); if (! solution.empty())
solution.erase(solution.begin());
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -3475,17 +3489,7 @@ void ClipperOffset::Execute(PolyTree& solution, double delta)
clpr.ReverseSolution(true); clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative); clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
//remove the outer PolyNode rectangle ... //remove the outer PolyNode rectangle ...
if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) solution.RemoveOutermostPolygon();
{
PolyNode* outerNode = solution.Childs[0];
solution.Childs.reserve(outerNode->ChildCount());
solution.Childs[0] = outerNode->Childs[0];
solution.Childs[0]->Parent = outerNode->Parent;
for (int i = 1; i < outerNode->ChildCount(); ++i)
solution.AddChild(*outerNode->Childs[i]);
}
else
solution.Clear();
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View file

@ -180,6 +180,7 @@ public:
PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); } PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); }
void Clear() { AllNodes.clear(); Childs.clear(); } void Clear() { AllNodes.clear(); Childs.clear(); }
int Total() const; int Total() const;
void RemoveOutermostPolygon();
private: private:
PolyTree(const PolyTree &src) = delete; PolyTree(const PolyTree &src) = delete;
PolyTree& operator=(const PolyTree &src) = delete; PolyTree& operator=(const PolyTree &src) = delete;
@ -521,6 +522,7 @@ public:
double MiterLimit; double MiterLimit;
double ArcTolerance; double ArcTolerance;
double ShortestEdgeLength; double ShortestEdgeLength;
private: private:
Paths m_destPolys; Paths m_destPolys;
Path m_srcPoly; Path m_srcPoly;
@ -528,6 +530,8 @@ private:
std::vector<DoublePoint> m_normals; std::vector<DoublePoint> m_normals;
double m_delta, m_sinA, m_sin, m_cos; double m_delta, m_sinA, m_sin, m_cos;
double m_miterLim, m_StepsPerRad; double m_miterLim, m_StepsPerRad;
// x: index of the lowest contour in m_polyNodes
// y: index of the lowest point in the lowest contour
IntPoint m_lowest; IntPoint m_lowest;
PolyNode m_polyNodes; PolyNode m_polyNodes;

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;
} }
@ -177,7 +177,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare))); append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare)));
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
@ -230,13 +230,13 @@ static ExPolygons inner_brim_area(const Print &print,
} }
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare))); append(brim_area_object, diff_ex(shrink_ex(ex_poly.holes, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly.holes, brim_width + brim_separation, ClipperLib::jtSquare)));
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
append(holes_object, ex_poly.holes); append(holes_object, ex_poly.holes);
} }
@ -385,10 +385,10 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing())); size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing()));
for (size_t i = 0; i < num_loops; ++i) { for (size_t i = 0; i < num_loops; ++i) {
try_cancel(); try_cancel();
islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); islands = expand(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
for (Polygon &poly : islands) for (Polygon &poly : islands)
poly.douglas_peucker(SCALED_RESOLUTION); poly.douglas_peucker(SCALED_RESOLUTION);
polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); polygons_append(loops, shrink(islands, 0.5f * float(flow.scaled_spacing())));
} }
loops = union_pt_chained_outside_in(loops); loops = union_pt_chained_outside_in(loops);

View file

@ -117,15 +117,6 @@ Polylines PolyTreeToPolylines(ClipperLib::PolyTree &&polytree)
return out; return out;
} }
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(input, ClipperLib::ptSubject, true);
ClipperLib::PolyTree polytree;
clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero
return PolyTreeToExPolygons(std::move(polytree));
}
#if 0 #if 0
// Global test. // Global test.
bool has_duplicate_points(const ClipperLib::PolyTree &polytree) bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
@ -165,23 +156,28 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
} }
#endif #endif
// Offset outside by 10um, one by one. // Offset CCW contours outside, CW contours (holes) inside.
template<typename PathsProvider> // Don't calculate union of the output paths.
static ClipperLib::Paths safety_offset(PathsProvider &&paths) template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{ {
ClipperLib::ClipperOffset co; ClipperLib::ClipperOffset co;
ClipperLib::Paths out; ClipperLib::Paths out;
out.reserve(paths.size()); out.reserve(paths.size());
ClipperLib::Paths out_this; ClipperLib::Paths out_this;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
for (const ClipperLib::Path &path : paths) { for (const ClipperLib::Path &path : paths) {
co.Clear(); co.Clear();
co.MiterLimit = 2.;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output // Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented. // contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed. // Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
co.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); co.AddPath(path, joinType, endType);
bool ccw = ClipperLib::Orientation(path); bool ccw = endType == ClipperLib::etClosedPolygon ? ClipperLib::Orientation(path) : true;
co.Execute(out_this, ccw ? ClipperSafetyOffset : - ClipperSafetyOffset); co.Execute(out_this, ccw ? offset : - offset);
if (! ccw) { if (! ccw) {
// Reverse the resulting contours. // Reverse the resulting contours.
for (ClipperLib::Path &path : out_this) for (ClipperLib::Path &path : out_this)
@ -192,38 +188,122 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths)
return out; return out;
} }
// Only safe for a single path. // Offset outside by 10um, one by one.
template<typename PathsProvider> template<typename PathsProvider>
ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) static ClipperLib::Paths safety_offset(PathsProvider &&paths)
{ {
// perform offset return raw_offset(std::forward<PathsProvider>(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit);
ClipperLib::ClipperOffset co; }
if (joinType == jtRound)
co.ArcTolerance = miterLimit; template<class TResult, class TSubj, class TClip>
else TResult clipper_do(
co.MiterLimit = miterLimit; const ClipperLib::ClipType clipType,
float delta_scaled = delta; TSubj && subject,
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); TClip && clip,
co.AddPaths(std::forward<PathsProvider>(input), joinType, endType); const ClipperLib::PolyFillType fillType)
ClipperLib::Paths retval; {
co.Execute(retval, delta_scaled); ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true);
TResult retval;
clipper.Execute(clipType, retval, fillType, fillType);
return retval; return retval;
} }
Slic3r::Polygons offset(const Slic3r::Polygon& polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) template<class TResult, class TSubj, class TClip>
{ return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } TResult clipper_do(
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType,
const ApplySafetyOffset do_safety_offset)
{
// Safety offset only allowed on intersection and difference.
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
return do_safety_offset == ApplySafetyOffset::Yes ?
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
}
template<class TResult, class TSubj>
TResult clipper_union(
TSubj && subject,
// fillType pftNonZero and pftPositive "should" produce the same result for "normalized with implicit union" set of polygons
const ClipperLib::PolyFillType fillType = ClipperLib::pftNonZero)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
TResult retval;
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
return retval;
}
// Perform union of input polygons using the positive rule, convert to ExPolygons.
//FIXME is there any benefit of not doing the boolean / using pftEvenOdd?
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union)
{
return PolyTreeToExPolygons(clipper_union<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit);
}
template<class TResult, typename PathsProvider>
static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
return clipper_union<TResult>(raw_offset(std::forward<PathsProvider>(paths), offset, joinType, miterLimit));
}
// used by shrink_paths()
template<class Container> static void remove_outermost_polygon(Container & solution);
template<> void remove_outermost_polygon<ClipperLib::Paths>(ClipperLib::Paths &solution)
{ if (! solution.empty()) solution.erase(solution.begin()); }
template<> void remove_outermost_polygon<ClipperLib::PolyTree>(ClipperLib::PolyTree &solution)
{ solution.RemoveOutermostPolygon(); }
template<class TResult, typename PathsProvider>
static TResult shrink_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
TResult out;
if (auto raw = raw_offset(std::forward<PathsProvider>(paths), - offset, joinType, miterLimit); ! raw.empty()) {
ClipperLib::Clipper clipper;
clipper.AddPaths(raw, ClipperLib::ptSubject, true);
ClipperLib::IntRect r = clipper.GetBounds();
clipper.AddPath({ { r.left - 10, r.bottom + 10 }, { r.right + 10, r.bottom + 10 }, { r.right + 10, r.top - 10 }, { r.left - 10, r.top - 10 } }, ClipperLib::ptSubject, true);
clipper.ReverseSolution(true);
clipper.Execute(ClipperLib::ctUnion, out, ClipperLib::pftNegative, ClipperLib::pftNegative);
remove_outermost_polygon(out);
}
return out;
}
template<class TResult, typename PathsProvider>
static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset != 0);
return offset > 0 ?
expand_paths<TResult>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit) :
shrink_paths<TResult>(std::forward<PathsProvider>(paths), - offset, joinType, miterLimit);
}
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(raw_offset(ClipperUtils::SinglePathProvider(polygon.points), delta, joinType, miterLimit)); }
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } { return to_polygons(offset_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } { return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
#endif // CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(ClipperUtils::SinglePathProvider(polyline.points), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } { assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); }
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(ClipperUtils::PolylinesProvider(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } { assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); }
// returns number of expolygons collected (0 or 1). // returns number of expolygons collected (0 or 1).
static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
@ -274,14 +354,8 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
append(out, std::move(contours)); append(out, std::move(contours));
} else if (delta < 0) { } else if (delta < 0) {
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour. // Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
// Subtract the offsetted holes from the offsetted contours. // Subtract the offsetted holes from the offsetted contours.
ClipperLib::Clipper clipper; if (auto output = clipper_do<ClipperLib::Paths>(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) {
clipper.Clear();
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
ClipperLib::Paths output;
clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
if (! output.empty()) {
append(out, std::move(output)); append(out, std::move(output));
} else { } else {
// The offsetted holes have eaten up the offsetted outer contour. // The offsetted holes have eaten up the offsetted outer contour.
@ -308,7 +382,7 @@ static int offset_expolygon_inner(const Slic3r::Surface &surface, const float de
static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
{ return offset_expolygon_inner(surface->expolygon, delta, joinType, miterLimit, out); } { return offset_expolygon_inner(surface->expolygon, delta, joinType, miterLimit, out); }
ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) ClipperLib::Paths expolygon_offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ {
ClipperLib::Paths out; ClipperLib::Paths out;
offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out); offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out);
@ -317,9 +391,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta,
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons. // This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours. // It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united. // Each ExPolygon is offsetted separately. For outer offset, the the offsetted ExPolygons shall be united outside of this function.
template<typename ExPolygonVector> template<typename ExPolygonVector>
ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) static std::pair<ClipperLib::Paths, size_t> expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ {
// Offsetted ExPolygons before they are united. // Offsetted ExPolygons before they are united.
ClipperLib::Paths output; ClipperLib::Paths output;
@ -329,124 +403,101 @@ ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta,
size_t expolygons_collected = 0; size_t expolygons_collected = 0;
for (const auto &expoly : expolygons) for (const auto &expoly : expolygons)
expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output); expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output);
return std::make_pair(std::move(output), expolygons_collected);
}
// 4) Unite the offsetted expolygons. // See comment on expolygon_offsets_raw. In addition, for positive offset the contours are united.
if (expolygons_collected > 1 && delta > 0) { template<typename ExPolygonVector>
static ClipperLib::Paths expolygons_offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit);
// Unite the offsetted expolygons.
return expolygons_collected > 1 && delta > 0 ?
// There is a chance that the outwards offsetted expolygons may intersect. Perform a union. // There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
ClipperLib::Clipper clipper; clipper_union<ClipperLib::Paths>(output) :
clipper.Clear();
clipper.AddPaths(output, ClipperLib::ptSubject, true);
clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
} else {
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
} output;
}
return output;
// See comment on expolygons_offset_raw. In addition, the polygons are always united to conver to polytree.
template<typename ExPolygonVector>
static ClipperLib::PolyTree expolygons_offset_pt(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit);
// Unite the offsetted expolygons for both the
return clipper_union<ClipperLib::PolyTree>(output);
} }
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); } { return to_polygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); } { return to_polygons(expolygons_offset(expolygons, delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } { return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } { return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } //FIXME one may spare one Clipper Union call.
{ return ClipperPaths_to_Slic3rExPolygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } { return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(surfaces, delta, joinType, miterLimit)); } { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &polygons)
{ return offset(polygons, ClipperSafetyOffset); }
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons)
{ return offset_ex(polygons, ClipperSafetyOffset); }
#endif // CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons)
{ return offset(expolygons, ClipperSafetyOffset); }
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons)
{ return offset_ex(expolygons, ClipperSafetyOffset); }
ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
// prepare ClipperOffset object
ClipperLib::ClipperOffset co;
if (joinType == jtRound) {
co.ArcTolerance = miterLimit;
} else {
co.MiterLimit = miterLimit;
}
float delta_scaled1 = delta1;
float delta_scaled2 = delta2;
co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR);
// perform first offset
ClipperLib::Paths output1;
co.AddPaths(ClipperUtils::PolygonsProvider(polygons), joinType, ClipperLib::etClosedPolygon);
co.Execute(output1, delta_scaled1);
// perform second offset
co.Clear();
co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths retval;
co.Execute(retval, delta_scaled2);
return retval;
}
Polygons offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
return to_polygons(_offset2(polygons, delta1, delta2, joinType, miterLimit));
}
ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
return ClipperPaths_to_Slic3rExPolygons(_offset2(polygons, delta1, delta2, joinType, miterLimit));
}
//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper
// conversions and unnecessary Clipper calls. It is not that bad now as Clipper uses Slic3r's own Point / Polygon types directly.
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{ {
return offset(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); return to_polygons(offset_paths<ClipperLib::Paths>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{ {
return offset_ex(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
ExPolygons offset2_ex(const Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(surfaces, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
template<class TResult, class TSubj, class TClip> // Offset outside, then inside produces morphological closing. All deltas should be positive.
TResult _clipper_do( Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType)
{ {
ClipperLib::Clipper clipper; assert(delta1 > 0);
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true); assert(delta2 > 0);
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true); return to_polygons(shrink_paths<ClipperLib::Paths>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
TResult retval; }
clipper.Execute(clipType, retval, fillType, fillType); Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
return retval; {
assert(delta1 > 0);
assert(delta2 > 0);
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
assert(delta1 > 0);
assert(delta2 > 0);
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
template<class TResult, class TSubj, class TClip> // Offset inside, then outside produces morphological opening. All deltas should be positive.
TResult _clipper_do( Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType,
const ApplySafetyOffset do_safety_offset)
{ {
// Safety offset only allowed on intersection and difference. assert(delta1 > 0);
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); assert(delta2 > 0);
return do_safety_offset == ApplySafetyOffset::Yes ? return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) : }
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType); 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
@ -457,29 +508,22 @@ TResult _clipper_do(
// 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time. // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
// 2) Run Clipper Union once again to extract the PolyTree from the result of 1). // 2) Run Clipper Union once again to extract the PolyTree from the result of 1).
template<typename PathProvider1, typename PathProvider2> template<typename PathProvider1, typename PathProvider2>
inline ClipperLib::PolyTree _clipper_do_polytree2( inline ClipperLib::PolyTree clipper_do_polytree(
const ClipperLib::ClipType clipType, const ClipperLib::ClipType clipType,
PathProvider1 &&subject, PathProvider1 &&subject,
PathProvider2 &&clip, PathProvider2 &&clip,
const ClipperLib::PolyFillType fillType) const ClipperLib::PolyFillType fillType)
{ {
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<PathProvider1>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<PathProvider2>(clip), ClipperLib::ptClip, true);
// Perform the operation with the output to input_subject. // Perform the operation with the output to input_subject.
// This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library // This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library
// if there are overapping edges. // if there are overapping edges.
ClipperLib::Paths input_subject; if (auto output = clipper_do<ClipperLib::Paths>(clipType, subject, clip, fillType); ! output.empty())
clipper.Execute(clipType, input_subject, fillType, fillType); // Perform an additional Union operation to generate the PolyTree ordering.
// Perform an additional Union operation to generate the PolyTree ordering. return clipper_union<ClipperLib::PolyTree>(output, fillType);
clipper.Clear(); return ClipperLib::PolyTree();
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
ClipperLib::PolyTree retval;
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
return retval;
} }
template<typename PathProvider1, typename PathProvider2> template<typename PathProvider1, typename PathProvider2>
inline ClipperLib::PolyTree _clipper_do_polytree2( inline ClipperLib::PolyTree clipper_do_polytree(
const ClipperLib::ClipType clipType, const ClipperLib::ClipType clipType,
PathProvider1 &&subject, PathProvider1 &&subject,
PathProvider2 &&clip, PathProvider2 &&clip,
@ -488,14 +532,14 @@ inline ClipperLib::PolyTree _clipper_do_polytree2(
{ {
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
return do_safety_offset == ApplySafetyOffset::Yes ? return do_safety_offset == ApplySafetyOffset::Yes ?
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) : clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType); clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
} }
template<class TSubj, class TClip> template<class TSubj, class TClip>
static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset) static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset)
{ {
return to_polygons(_clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset)); return to_polygons(clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset));
} }
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
@ -506,6 +550,8 @@ Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } { 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)
@ -529,7 +575,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons
template <typename TSubject, typename TClip> template <typename TSubject, typename TClip>
static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero) static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero)
{ return PolyTreeToExPolygons(_clipper_do_polytree2(clipType, std::forward<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); } { return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); }
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
@ -578,9 +624,9 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type) Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type)
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); } { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject) Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject)
{ return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject)
{ return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
template<typename PathsProvider1, typename PathsProvider2> template<typename PathsProvider1, typename PathsProvider2>
Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip)
@ -692,14 +738,15 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol
return retval; return retval;
} }
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
ClipperLib::PolyTree union_pt(const Polygons &subject) ClipperLib::PolyTree union_pt(const Polygons &subject)
{ {
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
} }
ClipperLib::PolyTree union_pt(const ExPolygons &subject) ClipperLib::PolyTree union_pt(const ExPolygons &subject)
{ {
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
} }
// Simple spatial ordering of Polynodes // Simple spatial ordering of Polynodes
@ -730,7 +777,7 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou
}); });
} }
static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval) static void traverse_pt_outside_in(ClipperLib::PolyNodes &&nodes, Polygons *retval)
{ {
// collect ordering points // collect ordering points
Points ordering_points; Points ordering_points;
@ -740,22 +787,20 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons
// Perform the ordering, push results recursively. // Perform the ordering, push results recursively.
//FIXME pass the last point to chain_clipper_polynodes? //FIXME pass the last point to chain_clipper_polynodes?
for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) { for (ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
retval->emplace_back(node->Contour); retval->emplace_back(std::move(node->Contour));
if (node->IsHole()) if (node->IsHole())
// Orient a hole, which is clockwise oriented, to CCW. // Orient a hole, which is clockwise oriented, to CCW.
retval->back().reverse(); retval->back().reverse();
// traverse the next depth // traverse the next depth
traverse_pt_outside_in(node->Childs, retval); traverse_pt_outside_in(std::move(node->Childs), retval);
} }
} }
Polygons union_pt_chained_outside_in(const Polygons &subject) Polygons union_pt_chained_outside_in(const Polygons &subject)
{ {
ClipperLib::PolyTree polytree = union_pt(subject);
Polygons retval; Polygons retval;
traverse_pt_outside_in(polytree.Childs, &retval); traverse_pt_outside_in(union_pt(subject).Childs, &retval);
return retval; return retval;
} }

View file

@ -12,16 +12,26 @@ using Slic3r::ClipperLib::jtMiter;
using Slic3r::ClipperLib::jtRound; using Slic3r::ClipperLib::jtRound;
using Slic3r::ClipperLib::jtSquare; using Slic3r::ClipperLib::jtSquare;
static constexpr const float ClipperSafetyOffset = 10.f; namespace Slic3r {
static constexpr const float ClipperSafetyOffset = 10.f;
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
// However such a high limit causes issues with large positive or negative offsets, where a sharp corner
// is extended excessively.
static constexpr const double DefaultMiterLimit = 3.;
static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Slic3r::ClipperLib::jtSquare;
// Miter limit is ignored for jtSquare.
static constexpr const double DefaultLineMiterLimit = 0.;
enum class ApplySafetyOffset { enum class ApplySafetyOffset {
No, No,
Yes Yes
}; };
#define CLIPPERUTILS_UNSAFE_OFFSET
namespace Slic3r {
namespace ClipperUtils { namespace ClipperUtils {
class PathsProviderIteratorBase { class PathsProviderIteratorBase {
public: public:
@ -81,6 +91,33 @@ namespace ClipperUtils {
static Points s_end; static Points s_end;
}; };
template<typename PathType>
class PathsProvider {
public:
PathsProvider(const std::vector<PathType> &paths) : m_paths(paths) {}
struct iterator : public PathsProviderIteratorBase {
public:
explicit iterator(typename std::vector<PathType>::const_iterator it) : m_it(it) {}
const Points& operator*() const { return *m_it; }
bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; }
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
const Points& operator++(int) { return *(m_it ++); }
iterator& operator++() { ++ m_it; return *this; }
private:
typename std::vector<PathType>::const_iterator m_it;
};
iterator cbegin() const { return iterator(m_paths.begin()); }
iterator begin() const { return this->cbegin(); }
iterator cend() const { return iterator(m_paths.end()); }
iterator end() const { return this->cend(); }
size_t size() const { return m_paths.size(); }
private:
const std::vector<PathType> &m_paths;
};
template<typename MultiPointType> template<typename MultiPointType>
class MultiPointsProvider { class MultiPointsProvider {
public: public:
@ -261,36 +298,82 @@ namespace ClipperUtils {
}; };
} }
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); // Perform union of input polygons using the non-zero rule, convert to ExPolygons.
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false);
// offset Polygons // offset Polygons
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); // Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
// offset Polylines // offset Polylines
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); // Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) { return offset_ex(expolygons, ClipperSafetyOffset); }
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons);
Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons); Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons);
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons); Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons);
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); // Aliases for the various offset(...) functions, conveying the purpose of the offset.
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); }
// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons.
inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
#ifdef CLIPPERUTILS_UNSAFE_OFFSET // Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons); // Offset outside, then inside produces morphological closing. All deltas should be positive.
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
#endif // CLIPPERUTILS_UNSAFE_OFFSET inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return closing(polygons, delta, delta, joinType, miterLimit); }
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return closing_ex(polygons, delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); }
inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); }
// Offset inside, then outside produces morphological opening. All deltas should be positive.
// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(polygons, delta, delta, joinType, miterLimit); }
inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(expolygons, delta, delta, joinType, miterLimit); }
inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(surfaces, delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); }
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
@ -299,6 +382,7 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::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);
@ -366,6 +450,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject); ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject);
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);

View file

@ -252,11 +252,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
// Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2 // Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
Polygons collapsed = diff( Polygons collapsed = diff(
surfaces_polygons, surfaces_polygons,
offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2 + ClipperSafetyOffset)); opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset)));
//FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being //FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being
// added if two offsetted void regions merge. // added if two offsetted void regions merge.
// polygons_append(voids, collapsed); // polygons_append(voids, collapsed);
ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, ApplySafetyOffset::Yes); ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes);
// Now find an internal infill SurfaceFill to add these extrusions to. // Now find an internal infill SurfaceFill to add these extrusions to.
SurfaceFill *internal_solid_fill = nullptr; SurfaceFill *internal_solid_fill = nullptr;
unsigned int region_id = 0; unsigned int region_id = 0;

View file

@ -402,19 +402,19 @@ public:
hole.rotate(angle); hole.rotate(angle);
} }
double mitterLimit = 3.; double miterLimit = DefaultMiterLimit;
// for the infill pattern, don't cut the corners. // for the infill pattern, don't cut the corners.
// default miterLimt = 3 // default miterLimt = 3
//double mitterLimit = 10.; //double miterLimit = 10.;
assert(aoffset1 < 0); assert(aoffset1 < 0);
assert(aoffset2 <= 0); assert(aoffset2 <= 0);
assert(aoffset2 == 0 || aoffset2 < aoffset1); assert(aoffset2 == 0 || aoffset2 < aoffset1);
// bool sticks_removed = // bool sticks_removed =
remove_sticks(polygons_src); remove_sticks(polygons_src);
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!"; // if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, mitterLimit); polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
if (aoffset2 < 0) if (aoffset2 < 0)
polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), ClipperLib::jtMiter, mitterLimit); polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit);
// Filter out contours with zero area or small area, contours with 2 points only. // Filter out contours with zero area or small area, contours with 2 points only.
const double min_area_threshold = 0.01 * aoffset2 * aoffset2; const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
remove_small(polygons_outer, min_area_threshold); remove_small(polygons_outer, min_area_threshold);

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);
} }
@ -868,7 +1062,7 @@ static Polygons get_boundary_external(const Layer &layer)
} }
// Used offset_ex for cases when another object will be in the hole of another polygon // Used offset_ex for cases when another object will be in the hole of another polygon
boundary = to_polygons(offset_ex(boundary, perimeter_offset)); boundary = expand(boundary, perimeter_offset);
// Reverse all polygons for making normals point from the polygon out. // Reverse all polygons for making normals point from the polygon out.
for (Polygon &poly : boundary) for (Polygon &poly : boundary)
poly.reverse(); poly.reverse();
@ -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

@ -1267,7 +1267,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

@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print)
std::vector<float> deltas(input.points.size(), offset); std::vector<float> deltas(input.points.size(), offset);
input.make_counter_clockwise(); input.make_counter_clockwise();
out.front() = mittered_offset_path_scaled(input.points, deltas, 3.); out.front() = mittered_offset_path_scaled(input.points, deltas, 3.);
return ClipperPaths_to_Slic3rExPolygons(out); return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union
}; };

View file

@ -152,7 +152,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine
auto it_end = it; 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

@ -188,17 +188,23 @@ public:
// Extrusion paths for the support base and for the support interface and contacts. // Extrusion paths for the support base and for the support interface and contacts.
ExtrusionEntityCollection support_fills; ExtrusionEntityCollection support_fills;
// Is there any valid extrusion assigned to this LayerRegion? // Is there any valid extrusion assigned to this LayerRegion?
virtual bool has_extrusions() const { return ! support_fills.empty(); } virtual bool has_extrusions() const { return ! support_fills.empty(); }
// Zero based index of an interface layer, used for alternating direction of interface / contact layers.
size_t interface_id() const { return m_interface_id; }
protected: protected:
friend class PrintObject; friend class PrintObject;
// The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower // The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower
// between the raft and the object first layer. // between the raft and the object first layer.
SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : SupportLayer(size_t id, size_t interface_id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
Layer(id, object, height, print_z, slice_z) {} Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id) {}
virtual ~SupportLayer() = default; virtual ~SupportLayer() = default;
size_t m_interface_id;
}; };
template<typename LayerContainer> template<typename LayerContainer>

View file

@ -431,9 +431,8 @@ void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_comp
for (const Surface &surface : this->slices.surfaces) 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

@ -1400,7 +1400,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
// Clean up thin projections. They are not printable anyways. // Clean up thin projections. They are not printable anyways.
top_ex = offset2_ex(top_ex, - stat.small_region_threshold, + stat.small_region_threshold); top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) { if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f; float offset = 0.f;
@ -1408,8 +1408,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) { for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) {
offset -= stat.extrusion_width; offset -= stat.extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = offset2_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
- stat.small_region_threshold, + stat.small_region_threshold);
if (last.empty()) if (last.empty())
break; break;
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
@ -1419,7 +1418,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
// Clean up thin projections. They are not printable anyways. // Clean up thin projections. They are not printable anyways.
bottom_ex = offset2_ex(bottom_ex, - stat.small_region_threshold, + stat.small_region_threshold); bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) { if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f; float offset = 0.f;
@ -1427,8 +1426,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) { for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) {
offset -= stat.extrusion_width; offset -= stat.extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = offset2_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
- stat.small_region_threshold, + stat.small_region_threshold);
if (last.empty()) if (last.empty())
break; break;
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));

View file

@ -347,10 +347,10 @@ void PerimeterGenerator::process()
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes) // (actually, something larger than that still may exist due to mitering or other causes)
coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3));
ExPolygons expp = offset2_ex( ExPolygons expp = opening_ex(
// medial axis requires non-overlapping geometry // medial axis requires non-overlapping geometry
diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)),
- float(min_width / 2.), float(min_width / 2.)); float(min_width / 2.));
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygon &ex : expp) for (ExPolygon &ex : expp)
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
@ -495,7 +495,7 @@ void PerimeterGenerator::process()
double max = 2. * perimeter_spacing; double max = 2. * perimeter_spacing;
ExPolygons gaps_ex = diff_ex( ExPolygons gaps_ex = diff_ex(
//FIXME offset2 would be enough and cheaper. //FIXME offset2 would be enough and cheaper.
offset2_ex(gaps, - float(min / 2.), float(min / 2.)), opening_ex(gaps, float(min / 2.)),
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
ThickPolylines polylines; ThickPolylines polylines;
for (const ExPolygon &ex : gaps_ex) for (const ExPolygon &ex : gaps_ex)

View file

@ -1139,7 +1139,7 @@ void Print::_make_wipe_tower()
// Insert the new support layer. // Insert the new support layer.
double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z); double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z);
//FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height);
++ it_layer; ++ it_layer;
} }
} }

View file

@ -300,8 +300,8 @@ public:
size_t support_layer_count() const { return m_support_layers.size(); } size_t support_layer_count() const { return m_support_layers.size(); }
void clear_support_layers(); void clear_support_layers();
SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; }
SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); SupportLayer* add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z);
SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z);
void delete_support_layer(int idx); void delete_support_layer(int idx);
// Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters.

View file

@ -1853,7 +1853,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters " 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

@ -461,15 +461,15 @@ void PrintObject::clear_support_layers()
m_support_layers.clear(); m_support_layers.clear();
} }
SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z) SupportLayer* PrintObject::add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z)
{ {
m_support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1)); m_support_layers.emplace_back(new SupportLayer(id, interface_id, this, height, print_z, -1));
return m_support_layers.back(); return m_support_layers.back();
} }
SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z)
{ {
return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); return m_support_layers.insert(pos, new SupportLayer(id, interface_id, this, height, print_z, slice_z));
} }
// Called by Print::apply(). // Called by Print::apply().
@ -774,7 +774,7 @@ void PrintObject::detect_surfaces_type()
ExPolygons upper_slices = interface_shells ? ExPolygons upper_slices = interface_shells ?
diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop); surfaces_append(top, opening_ex(upper_slices, offset), stTop);
} else { } else {
// if no upper layer, all surfaces of this one are solid // if no upper layer, all surfaces of this one are solid
// we clone surfaces because we're going to clear the slices collection // we clone surfaces because we're going to clear the slices collection
@ -792,15 +792,15 @@ void PrintObject::detect_surfaces_type()
to_polygons(lower_layer->get_region(region_id)->slices.surfaces) : to_polygons(lower_layer->get_region(region_id)->slices.surfaces) :
to_polygons(lower_layer->slices); to_polygons(lower_layer->slices);
surfaces_append(bottom, surfaces_append(bottom,
offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset), opening_ex(diff(layerm->slices.surfaces, lower_slices, true), offset),
surface_type_bottom_other); surface_type_bottom_other);
#else #else
// Any surface lying on the void is a true bottom bridge (an overhang) // Any surface lying on the void is a true bottom bridge (an overhang)
surfaces_append( surfaces_append(
bottom, bottom,
offset2_ex( opening_ex(
diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
-offset, offset), offset),
surface_type_bottom_other); surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces // if user requested internal shells, we need to identify surfaces
// lying on other slices not belonging to this region // lying on other slices not belonging to this region
@ -809,12 +809,12 @@ void PrintObject::detect_surfaces_type()
// on something else, excluding those lying on our own region // on something else, excluding those lying on our own region
surfaces_append( surfaces_append(
bottom, bottom,
offset2_ex( opening_ex(
diff_ex( diff_ex(
intersection(layerm->slices.surfaces, lower_layer->lslices), // supported intersection(layerm->slices.surfaces, lower_layer->lslices), // supported
lower_layer->m_regions[region_id]->slices.surfaces, lower_layer->m_regions[region_id]->slices.surfaces,
ApplySafetyOffset::Yes), ApplySafetyOffset::Yes),
-offset, offset), offset),
stBottom); stBottom);
} }
#endif #endif
@ -1088,7 +1088,7 @@ void PrintObject::discover_vertical_shells()
// For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. // 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
@ -1337,7 +1337,7 @@ void PrintObject::discover_vertical_shells()
// get a triangle in $too_narrow; if we grow it below then the shell // get a triangle in $too_narrow; if we grow it below then the shell
// would have a different shape from the external surface and we'd still // would have a different shape from the external surface and we'd still
// have the same angle, so the next shell would be grown even more and so on. // have the same angle, so the next shell would be grown even more and so on.
Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true); Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true);
if (! too_narrow.empty()) { if (! too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer // grow the collapsing parts and add the extra area to the neighbor layer
// as well as to our original surfaces so that we support this // as well as to our original surfaces so that we support this
@ -1453,7 +1453,7 @@ void PrintObject::bridge_over_infill()
// The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer.
{ {
float min_width = float(bridge_flow.scaled_width()) * 3.f; float min_width = float(bridge_flow.scaled_width()) * 3.f;
to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); to_bridge_pp = opening(to_bridge_pp, min_width);
} }
if (to_bridge_pp.empty()) continue; if (to_bridge_pp.empty()) continue;
@ -1744,7 +1744,7 @@ void PrintObject::clip_fill_surfaces()
for (const LayerRegion *layerm : layer->m_regions) for (const LayerRegion *layerm : layer->m_regions)
pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width());
// Append such thick perimeters to the areas that need support // Append such thick perimeters to the areas that need support
polygons_append(overhangs, offset2(perimeters, -pw, +pw)); polygons_append(overhangs, opening(perimeters, pw));
} }
// Find new internal infill. // Find new internal infill.
polygons_append(overhangs, std::move(upper_internal)); polygons_append(overhangs, std::move(upper_internal));
@ -1884,7 +1884,7 @@ void PrintObject::discover_horizontal_shells()
float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
Polygons too_narrow = diff( Polygons too_narrow = diff(
new_internal_solid, new_internal_solid,
offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, jtMiter, 5)); opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5));
// Trim the regularized region by the original region. // Trim the regularized region by the original region.
if (! too_narrow.empty()) if (! too_narrow.empty())
new_internal_solid = solid = diff(new_internal_solid, too_narrow); new_internal_solid = solid = diff(new_internal_solid, too_narrow);
@ -1903,7 +1903,7 @@ void PrintObject::discover_horizontal_shells()
// have the same angle, so the next shell would be grown even more and so on. // have the same angle, so the next shell would be grown even more and so on.
Polygons too_narrow = diff( Polygons too_narrow = diff(
new_internal_solid, new_internal_solid,
offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5));
if (! too_narrow.empty()) { if (! too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer // grow the collapsing parts and add the extra area to the neighbor layer
// as well as to our original surfaces so that we support this // as well as to our original surfaces so that we support this
@ -1915,7 +1915,7 @@ void PrintObject::discover_horizontal_shells()
polygons_append(internal, to_polygons(surface.expolygon)); polygons_append(internal, to_polygons(surface.expolygon));
polygons_append(new_internal_solid, polygons_append(new_internal_solid,
intersection( intersection(
offset(too_narrow, +margin), expand(too_narrow, +margin),
// Discard bridges as they are grown for anchoring and we can't // Discard bridges as they are grown for anchoring and we can't
// remove such anchors. (This may happen when a bridge is being // remove such anchors. (This may happen when a bridge is being
// anchored onto a wall where little space remains after the bridge // anchored onto a wall where little space remains after the bridge

View file

@ -393,7 +393,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
} }
} }
if (merged) if (merged)
expolygons = offset2_ex(expolygons, float(scale_(EPSILON)), -float(scale_(EPSILON))); expolygons = closing_ex(expolygons, float(scale_(EPSILON)));
slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
i = j; i = j;
} }
@ -648,7 +648,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
ByRegion &src = by_region[region_id]; ByRegion &src = by_region[region_id];
if (src.needs_merge) if (src.needs_merge)
// Multiple regions were merged into one. // Multiple regions were merged into one.
src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON))); src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
} }
} }

View file

@ -186,8 +186,8 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
// Produce 2 bands around the island, a safe band for dangling overhangs // Produce 2 bands around the island, a safe band for dangling overhangs
// and an unsafe band for sloped overhangs. // and an unsafe band for sloped overhangs.
// These masks include the original island // These masks include the original island
auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare);
// Absolutely hopeless overhangs are those outside the unsafe band // Absolutely hopeless overhangs are those outside the unsafe band
top.overhangs = diff_ex(*top.polygon, overh_mask); top.overhangs = diff_ex(*top.polygon, overh_mask);

View file

@ -367,6 +367,29 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
// Object is printed with the same extruder as the support. // Object is printed with the same extruder as the support.
m_support_params.can_merge_support_regions = true; m_support_params.can_merge_support_regions = true;
} }
m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value));
m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.));
m_support_params.interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing();
m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing);
m_support_params.support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing();
m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing);
if (m_object_config->support_material_interface_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
m_support_params.interface_spacing = m_support_params.support_spacing;
m_support_params.interface_density = m_support_params.support_density;
}
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
m_support_params.with_sheath = m_object_config->support_material_with_sheath;
m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (m_support_params.support_density > 0.95 ? ipRectilinear : ipSupportBase);
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
m_support_params.contact_fill_pattern =
(m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
m_object_config->support_material_interface_pattern == smipConcentric ?
ipConcentric :
(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
} }
// Using the std::deque as an allocator. // Using the std::deque as an allocator.
@ -397,6 +420,11 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr
dst.insert(dst.end(), src.begin(), src.end()); dst.insert(dst.end(), src.begin(), src.end());
} }
// Support layer that is covered by some form of dense interface.
static constexpr const std::initializer_list<PrintObjectSupportMaterial::SupporLayerType> support_types_interface {
PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface
};
void PrintObjectSupportMaterial::generate(PrintObject &object) void PrintObjectSupportMaterial::generate(PrintObject &object)
{ {
BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
@ -546,6 +574,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
// Sort the layers lexicographically by a raising print_z and a decreasing height. // Sort the layers lexicographically by a raising print_z and a decreasing height.
std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
int layer_id = 0; int layer_id = 0;
int layer_id_interface = 0;
assert(object.support_layers().empty()); assert(object.support_layers().empty());
for (size_t i = 0; i < layers_sorted.size();) { for (size_t i = 0; i < layers_sorted.size();) {
// Find the last layer with roughly the same print_z, find the minimum layer height of all. // Find the last layer with roughly the same print_z, find the minimum layer height of all.
@ -557,17 +586,43 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z);
coordf_t height_min = layers_sorted[i]->height; coordf_t height_min = layers_sorted[i]->height;
bool empty = true; bool empty = true;
// For snug supports, layers where the direction of the support interface shall change are accounted for.
size_t num_interfaces = 0;
size_t num_top_contacts = 0;
double top_contact_bottom_z = 0;
for (size_t u = i; u < j; ++u) { for (size_t u = i; u < j; ++u) {
MyLayer &layer = *layers_sorted[u]; MyLayer &layer = *layers_sorted[u];
if (! layer.polygons.empty()) if (! layer.polygons.empty()) {
empty = false; empty = false;
num_interfaces += one_of(layer.layer_type, support_types_interface);
if (layer.layer_type == sltTopContact) {
++ num_top_contacts;
assert(num_top_contacts <= 1);
// All top contact layers sharing this print_z shall also share bottom_z.
//assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON);
top_contact_bottom_z = layer.bottom_z;
}
}
layer.print_z = zavg; layer.print_z = zavg;
height_min = std::min(height_min, layer.height); height_min = std::min(height_min, layer.height);
} }
if (! empty) { if (! empty) {
// Here the upper_layer and lower_layer pointers are left to null at the support layers, // Here the upper_layer and lower_layer pointers are left to null at the support layers,
// as they are never used. These pointers are candidates for removal. // as they are never used. These pointers are candidates for removal.
object.add_support_layer(layer_id ++, height_min, zavg); bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces;
size_t this_layer_id_interface = layer_id_interface;
if (this_layer_contacts_only) {
// Find a supporting layer for its interface ID.
for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it)
if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) {
// other_layer supports this top contact layer. Assign a different support interface direction to this layer
// from the layer that supports it.
this_layer_id_interface = other_layer.interface_id() + 1;
}
}
object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg);
if (num_interfaces && ! this_layer_contacts_only)
++ layer_id_interface;
} }
i = j; i = j;
} }
@ -883,7 +938,14 @@ public:
// Merge the support polygons by applying morphological closing and inwards smoothing. // Merge the support polygons by applying morphological closing and inwards smoothing.
auto closing_distance = scaled<float>(m_support_material_closing_radius); auto closing_distance = scaled<float>(m_support_material_closing_radius);
auto smoothing_distance = scaled<float>(m_extrusion_width); auto smoothing_distance = scaled<float>(m_extrusion_width);
return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance); #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z),
{ { { diff_ex(expand(*m_support_polygons, closing_distance), closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "closed", "blue", 0.5f } },
{ { union_ex(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance)) }, { "regularized", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } },
{ { union_ex(*m_support_polygons) }, { "src", "green", 0.5f } },
});
#endif /* SLIC3R_DEBUG */
return smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance);
} }
assert(false); assert(false);
return Polygons(); return Polygons();
@ -1250,7 +1312,7 @@ namespace SupportMaterialInternal {
Polygons bridges; Polygons bridges;
{ {
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
Polygons lower_grown_slices = offset(lower_layer_polygons, Polygons lower_grown_slices = expand(lower_layer_polygons,
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))),
SUPPORT_SURFACES_OFFSET_PARAMETERS); SUPPORT_SURFACES_OFFSET_PARAMETERS);
@ -1414,7 +1476,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
overhang_polygons = to_polygons(layer.lslices); overhang_polygons = to_polygons(layer.lslices);
#endif #endif
// Expand for better stability. // Expand for better stability.
contact_polygons = offset(overhang_polygons, scaled<float>(object_config.raft_expansion.value)); contact_polygons = expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
} }
else if (! layer.regions().empty()) else if (! layer.regions().empty())
{ {
@ -1475,20 +1537,20 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
//FIXME cache the lower layer offset if this layer has multiple regions. //FIXME cache the lower layer offset if this layer has multiple regions.
#if 0 #if 0
//FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874
diff_polygons = offset2( diff_polygons = opening(
diff(layerm_polygons, diff(layerm_polygons,
// Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they
// are not supporting this layer. // are not supporting this layer.
// However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports. // However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports.
// For example, see GH issue #3094 // For example, see GH issue #3094
offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), opening(lower_layer_polygons, 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
//FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to //FIXME This opening is targeted to reduce very thin regions to support, but it may lead to
// no support at all for not so steep overhangs. // no support at all for not so steep overhangs.
- 0.1f * fw, 0.1f * fw); 0.1f * fw);
#else #else
diff_polygons = diff_polygons =
diff(layerm_polygons, diff(layerm_polygons,
offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
#endif #endif
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
// Don't support overhangs above the top surfaces. // Don't support overhangs above the top surfaces.
@ -1500,7 +1562,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// This is done to increase size of the supporting columns below, as they are calculated by // This is done to increase size of the supporting columns below, as they are calculated by
// propagating these contact surfaces downwards. // propagating these contact surfaces downwards.
diff_polygons = diff( diff_polygons = diff(
intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons),
lower_layer_polygons); lower_layer_polygons);
} }
//FIXME add user defined filtering here based on minimal area or minimum radius or whatever. //FIXME add user defined filtering here based on minimal area or minimum radius or whatever.
@ -1516,7 +1578,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// Subtracting them as they are may leave unwanted narrow // Subtracting them as they are may leave unwanted narrow
// residues of diff_polygons that would then be supported. // residues of diff_polygons that would then be supported.
diff_polygons = diff(diff_polygons, diff_polygons = diff(diff_polygons,
offset(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); expand(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON)));
} }
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
@ -1588,7 +1650,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]),
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object. // Inflate just a tiny bit to avoid intersection of the overhang areas with the object.
offset(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
{ { layer.lslices, { "layer.lslices", "gray", 0.2f } }, { { layer.lslices, { "layer.lslices", "gray", 0.2f } },
@ -1641,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.
@ -1665,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.
@ -1720,7 +1785,7 @@ static inline void fill_contact_layer(
if (lower_layer_polygons_for_dense_interface_cache.empty()) if (lower_layer_polygons_for_dense_interface_cache.empty())
lower_layer_polygons_for_dense_interface_cache = lower_layer_polygons_for_dense_interface_cache =
//FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height. //FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height.
offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); opening(lower_layer_polygons, no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS);
return lower_layer_polygons_for_dense_interface_cache; return lower_layer_polygons_for_dense_interface_cache;
}; };
@ -1733,7 +1798,7 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
)); ));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
bool reduce_interfaces = layer_id > 0 && ! slicing_params.soluble_interface; bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface;
if (reduce_interfaces) { if (reduce_interfaces) {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface());
@ -1741,7 +1806,7 @@ static inline void fill_contact_layer(
dense_interface_polygons = dense_interface_polygons =
diff( diff(
// Regularize the contour. // Regularize the contour.
offset(dense_interface_polygons, no_interface_offset * 0.1f), expand(dense_interface_polygons, no_interface_offset * 0.1f),
slices_margin.polygons); slices_margin.polygons);
// Support islands, to be stretched into a grid. // Support islands, to be stretched into a grid.
//FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
@ -1799,7 +1864,7 @@ static inline void fill_contact_layer(
dense_interface_polygons = dense_interface_polygons =
diff( diff(
// Regularize the contour. // Regularize the contour.
offset(dense_interface_polygons, no_interface_offset * 0.1f), expand(dense_interface_polygons, no_interface_offset * 0.1f),
slices_margin.all_polygons); slices_margin.all_polygons);
// Support islands, to be stretched into a grid. // Support islands, to be stretched into a grid.
//FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
@ -1937,7 +2002,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
); );
// Now apply the contact areas to the layer where they need to be made. // Now apply the contact areas to the layer where they need to be made.
if (! contact_polygons.empty()) { if (! contact_polygons.empty() || ! overhang_polygons.empty()) {
// Allocate the two empty layers. // Allocate the two empty layers.
auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex);
if (new_layer) { if (new_layer) {
@ -2041,8 +2106,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
layer_new.idx_object_layer_below = layer_id; layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges;
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
//FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! slicing_params.soluble_interface) { if (! slicing_params.soluble_interface) {
// Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
@ -2081,7 +2145,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
// Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer.
//FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage?
touching = offset(touching, float(SCALED_EPSILON)); touching = expand(touching, float(SCALED_EPSILON));
for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
const Layer &layer_above = *object.layers()[layer_id_above]; const Layer &layer_above = *object.layers()[layer_id_above];
if (layer_above.print_z > layer_new.print_z - EPSILON) if (layer_above.print_z > layer_new.print_z - EPSILON)
@ -2249,7 +2313,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
#endif #endif
// These are the overhang surfaces. They are touching the object and they are not expanded away from the object. // These are the overhang surfaces. They are touching the object and they are not expanded away from the object.
// Use a slight positive offset to overlap the touching regions. // Use a slight positive offset to overlap the touching regions.
polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON))); polygons_append(polygons_new, expand(*top_contact.overhang_polygons, float(SCALED_EPSILON)));
polygons_append(overhangs_projection, union_(polygons_new)); polygons_append(overhangs_projection, union_(polygons_new));
polygons_append(enforcers_projection, enforcers_new); polygons_append(enforcers_projection, enforcers_new);
} }
@ -2478,14 +2542,16 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
// or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer.
// Intermediate layers are always printed with a normal etrusion flow (non-bridging). // Intermediate layers are always printed with a normal etrusion flow (non-bridging).
size_t idx_layer_object = 0; size_t idx_layer_object = 0;
for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) { size_t idx_extreme_first = 0;
if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) {
// This is a raft contact layer, its height has been decided in this->top_contact_layers().
// Ignore this layer when calculating the intermediate support layers.
assert(extremes.front()->layer_type == sltTopContact);
++ idx_extreme_first;
}
for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) {
MyLayer *extr2 = extremes[idx_extreme]; MyLayer *extr2 = extremes[idx_extreme];
coordf_t extr2z = extr2->extreme_z(); coordf_t extr2z = extr2->extreme_z();
if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) {
// This is a raft contact layer, its height has been decided in this->top_contact_layers().
assert(extr2->layer_type == sltTopContact);
continue;
}
if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) {
// This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers().
assert(extr2->layer_type == sltTopContact); assert(extr2->layer_type == sltTopContact);
@ -2502,7 +2568,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
} }
assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON);
assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON);
MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1]; MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1];
// Fuse a support layer firmly to the raft top interface (not to the raft contacts). // Fuse a support layer firmly to the raft top interface (not to the raft contacts).
coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z();
assert(extr2z >= extr1z); assert(extr2z >= extr1z);
@ -2736,7 +2802,6 @@ void PrintObjectSupportMaterial::generate_base_layers(
ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons
layer_intermediate.layer_type = sltBase; layer_intermediate.layer_type = sltBase;
// For snug supports, expand the interfaces into the intermediate layer to make it printable.
#if 0 #if 0
// coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing);
// Fillet the base polygons and trim them again with the top, interface and contact layers. // Fillet the base polygons and trim them again with the top, interface and contact layers.
@ -2868,7 +2933,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
if (brim_inner) { if (brim_inner) {
Polygons holes = ex.holes; Polygons holes = ex.holes;
polygons_reverse(holes); polygons_reverse(holes);
holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1))); holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1)));
polygons_reverse(holes); polygons_reverse(holes);
polygons_append(brim, std::move(holes)); polygons_append(brim, std::move(holes));
} else } else
@ -2900,11 +2965,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
Polygons interface_polygons; Polygons interface_polygons;
if (contacts != nullptr && ! contacts->polygons.empty()) if (contacts != nullptr && ! contacts->polygons.empty())
polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (interfaces != nullptr && ! interfaces->polygons.empty()) if (interfaces != nullptr && ! interfaces->polygons.empty())
polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) if (base_interfaces != nullptr && ! base_interfaces->polygons.empty())
polygons_append(interface_polygons, offset(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
// Output vector. // Output vector.
MyLayersPtr raft_layers; MyLayersPtr raft_layers;
@ -2931,7 +2996,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.print_z = m_slicing_params.first_print_layer_height;
new_layer.height = m_slicing_params.first_print_layer_height; new_layer.height = m_slicing_params.first_print_layer_height;
new_layer.bottom_z = 0.; new_layer.bottom_z = 0.;
new_layer.polygons = inflate_factor_1st_layer > 0 ? offset(base, inflate_factor_1st_layer) : base; new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base;
} }
// Insert the base layers. // Insert the base layers.
for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) {
@ -2965,7 +3030,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width())));
float step = inflate_factor_1st_layer / nsteps; float step = inflate_factor_1st_layer / nsteps;
for (int i = 0; i < nsteps; ++ i) for (int i = 0; i < nsteps; ++ i)
raft = diff(offset(raft, step), trimming); raft = diff(expand(raft, step), trimming);
} else } else
raft = diff(raft, trimming); raft = diff(raft, trimming);
if (contacts != nullptr) if (contacts != nullptr)
@ -3028,26 +3093,43 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
interface_layers.assign(intermediate_layers.size(), nullptr); interface_layers.assign(intermediate_layers.size(), nullptr);
if (num_base_interface_layers_top || num_base_interface_layers_bottom) if (num_base_interface_layers_top || num_base_interface_layers_bottom)
base_interface_layers.assign(intermediate_layers.size(), nullptr); base_interface_layers.assign(intermediate_layers.size(), nullptr);
auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5;
auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density;
auto closing_distance = smoothing_distance; // scaled<float>(m_object_config->support_material_closing_radius.value);
tbb::spin_mutex layer_storage_mutex; tbb::spin_mutex layer_storage_mutex;
// Insert a new layer into base_interface_layers, if intersection with base exists. // Insert a new layer into base_interface_layers, if intersection with base exists.
auto insert_layer = [&layer_storage, &layer_storage_mutex](MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) { auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius](
MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* {
assert(! bottom.empty() || ! top.empty()); assert(! bottom.empty() || ! top.empty());
MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type);
layer_new.print_z = intermediate_layer.print_z;
layer_new.bottom_z = intermediate_layer.bottom_z;
layer_new.height = intermediate_layer.height;
layer_new.bridging = intermediate_layer.bridging;
// Merge top into bottom, unite them with a safety offset. // Merge top into bottom, unite them with a safety offset.
append(bottom, std::move(top)); append(bottom, std::move(top));
layer_new.polygons = intersection(union_safety_offset(std::move(bottom)), intermediate_layer.polygons); // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners).
// Subtract the interface from the base regions. bottom = intersection(
intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); snug_supports ?
if (subtract) smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
// Trim the base interface layer with the interface layer. union_safety_offset(std::move(bottom)),
layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); intermediate_layer.polygons);
//FIXME filter layer_new.polygons islands by a minimum area? if (! bottom.empty()) {
// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; //FIXME Remove non-printable tiny islands, let them be printed using the base support.
return &layer_new; //bottom = opening(std::move(bottom), minimum_island_radius);
if (! bottom.empty()) {
MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type);
layer_new.polygons = std::move(bottom);
layer_new.print_z = intermediate_layer.print_z;
layer_new.bottom_z = intermediate_layer.bottom_z;
layer_new.height = intermediate_layer.height;
layer_new.bridging = intermediate_layer.bridging;
// Subtract the interface from the base regions.
intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons);
if (subtract)
// Trim the base interface layer with the interface layer.
layer_new.polygons = diff(std::move(layer_new.polygons), *subtract);
//FIXME filter layer_new.polygons islands by a minimum area?
// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
return &layer_new;
}
}
return nullptr;
}; };
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())), tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
[&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer,
@ -3196,7 +3278,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
return; return;
if (! with_sheath) { if (! with_sheath) {
fill_expolygons_generate_paths(dst, offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)), filler, density, role, flow); fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow);
return; return;
} }
@ -3208,7 +3290,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
// Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop.
double clip_length = spacing * 0.15; double clip_length = spacing * 0.15;
for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) { for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) {
// Don't reorder the skirt and its infills. // Don't reorder the skirt and its infills.
std::unique_ptr<ExtrusionEntityCollection> eec; std::unique_ptr<ExtrusionEntityCollection> eec;
if (no_sort) { if (no_sort) {
@ -3461,10 +3543,10 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
// make more loops // make more loops
Polygons loop_polygons = loops0; Polygons loop_polygons = loops0;
for (int i = 1; i < n_contact_loops; ++ i) for (int i = 1; i < n_contact_loops; ++ i)
polygons_append(loop_polygons, polygons_append(loop_polygons,
offset2( opening(
loops0, loops0,
- i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(),
0.5f * flow.scaled_spacing())); 0.5f * flow.scaled_spacing()));
// Clip such loops to the side oriented towards the object. // Clip such loops to the side oriented towards the object.
// Collect split points, so they will be recognized after the clipping. // Collect split points, so they will be recognized after the clipping.
@ -3476,7 +3558,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
map_split_points[it->first_point()] = -1; map_split_points[it->first_point()] = -1;
loop_lines.push_back(it->split_at_first_point()); loop_lines.push_back(it->split_at_first_point());
} }
loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)));
// Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces.
// Try to connect them. // Try to connect them.
for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) {
@ -3816,27 +3898,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width());
loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0;
float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); std::vector<float> angles { m_support_params.base_angle };
float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); if (m_object_config->support_material_pattern == smpRectilinearGrid)
coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); angles.push_back(m_support_params.interface_angle);
coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing);
coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing();
coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing);
if (m_object_config->support_material_interface_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
interface_spacing = support_spacing;
interface_density = support_density;
}
// Prepare fillers.
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
bool with_sheath = m_object_config->support_material_with_sheath;
InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density > 0.95 ? ipRectilinear : ipSupportBase);
std::vector<float> angles;
angles.push_back(base_angle);
if (support_pattern == smpRectilinearGrid)
angles.push_back(interface_angle);
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
@ -3848,16 +3912,16 @@ void PrintObjectSupportMaterial::generate_toolpaths(
float raft_angle_interface = 0.f; float raft_angle_interface = 0.f;
if (m_slicing_params.base_raft_layers > 1) { if (m_slicing_params.base_raft_layers > 1) {
// There are all raft layer types (1st layer, base, interface & contact layers) available. // There are all raft layer types (1st layer, base, interface & contact layers) available.
raft_angle_1st_layer = interface_angle; raft_angle_1st_layer = m_support_params.interface_angle;
raft_angle_base = base_angle; raft_angle_base = m_support_params.base_angle;
raft_angle_interface = interface_angle; raft_angle_interface = m_support_params.interface_angle;
} else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) {
// 1st layer, interface & contact layers available. // 1st layer, interface & contact layers available.
raft_angle_1st_layer = base_angle; raft_angle_1st_layer = m_support_params.base_angle;
if (this->has_support()) if (this->has_support())
// Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer.
raft_angle_1st_layer += 0.7854f; raft_angle_1st_layer += 0.7854f;
raft_angle_interface = interface_angle; raft_angle_interface = m_support_params.interface_angle;
} else if (m_slicing_params.interface_raft_layers == 1) { } else if (m_slicing_params.interface_raft_layers == 1) {
// Only the contact raft layer is non-empty, which will be printed as the 1st layer. // Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(m_slicing_params.base_raft_layers == 0); assert(m_slicing_params.base_raft_layers == 0);
@ -3874,7 +3938,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1));
tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers), tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
[this, &support_layers, &raft_layers, [this, &support_layers, &raft_layers,
infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath] &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
{ {
@ -3883,8 +3947,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
assert(support_layer.support_fills.entities.empty()); assert(support_layer.support_fills.entities.empty());
MyLayer &raft_layer = *raft_layers[support_layer_id]; MyLayer &raft_layer = *raft_layers[support_layer_id];
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(ipRectilinear)); std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.interface_fill_pattern));
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern)); std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object); filler_interface->set_bounding_box(bbox_object);
filler_support->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object);
@ -3900,17 +3964,17 @@ void PrintObjectSupportMaterial::generate_toolpaths(
Fill * filler = filler_support.get(); Fill * filler = filler_support.get();
filler->angle = raft_angle_base; filler->angle = raft_angle_base;
filler->spacing = m_support_params.support_material_flow.spacing(); filler->spacing = m_support_params.support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density));
fill_expolygons_with_sheath_generate_paths( fill_expolygons_with_sheath_generate_paths(
// Destination // Destination
support_layer.support_fills.entities, support_layer.support_fills.entities,
// Regions to fill // Regions to fill
to_infill_polygons, to_infill_polygons,
// Filler and its parameters // Filler and its parameters
filler, float(support_density), filler, float(m_support_params.support_density),
// Extrusion parameters // Extrusion parameters
erSupportMaterial, flow, erSupportMaterial, flow,
with_sheath, false); m_support_params.with_sheath, false);
} }
} }
@ -3929,7 +3993,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler->spacing = m_support_params.support_material_flow.spacing(); filler->spacing = m_support_params.support_material_flow.spacing();
assert(! raft_layer.bridging); assert(! raft_layer.bridging);
flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter());
density = float(interface_density); density = float(m_support_params.interface_density);
} else } else
continue; continue;
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
@ -3970,15 +4034,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
}; };
std::vector<LayerCache> layer_caches(support_layers.size()); std::vector<LayerCache> layer_caches(support_layers.size());
const auto fill_type_interface =
(m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
m_object_config->support_material_interface_pattern == smipConcentric ?
ipConcentric : ipRectilinear;
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()), tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()),
[this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor,
infill_pattern, &bbox_object, support_density, fill_type_interface, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath] &bbox_object, &angles, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
// Indices of the 1st layer in their respective container at the support layer height. // Indices of the 1st layer in their respective container at the support layer height.
size_t idx_layer_bottom_contact = size_t(-1); size_t idx_layer_bottom_contact = size_t(-1);
@ -3987,14 +4045,14 @@ void PrintObjectSupportMaterial::generate_toolpaths(
size_t idx_layer_interface = size_t(-1); size_t idx_layer_interface = size_t(-1);
size_t idx_layer_base_interface = size_t(-1); size_t idx_layer_base_interface = size_t(-1);
const auto fill_type_first_layer = ipRectilinear; const auto fill_type_first_layer = ipRectilinear;
auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(fill_type_interface)); auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
// Filler for the 1st layer interface, if different from filler_interface. // Filler for the 1st layer interface, if different from filler_interface.
auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && fill_type_interface != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr);
// Pointer to the 1st layer interface filler. // Pointer to the 1st layer interface filler.
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear)); auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase));
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern)); auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object); filler_interface->set_bounding_box(bbox_object);
if (filler_first_layer_ptr) if (filler_first_layer_ptr)
filler_first_layer_ptr->set_bounding_box(bbox_object); filler_first_layer_ptr->set_bounding_box(bbox_object);
@ -4005,6 +4063,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
{ {
SupportLayer &support_layer = *support_layers[support_layer_id]; SupportLayer &support_layer = *support_layers[support_layer_id];
LayerCache &layer_cache = layer_caches[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id];
float interface_angle_delta = m_object_config->support_material_style.value == smsSnug ?
(support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) :
0;
// Find polygons with the same print_z. // Find polygons with the same print_z.
MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
@ -4084,8 +4145,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// If zero interface layers are configured, use the same angle as for the base layers. // If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] : angles[support_layer_id % angles.size()] :
// Use interface angle for the interface layers. // Use interface angle for the interface layers.
interface_angle; m_support_params.interface_angle + interface_angle_delta;
double density = interface_as_base ? support_density : interface_density; double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density;
filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing();
filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density));
fill_expolygons_generate_paths( fill_expolygons_generate_paths(
@ -4106,9 +4167,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
assert(! base_interface_layer.layer->bridging); assert(! base_interface_layer.layer->bridging);
Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = interface_angle; filler->angle = m_support_params.interface_angle + interface_angle_delta;
filler->spacing = m_support_params.support_material_interface_flow.spacing(); filler->spacing = m_support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density));
fill_expolygons_generate_paths( fill_expolygons_generate_paths(
// Destination // Destination
base_interface_layer.extrusions, base_interface_layer.extrusions,
@ -4116,7 +4177,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Regions to fill // Regions to fill
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
// Filler and its parameters // Filler and its parameters
filler, float(interface_density), filler, float(m_support_params.interface_density),
// Extrusion parameters // Extrusion parameters
erSupportMaterial, interface_flow); erSupportMaterial, interface_flow);
} }
@ -4130,9 +4191,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
assert(! base_layer.layer->bridging); assert(! base_layer.layer->bridging);
auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height));
filler->spacing = m_support_params.support_material_flow.spacing(); filler->spacing = m_support_params.support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density));
float density = float(support_density); float density = float(m_support_params.support_density);
bool sheath = with_sheath; bool sheath = m_support_params.with_sheath;
bool no_sort = false; bool no_sort = false;
if (base_layer.layer->bottom_z < EPSILON) { if (base_layer.layer->bottom_z < EPSILON) {
// Base flange (the 1st layer). // Base flange (the 1st layer).

View file

@ -132,6 +132,18 @@ public:
// coordf_t support_layer_height_max; // coordf_t support_layer_height_max;
coordf_t gap_xy; coordf_t gap_xy;
float base_angle;
float interface_angle;
coordf_t interface_spacing;
coordf_t interface_density;
coordf_t support_spacing;
coordf_t support_density;
InfillPattern base_fill_pattern;
InfillPattern interface_fill_pattern;
InfillPattern contact_fill_pattern;
bool with_sheath;
}; };
// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained

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

@ -1617,7 +1617,7 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c
/* The following line is commented out because it can generate wrong polygons, /* The following line is commented out because it can generate wrong polygons,
see for example issue #661 */ see for example issue #661 */
//ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); //ExPolygons ex_slices = closing(p_slices, safety_offset);
#ifdef SLIC3R_TRIANGLEMESH_DEBUG #ifdef SLIC3R_TRIANGLEMESH_DEBUG
size_t holes_count = 0; size_t holes_count = 0;

View file

@ -127,7 +127,8 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, 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

@ -334,19 +334,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];
@ -661,6 +661,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,39 +61,28 @@ 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.
// 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 ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
if (platform_flavor() == PlatformFlavor::OSXOnArm) // When setting this technology to default rename the following from "gouraud_mod" to "gouraud"
valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" }, { "FLIP_TRIANGLE_NORMALS"sv valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" }
#if ENABLE_ENVIRONMENT_MAP
, "ENABLE_ENVIRONMENT_MAP"sv
#endif
});
else
valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" }
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
#endif
#else #else
if (platform_flavor() == PlatformFlavor::OSXOnArm) valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv
#if ENABLE_ENVIRONMENT_MAP
, "ENABLE_ENVIRONMENT_MAP"sv
#endif
});
else
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
#endif
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
#endif // ENABLE_ENVIRONMENT_MAP
); );
// 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);
@ -2020,14 +2022,14 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
menu->Append(local_menu, _L("&Configuration")); menu->Append(local_menu, _L("&Configuration"));
} }
void GUI_App::open_preferences(size_t open_on_tab) void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
{ {
bool app_layout_changed = false; bool app_layout_changed = false;
{ {
// the dialog needs to be destroyed before the call to recreate_GUI() // the dialog needs to be destroyed before the call to recreate_GUI()
// or sometimes the application crashes into wxDialogBase() destructor // or sometimes the application crashes into wxDialogBase() destructor
// so we put it into an inner scope // so we put it into an inner scope
PreferencesDialog dlg(mainframe, open_on_tab); PreferencesDialog dlg(mainframe, open_on_tab, highlight_option);
dlg.ShowModal(); dlg.ShowModal();
app_layout_changed = dlg.settings_layout_changed(); app_layout_changed = dlg.settings_layout_changed();
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER

View file

@ -259,7 +259,7 @@ public:
wxString current_language_code_safe() const; wxString current_language_code_safe() const;
bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } bool is_localized() const { return m_wxLocale->GetLocale() != "English"; }
void open_preferences(size_t open_on_tab = 0); void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
virtual bool OnExceptionInMainLoop() override; virtual bool OnExceptionInMainLoop() override;
// Calls wxLaunchDefaultBrowser if user confirms in dialog. // Calls wxLaunchDefaultBrowser if user confirms in dialog.

View file

@ -77,8 +77,10 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/)
m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()), m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()),
wxLC_ICON | wxSIMPLE_BORDER); wxLC_ICON | wxSIMPLE_BORDER);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this);
m_list_ctrl->Bind(wxEVT_LIST_KEY_DOWN, &GalleryDialog::key_down, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &GalleryDialog::show_context_menu, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) { m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) {
m_selected_items.clear(); m_selected_items.clear();
select(event); select(event);
@ -111,19 +113,11 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/)
this->Bind(wxEVT_BUTTON, method, this, ID); this->Bind(wxEVT_BUTTON, method, this, ID);
}; };
auto enable_del_fn = [this]() { size_t btn_pos = 0;
if (m_selected_items.empty()) add_btn(btn_pos++, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
return false; add_btn(btn_pos++, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, [this](){ return can_delete(); });
for (const Item& item : m_selected_items) //add_btn(btn_pos++, ID_BTN_REPLACE_CUSTOM_PNG, _L("Change thumbnail"), _L("Replace PNG for custom shape. You can't raplace thimbnail for system shape"), &GalleryDialog::change_thumbnail, [this](){ return can_change_thumbnail(); });
if (item.is_system) buttons->InsertStretchSpacer(btn_pos, 2* BORDER_W);
return false;
return true;
};
add_btn(0, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
add_btn(1, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, enable_del_fn);
add_btn(2, ID_BTN_REPLACE_CUSTOM_PNG, _L("Replace PNG"), _L("Replace PNG for custom shape. You can't raplace PNG for system shape"),&GalleryDialog::replace_custom_png, [this]() { return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); });
buttons->InsertStretchSpacer(3, 2* BORDER_W);
load_label_icon_list(); load_label_icon_list();
@ -146,6 +140,21 @@ GalleryDialog::~GalleryDialog()
{ {
} }
bool GalleryDialog::can_delete()
{
if (m_selected_items.empty())
return false;
for (const Item& item : m_selected_items)
if (item.is_system)
return false;
return true;
}
bool GalleryDialog::can_change_thumbnail()
{
return (m_selected_items.size() == 1 && !m_selected_items[0].is_system);
}
void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect)
{ {
const int& em = em_unit(); const int& em = em_unit();
@ -405,7 +414,7 @@ void GalleryDialog::add_custom_shapes(wxEvent& event)
load_files(input_files); load_files(input_files);
} }
void GalleryDialog::del_custom_shapes(wxEvent& event) void GalleryDialog::del_custom_shapes()
{ {
auto custom_dir = get_dir(false); auto custom_dir = get_dir(false);
@ -438,7 +447,7 @@ static void show_warning(const wxString& title, const std::string& error_file_ty
dialog.ShowModal(); dialog.ShowModal();
} }
void GalleryDialog::replace_custom_png(wxEvent& event) void GalleryDialog::change_thumbnail()
{ {
if (m_selected_items.size() != 1 || m_selected_items[0].is_system) if (m_selected_items.size() != 1 || m_selected_items[0].is_system)
return; return;
@ -494,6 +503,23 @@ void GalleryDialog::deselect(wxListEvent& event)
m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; })); m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; }));
} }
void GalleryDialog::show_context_menu(wxListEvent& event)
{
wxMenu* menu = new wxMenu();
if (can_delete())
append_menu_item(menu, wxID_ANY, _L("Delete"), "", [this](wxCommandEvent&) { del_custom_shapes(); });
if (can_change_thumbnail())
append_menu_item(menu, wxID_ANY, _L("Change thumbnail"), "", [this](wxCommandEvent&) { change_thumbnail(); });
this->PopupMenu(menu);
}
void GalleryDialog::key_down(wxListEvent& event)
{
if (can_delete() && (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK))
del_custom_shapes();
}
void GalleryDialog::update() void GalleryDialog::update()
{ {
m_selected_items.clear(); m_selected_items.clear();

View file

@ -33,10 +33,17 @@ class GalleryDialog : public DPIDialog
void load_label_icon_list(); void load_label_icon_list();
void add_custom_shapes(wxEvent& event); void add_custom_shapes(wxEvent& event);
void del_custom_shapes(wxEvent& event); void del_custom_shapes();
void replace_custom_png(wxEvent& event); void del_custom_shapes(wxEvent& event) { del_custom_shapes(); }
void change_thumbnail();
void change_thumbnail(wxEvent& event) { change_thumbnail(); }
void select(wxListEvent& event); void select(wxListEvent& event);
void deselect(wxListEvent& event); void deselect(wxListEvent& event);
void show_context_menu(wxListEvent& event);
void key_down(wxListEvent& event);
bool can_delete();
bool can_change_thumbnail();
void update(); void update();

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,13 +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;
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS assert(shader->get_name() == "mm_gouraud");
assert(shader->get_name() == "gouraud_mod");
#else
assert(shader->get_name() == "gouraud");
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("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)) {
@ -619,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,36 +43,33 @@ 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
{
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
auto* shader = wxGetApp().get_shader("gouraud_mod"); auto* shader = wxGetApp().get_shader("gouraud_mod");
#else #else
auto *shader = wxGetApp().get_shader("gouraud"); auto* shader = wxGetApp().get_shader("gouraud");
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
if (! shader) if (! shader)
return; return;
@ -83,10 +80,11 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool
#else #else
shader->set_uniform("print_box.actived", false); shader->set_uniform("print_box.actived", false);
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
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;
@ -265,7 +263,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;
} }
@ -296,11 +299,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); 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};
@ -326,10 +330,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) {
@ -351,7 +357,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>();
@ -360,7 +367,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { 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)
@ -369,7 +377,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;
@ -382,17 +391,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);
@ -417,9 +430,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)
@ -576,7 +592,8 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
#else #else
assert(shader->get_name() == "gouraud"); assert(shader->get_name() == "gouraud");
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);});
shader->set_uniform("offset_depth_buffer", true);
for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), 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()) {
@ -602,7 +619,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

@ -924,6 +924,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

@ -919,7 +919,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp
} }
if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{ {
wxGetApp().open_preferences(2); wxGetApp().open_preferences(2, "show_hints");
} }
ImGui::PopStyleColor(5); ImGui::PopStyleColor(5);

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

@ -597,6 +597,8 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
{ {
if (field && field->undo_to_sys_bitmap()) if (field && field->undo_to_sys_bitmap())
h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink()) + ctrl->m_h_gap; h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink()) + ctrl->m_h_gap;
else if (field && !field->undo_to_sys_bitmap() && field->blink())
draw_blinking_bmp(dc, wxPoint(h_pos, v_pos), field->blink());
// update width for full_width fields // update width for full_width fields
if (option_set.front().opt.full_width && field->getWindow()) if (option_set.front().opt.full_width && field->getWindow())
field->getWindow()->SetSize(ctrl->GetSize().x - h_pos, -1); field->getWindow()->SetSize(ctrl->GetSize().x - h_pos, -1);

View file

@ -4032,6 +4032,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()
{ {
@ -4164,6 +4165,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

@ -43,7 +43,7 @@ namespace Slic3r {
namespace GUI { namespace GUI {
PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) : PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab, const std::string& highlight_opt_key) :
DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition, DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE) wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
{ {
@ -51,6 +51,8 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) :
isOSX = true; isOSX = true;
#endif #endif
build(selected_tab); build(selected_tab);
if (!highlight_opt_key.empty())
init_highlighter(highlight_opt_key);
} }
static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& title, wxBookCtrlBase* tabs) static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& title, wxBookCtrlBase* tabs)
@ -450,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();
@ -755,6 +759,71 @@ void PreferencesDialog::create_settings_text_color_widget()
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());
} }
void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key)
{
m_highlighter.set_timer_owner(this, 0);
this->Bind(wxEVT_TIMER, [this](wxTimerEvent&)
{
m_highlighter.blink();
});
std::pair<OG_CustomCtrl*, bool*> ctrl = { nullptr, nullptr };
for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui }) {
ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1);
if (ctrl.first && ctrl.second) {
m_highlighter.init(ctrl);
break;
}
}
}
void PreferencesDialog::PreferencesHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/)
{
m_timer.SetOwner(owner, timerid);
}
void PreferencesDialog::PreferencesHighlighter::init(std::pair<OG_CustomCtrl*, bool*> params)
{
if (m_timer.IsRunning())
invalidate();
if (!params.first || !params.second)
return;
m_timer.Start(300, false);
m_custom_ctrl = params.first;
m_show_blink_ptr = params.second;
*m_show_blink_ptr = true;
m_custom_ctrl->Refresh();
}
void PreferencesDialog::PreferencesHighlighter::invalidate()
{
m_timer.Stop();
if (m_custom_ctrl && m_show_blink_ptr) {
*m_show_blink_ptr = false;
m_custom_ctrl->Refresh();
m_show_blink_ptr = nullptr;
m_custom_ctrl = nullptr;
}
m_blink_counter = 0;
}
void PreferencesDialog::PreferencesHighlighter::blink()
{
if (m_custom_ctrl && m_show_blink_ptr) {
*m_show_blink_ptr = !*m_show_blink_ptr;
m_custom_ctrl->Refresh();
}
else
return;
if ((++m_blink_counter) == 11)
invalidate();
}
} // GUI } // GUI
} // Slic3r } // Slic3r

View file

@ -5,6 +5,7 @@
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
#include <wx/dialog.h> #include <wx/dialog.h>
#include <wx/timer.h>
#include <map> #include <map>
class wxColourPickerCtrl; class wxColourPickerCtrl;
@ -20,6 +21,7 @@ namespace Slic3r {
namespace GUI { namespace GUI {
class ConfigOptionsGroup; class ConfigOptionsGroup;
class OG_CustomCtrl;
class PreferencesDialog : public DPIDialog class PreferencesDialog : public DPIDialog
{ {
@ -39,7 +41,7 @@ class PreferencesDialog : public DPIDialog
bool m_recreate_GUI{false}; bool m_recreate_GUI{false};
public: public:
explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0); explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0, const std::string& highlight_opt_key = std::string());
~PreferencesDialog() = default; ~PreferencesDialog() = default;
bool settings_layout_changed() const { return m_settings_layout_changed; } bool settings_layout_changed() const { return m_settings_layout_changed; }
@ -55,6 +57,22 @@ protected:
void create_icon_size_slider(); void create_icon_size_slider();
void create_settings_mode_widget(); void create_settings_mode_widget();
void create_settings_text_color_widget(); void create_settings_text_color_widget();
void init_highlighter(const t_config_option_key& opt_key);
struct PreferencesHighlighter
{
void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY);
void init(std::pair<OG_CustomCtrl*, bool*>);
void blink();
void invalidate();
private:
OG_CustomCtrl* m_custom_ctrl{ nullptr };
bool* m_show_blink_ptr{ nullptr };
int m_blink_counter{ 0 };
wxTimer m_timer;
}
m_highlighter;
}; };
} // GUI } // GUI

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:

View file

@ -34,7 +34,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
} }
} }
WHEN("offset2_ex") { WHEN("offset2_ex") {
ExPolygons result = Slic3r::offset2_ex(square_with_hole, 5.f, -2.f); ExPolygons result = Slic3r::offset2_ex({ square_with_hole }, 5.f, -2.f);
THEN("offset matches") { THEN("offset matches") {
REQUIRE(result == ExPolygons { { REQUIRE(result == ExPolygons { {
{ { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } }, { { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } },

View file

@ -49,7 +49,7 @@ offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, mit
Slic3r::ClipperLib::JoinType joinType Slic3r::ClipperLib::JoinType joinType
double miterLimit double miterLimit
CODE: CODE:
RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit); RETVAL = offset2_ex(union_ex(polygons), delta1, delta2, joinType, miterLimit);
OUTPUT: OUTPUT:
RETVAL RETVAL