Fixed conflicts after merge with master and ported changes into gouraud shaders to gouraud_mod shaders
This commit is contained in:
commit
2c0815f537
@ -32,6 +32,8 @@ struct SlopeDetection
|
||||
uniform vec4 uniform_color;
|
||||
uniform SlopeDetection slope;
|
||||
|
||||
uniform bool offset_depth_buffer;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
uniform sampler2D environment_tex;
|
||||
uniform bool use_environment_tex;
|
||||
@ -50,8 +52,6 @@ varying float world_pos_z;
|
||||
varying float world_normal_z;
|
||||
varying vec3 eye_normal;
|
||||
|
||||
uniform bool compute_triangle_normals_in_fs;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
@ -59,36 +59,7 @@ void main()
|
||||
vec3 color = uniform_color.rgb;
|
||||
float alpha = uniform_color.a;
|
||||
|
||||
vec2 intensity_fs = intensity;
|
||||
vec3 eye_normal_fs = eye_normal;
|
||||
float world_normal_z_fs = world_normal_z;
|
||||
if (compute_triangle_normals_in_fs) {
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
#ifdef FLIP_TRIANGLE_NORMALS
|
||||
triangle_normal = -triangle_normal;
|
||||
#endif
|
||||
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity_fs = vec2(0.0, 0.0);
|
||||
intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (gl_ModelViewMatrix * model_pos).xyz;
|
||||
intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// z component of normal vector in world coordinate used for slope shading
|
||||
world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0;
|
||||
}
|
||||
|
||||
if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) {
|
||||
if (slope.actived && world_normal_z < slope.normal_z - EPSILON) {
|
||||
color = vec3(0.7, 0.7, 1.0);
|
||||
alpha = 1.0;
|
||||
}
|
||||
@ -96,8 +67,13 @@ void main()
|
||||
color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color;
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
if (use_environment_tex)
|
||||
gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha);
|
||||
gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha);
|
||||
else
|
||||
#endif
|
||||
gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha);
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
|
||||
|
||||
// In the support painting gizmo and the seam painting gizmo are painted triangles rendered over the already
|
||||
// rendered object. To resolved z-fighting between previously rendered object and painted triangles, values
|
||||
// inside the depth buffer are offset by small epsilon for painted triangles inside those gizmos.
|
||||
gl_FragDepth = gl_FragCoord.z - (offset_depth_buffer ? EPSILON : 0.0);
|
||||
}
|
||||
|
@ -54,11 +54,8 @@ varying float world_pos_z;
|
||||
varying float world_normal_z;
|
||||
varying vec3 eye_normal;
|
||||
|
||||
uniform bool compute_triangle_normals_in_fs;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (!compute_triangle_normals_in_fs) {
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
|
||||
@ -73,7 +70,6 @@ void main()
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
}
|
||||
|
||||
model_pos = gl_Vertex;
|
||||
// Point in homogenous coordinates.
|
||||
@ -90,7 +86,6 @@ void main()
|
||||
}
|
||||
|
||||
// z component of normal vector in world coordinate used for slope shading
|
||||
if (!compute_triangle_normals_in_fs)
|
||||
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0;
|
||||
|
||||
gl_Position = ftransform();
|
||||
|
@ -45,6 +45,8 @@ struct SlopeDetection
|
||||
uniform vec4 uniform_color;
|
||||
uniform SlopeDetection slope;
|
||||
|
||||
uniform bool offset_depth_buffer;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
uniform sampler2D environment_tex;
|
||||
uniform bool use_environment_tex;
|
||||
@ -62,8 +64,6 @@ varying vec4 world_pos;
|
||||
varying float world_normal_z;
|
||||
varying vec3 eye_normal;
|
||||
|
||||
uniform bool compute_triangle_normals_in_fs;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
@ -71,36 +71,7 @@ void main()
|
||||
vec3 color = uniform_color.rgb;
|
||||
float alpha = uniform_color.a;
|
||||
|
||||
vec2 intensity_fs = intensity;
|
||||
vec3 eye_normal_fs = eye_normal;
|
||||
float world_normal_z_fs = world_normal_z;
|
||||
if (compute_triangle_normals_in_fs) {
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
#ifdef FLIP_TRIANGLE_NORMALS
|
||||
triangle_normal = -triangle_normal;
|
||||
#endif
|
||||
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity_fs = vec2(0.0, 0.0);
|
||||
intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (gl_ModelViewMatrix * model_pos).xyz;
|
||||
intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// z component of normal vector in world coordinate used for slope shading
|
||||
world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0;
|
||||
}
|
||||
|
||||
if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) {
|
||||
if (slope.actived && world_normal_z < slope.normal_z - EPSILON) {
|
||||
color = vec3(0.7, 0.7, 1.0);
|
||||
alpha = 1.0;
|
||||
}
|
||||
@ -123,8 +94,13 @@ void main()
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
if (use_environment_tex)
|
||||
gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha);
|
||||
gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha);
|
||||
else
|
||||
#endif
|
||||
gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha);
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
|
||||
|
||||
// In the support painting gizmo and the seam painting gizmo are painted triangles rendered over the already
|
||||
// rendered object. To resolved z-fighting between previously rendered object and painted triangles, values
|
||||
// inside the depth buffer are offset by small epsilon for painted triangles inside those gizmos.
|
||||
gl_FragDepth = gl_FragCoord.z - (offset_depth_buffer ? EPSILON : 0.0);
|
||||
}
|
||||
|
@ -43,11 +43,8 @@ varying vec4 world_pos;
|
||||
varying float world_normal_z;
|
||||
varying vec3 eye_normal;
|
||||
|
||||
uniform bool compute_triangle_normals_in_fs;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (!compute_triangle_normals_in_fs) {
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
|
||||
@ -62,14 +59,12 @@ void main()
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
}
|
||||
|
||||
model_pos = gl_Vertex;
|
||||
// Point in homogenous coordinates.
|
||||
world_pos = volume_world_matrix * gl_Vertex;
|
||||
|
||||
// z component of normal vector in world coordinate used for slope shading
|
||||
if (!compute_triangle_normals_in_fs)
|
||||
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0;
|
||||
|
||||
gl_Position = ftransform();
|
||||
|
55
resources/shaders/mm_gouraud.fs
Normal file
55
resources/shaders/mm_gouraud.fs
Normal file
@ -0,0 +1,55 @@
|
||||
#version 110
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const float EPSILON = 0.0001;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
vec3 color = uniform_color.rgb;
|
||||
float alpha = uniform_color.a;
|
||||
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
#ifdef FLIP_TRIANGLE_NORMALS
|
||||
triangle_normal = -triangle_normal;
|
||||
#endif
|
||||
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 eye_normal = normalize(gl_NormalMatrix * triangle_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
// x = diffuse, y = specular;
|
||||
vec2 intensity = vec2(0.0, 0.0);
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (gl_ModelViewMatrix * model_pos).xyz;
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
|
||||
}
|
23
resources/shaders/mm_gouraud.vs
Normal file
23
resources/shaders/mm_gouraud.vs
Normal file
@ -0,0 +1,23 @@
|
||||
#version 110
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
uniform mat4 volume_world_matrix;
|
||||
// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane.
|
||||
uniform vec2 z_range;
|
||||
// Clipping plane - general orientation. Used by the SLA gizmo.
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
|
||||
void main()
|
||||
{
|
||||
model_pos = gl_Vertex;
|
||||
// Point in homogenous coordinates.
|
||||
vec4 world_pos = volume_world_matrix * gl_Vertex;
|
||||
|
||||
gl_Position = ftransform();
|
||||
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
}
|
@ -157,6 +157,19 @@ bool PolyNode::IsHole() const
|
||||
return result;
|
||||
}
|
||||
|
||||
void PolyTree::RemoveOutermostPolygon()
|
||||
{
|
||||
if (this->ChildCount() == 1 && this->Childs[0]->ChildCount() > 0) {
|
||||
PolyNode *outerNode = this->Childs[0];
|
||||
this->Childs.reserve(outerNode->ChildCount());
|
||||
this->Childs[0] = outerNode->Childs[0];
|
||||
this->Childs[0]->Parent = outerNode->Parent;
|
||||
for (int i = 1; i < outerNode->ChildCount(); ++i)
|
||||
this->AddChild(*outerNode->Childs[i]);
|
||||
} else
|
||||
this->Clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Miscellaneous global functions
|
||||
//------------------------------------------------------------------------------
|
||||
@ -3444,7 +3457,8 @@ void ClipperOffset::Execute(Paths& solution, double delta)
|
||||
clpr.AddPath(outer, ptSubject, true);
|
||||
clpr.ReverseSolution(true);
|
||||
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
|
||||
if (solution.size() > 0) solution.erase(solution.begin());
|
||||
if (! solution.empty())
|
||||
solution.erase(solution.begin());
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@ -3475,17 +3489,7 @@ void ClipperOffset::Execute(PolyTree& solution, double delta)
|
||||
clpr.ReverseSolution(true);
|
||||
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
|
||||
//remove the outer PolyNode rectangle ...
|
||||
if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0)
|
||||
{
|
||||
PolyNode* outerNode = solution.Childs[0];
|
||||
solution.Childs.reserve(outerNode->ChildCount());
|
||||
solution.Childs[0] = outerNode->Childs[0];
|
||||
solution.Childs[0]->Parent = outerNode->Parent;
|
||||
for (int i = 1; i < outerNode->ChildCount(); ++i)
|
||||
solution.AddChild(*outerNode->Childs[i]);
|
||||
}
|
||||
else
|
||||
solution.Clear();
|
||||
solution.RemoveOutermostPolygon();
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -180,6 +180,7 @@ public:
|
||||
PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); }
|
||||
void Clear() { AllNodes.clear(); Childs.clear(); }
|
||||
int Total() const;
|
||||
void RemoveOutermostPolygon();
|
||||
private:
|
||||
PolyTree(const PolyTree &src) = delete;
|
||||
PolyTree& operator=(const PolyTree &src) = delete;
|
||||
@ -521,6 +522,7 @@ public:
|
||||
double MiterLimit;
|
||||
double ArcTolerance;
|
||||
double ShortestEdgeLength;
|
||||
|
||||
private:
|
||||
Paths m_destPolys;
|
||||
Path m_srcPoly;
|
||||
@ -528,6 +530,8 @@ private:
|
||||
std::vector<DoublePoint> m_normals;
|
||||
double m_delta, m_sinA, m_sin, m_cos;
|
||||
double m_miterLim, m_StepsPerRad;
|
||||
// x: index of the lowest contour in m_polyNodes
|
||||
// y: index of the lowest point in the lowest contour
|
||||
IntPoint m_lowest;
|
||||
PolyNode m_polyNodes;
|
||||
|
||||
|
@ -50,7 +50,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr
|
||||
{
|
||||
ExPolygons ex_polygons;
|
||||
for (LayerRegion *region : print_object.layers().front()->regions())
|
||||
Slic3r::append(ex_polygons, offset_ex(offset_ex(region->slices.surfaces, float(SCALED_EPSILON)), -float(SCALED_EPSILON)));
|
||||
Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON)));
|
||||
return ex_polygons;
|
||||
}
|
||||
|
||||
@ -177,7 +177,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print
|
||||
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare)));
|
||||
|
||||
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare));
|
||||
append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
|
||||
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
|
||||
@ -230,13 +230,13 @@ static ExPolygons inner_brim_area(const Print &print,
|
||||
}
|
||||
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
|
||||
append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare)));
|
||||
append(brim_area_object, diff_ex(shrink_ex(ex_poly.holes, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly.holes, brim_width + brim_separation, ClipperLib::jtSquare)));
|
||||
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
|
||||
|
||||
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare));
|
||||
append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
|
||||
|
||||
append(holes_object, ex_poly.holes);
|
||||
}
|
||||
@ -385,10 +385,10 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing()));
|
||||
for (size_t i = 0; i < num_loops; ++i) {
|
||||
try_cancel();
|
||||
islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
|
||||
islands = expand(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
|
||||
for (Polygon &poly : islands)
|
||||
poly.douglas_peucker(SCALED_RESOLUTION);
|
||||
polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
|
||||
polygons_append(loops, shrink(islands, 0.5f * float(flow.scaled_spacing())));
|
||||
}
|
||||
loops = union_pt_chained_outside_in(loops);
|
||||
|
||||
|
@ -117,15 +117,6 @@ Polylines PolyTreeToPolylines(ClipperLib::PolyTree &&polytree)
|
||||
return out;
|
||||
}
|
||||
|
||||
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(input, ClipperLib::ptSubject, true);
|
||||
ClipperLib::PolyTree polytree;
|
||||
clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero
|
||||
return PolyTreeToExPolygons(std::move(polytree));
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Global test.
|
||||
bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
|
||||
@ -165,23 +156,28 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Offset outside by 10um, one by one.
|
||||
template<typename PathsProvider>
|
||||
static ClipperLib::Paths safety_offset(PathsProvider &&paths)
|
||||
// Offset CCW contours outside, CW contours (holes) inside.
|
||||
// Don't calculate union of the output paths.
|
||||
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
|
||||
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
ClipperLib::ClipperOffset co;
|
||||
ClipperLib::Paths out;
|
||||
out.reserve(paths.size());
|
||||
ClipperLib::Paths out_this;
|
||||
if (joinType == jtRound)
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
for (const ClipperLib::Path &path : paths) {
|
||||
co.Clear();
|
||||
co.MiterLimit = 2.;
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
co.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
|
||||
bool ccw = ClipperLib::Orientation(path);
|
||||
co.Execute(out_this, ccw ? ClipperSafetyOffset : - ClipperSafetyOffset);
|
||||
co.AddPath(path, joinType, endType);
|
||||
bool ccw = endType == ClipperLib::etClosedPolygon ? ClipperLib::Orientation(path) : true;
|
||||
co.Execute(out_this, ccw ? offset : - offset);
|
||||
if (! ccw) {
|
||||
// Reverse the resulting contours.
|
||||
for (ClipperLib::Path &path : out_this)
|
||||
@ -192,38 +188,122 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths)
|
||||
return out;
|
||||
}
|
||||
|
||||
// Only safe for a single path.
|
||||
// Offset outside by 10um, one by one.
|
||||
template<typename PathsProvider>
|
||||
ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
static ClipperLib::Paths safety_offset(PathsProvider &&paths)
|
||||
{
|
||||
// perform offset
|
||||
ClipperLib::ClipperOffset co;
|
||||
if (joinType == jtRound)
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
float delta_scaled = delta;
|
||||
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
co.AddPaths(std::forward<PathsProvider>(input), joinType, endType);
|
||||
ClipperLib::Paths retval;
|
||||
co.Execute(retval, delta_scaled);
|
||||
return raw_offset(std::forward<PathsProvider>(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit);
|
||||
}
|
||||
|
||||
template<class TResult, class TSubj, class TClip>
|
||||
TResult clipper_do(
|
||||
const ClipperLib::ClipType clipType,
|
||||
TSubj && subject,
|
||||
TClip && clip,
|
||||
const ClipperLib::PolyFillType fillType)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true);
|
||||
TResult retval;
|
||||
clipper.Execute(clipType, retval, fillType, fillType);
|
||||
return retval;
|
||||
}
|
||||
|
||||
Slic3r::Polygons offset(const Slic3r::Polygon& polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
|
||||
template<class TResult, class TSubj, class TClip>
|
||||
TResult clipper_do(
|
||||
const ClipperLib::ClipType clipType,
|
||||
TSubj && subject,
|
||||
TClip && clip,
|
||||
const ClipperLib::PolyFillType fillType,
|
||||
const ApplySafetyOffset do_safety_offset)
|
||||
{
|
||||
// Safety offset only allowed on intersection and difference.
|
||||
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
|
||||
return do_safety_offset == ApplySafetyOffset::Yes ?
|
||||
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
|
||||
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
|
||||
}
|
||||
|
||||
template<class TResult, class TSubj>
|
||||
TResult clipper_union(
|
||||
TSubj && subject,
|
||||
// fillType pftNonZero and pftPositive "should" produce the same result for "normalized with implicit union" set of polygons
|
||||
const ClipperLib::PolyFillType fillType = ClipperLib::pftNonZero)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
|
||||
TResult retval;
|
||||
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Perform union of input polygons using the positive rule, convert to ExPolygons.
|
||||
//FIXME is there any benefit of not doing the boolean / using pftEvenOdd?
|
||||
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union)
|
||||
{
|
||||
return PolyTreeToExPolygons(clipper_union<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
|
||||
}
|
||||
|
||||
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
|
||||
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(offset > 0);
|
||||
return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit);
|
||||
}
|
||||
|
||||
template<class TResult, typename PathsProvider>
|
||||
static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(offset > 0);
|
||||
return clipper_union<TResult>(raw_offset(std::forward<PathsProvider>(paths), offset, joinType, miterLimit));
|
||||
}
|
||||
|
||||
// used by shrink_paths()
|
||||
template<class Container> static void remove_outermost_polygon(Container & solution);
|
||||
template<> void remove_outermost_polygon<ClipperLib::Paths>(ClipperLib::Paths &solution)
|
||||
{ if (! solution.empty()) solution.erase(solution.begin()); }
|
||||
template<> void remove_outermost_polygon<ClipperLib::PolyTree>(ClipperLib::PolyTree &solution)
|
||||
{ solution.RemoveOutermostPolygon(); }
|
||||
|
||||
template<class TResult, typename PathsProvider>
|
||||
static TResult shrink_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(offset > 0);
|
||||
TResult out;
|
||||
if (auto raw = raw_offset(std::forward<PathsProvider>(paths), - offset, joinType, miterLimit); ! raw.empty()) {
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(raw, ClipperLib::ptSubject, true);
|
||||
ClipperLib::IntRect r = clipper.GetBounds();
|
||||
clipper.AddPath({ { r.left - 10, r.bottom + 10 }, { r.right + 10, r.bottom + 10 }, { r.right + 10, r.top - 10 }, { r.left - 10, r.top - 10 } }, ClipperLib::ptSubject, true);
|
||||
clipper.ReverseSolution(true);
|
||||
clipper.Execute(ClipperLib::ctUnion, out, ClipperLib::pftNegative, ClipperLib::pftNegative);
|
||||
remove_outermost_polygon(out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template<class TResult, typename PathsProvider>
|
||||
static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(offset != 0);
|
||||
return offset > 0 ?
|
||||
expand_paths<TResult>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit) :
|
||||
shrink_paths<TResult>(std::forward<PathsProvider>(paths), - offset, joinType, miterLimit);
|
||||
}
|
||||
|
||||
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(raw_offset(ClipperUtils::SinglePathProvider(polygon.points), delta, joinType, miterLimit)); }
|
||||
|
||||
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
|
||||
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
|
||||
{ return to_polygons(offset_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
|
||||
#endif // CLIPPERUTILS_UNSAFE_OFFSET
|
||||
{ return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
|
||||
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(ClipperUtils::SinglePathProvider(polyline.points), ClipperLib::etOpenButt, delta, joinType, miterLimit)); }
|
||||
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); }
|
||||
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(ClipperUtils::PolylinesProvider(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); }
|
||||
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); }
|
||||
|
||||
// returns number of expolygons collected (0 or 1).
|
||||
static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
|
||||
@ -275,13 +355,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
|
||||
} else if (delta < 0) {
|
||||
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
|
||||
// Subtract the offsetted holes from the offsetted contours.
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.Clear();
|
||||
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(holes, ClipperLib::ptClip, true);
|
||||
ClipperLib::Paths output;
|
||||
clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
if (! output.empty()) {
|
||||
if (auto output = clipper_do<ClipperLib::Paths>(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) {
|
||||
append(out, std::move(output));
|
||||
} else {
|
||||
// The offsetted holes have eaten up the offsetted outer contour.
|
||||
@ -308,7 +382,7 @@ static int offset_expolygon_inner(const Slic3r::Surface &surface, const float de
|
||||
static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
|
||||
{ return offset_expolygon_inner(surface->expolygon, delta, joinType, miterLimit, out); }
|
||||
|
||||
ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
ClipperLib::Paths expolygon_offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
ClipperLib::Paths out;
|
||||
offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out);
|
||||
@ -317,9 +391,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta,
|
||||
|
||||
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
|
||||
// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
|
||||
// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united.
|
||||
// Each ExPolygon is offsetted separately. For outer offset, the the offsetted ExPolygons shall be united outside of this function.
|
||||
template<typename ExPolygonVector>
|
||||
ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
static std::pair<ClipperLib::Paths, size_t> expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
// Offsetted ExPolygons before they are united.
|
||||
ClipperLib::Paths output;
|
||||
@ -329,124 +403,101 @@ ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta,
|
||||
size_t expolygons_collected = 0;
|
||||
for (const auto &expoly : expolygons)
|
||||
expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output);
|
||||
|
||||
// 4) Unite the offsetted expolygons.
|
||||
if (expolygons_collected > 1 && delta > 0) {
|
||||
// There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.Clear();
|
||||
clipper.AddPaths(output, ClipperLib::ptSubject, true);
|
||||
clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
} else {
|
||||
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
|
||||
return std::make_pair(std::move(output), expolygons_collected);
|
||||
}
|
||||
|
||||
return output;
|
||||
// See comment on expolygon_offsets_raw. In addition, for positive offset the contours are united.
|
||||
template<typename ExPolygonVector>
|
||||
static ClipperLib::Paths expolygons_offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit);
|
||||
// Unite the offsetted expolygons.
|
||||
return expolygons_collected > 1 && delta > 0 ?
|
||||
// There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
|
||||
clipper_union<ClipperLib::Paths>(output) :
|
||||
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
|
||||
output;
|
||||
}
|
||||
|
||||
// See comment on expolygons_offset_raw. In addition, the polygons are always united to conver to polytree.
|
||||
template<typename ExPolygonVector>
|
||||
static ClipperLib::PolyTree expolygons_offset_pt(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit);
|
||||
// Unite the offsetted expolygons for both the
|
||||
return clipper_union<ClipperLib::PolyTree>(output);
|
||||
}
|
||||
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); }
|
||||
{ return to_polygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); }
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); }
|
||||
{ return to_polygons(expolygons_offset(expolygons, delta, joinType, miterLimit)); }
|
||||
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); }
|
||||
{ return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); }
|
||||
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); }
|
||||
{ return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); }
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); }
|
||||
//FIXME one may spare one Clipper Union call.
|
||||
{ return ClipperPaths_to_Slic3rExPolygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); }
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); }
|
||||
{ return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); }
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{ return ClipperPaths_to_Slic3rExPolygons(_offset(surfaces, delta, joinType, miterLimit)); }
|
||||
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
|
||||
|
||||
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
|
||||
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &polygons)
|
||||
{ return offset(polygons, ClipperSafetyOffset); }
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons)
|
||||
{ return offset_ex(polygons, ClipperSafetyOffset); }
|
||||
#endif // CLIPPERUTILS_UNSAFE_OFFSET
|
||||
|
||||
Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons)
|
||||
{ return offset(expolygons, ClipperSafetyOffset); }
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons)
|
||||
{ return offset_ex(expolygons, ClipperSafetyOffset); }
|
||||
|
||||
ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
|
||||
{
|
||||
// prepare ClipperOffset object
|
||||
ClipperLib::ClipperOffset co;
|
||||
if (joinType == jtRound) {
|
||||
co.ArcTolerance = miterLimit;
|
||||
} else {
|
||||
co.MiterLimit = miterLimit;
|
||||
}
|
||||
float delta_scaled1 = delta1;
|
||||
float delta_scaled2 = delta2;
|
||||
co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR);
|
||||
|
||||
// perform first offset
|
||||
ClipperLib::Paths output1;
|
||||
co.AddPaths(ClipperUtils::PolygonsProvider(polygons), joinType, ClipperLib::etClosedPolygon);
|
||||
co.Execute(output1, delta_scaled1);
|
||||
|
||||
// perform second offset
|
||||
co.Clear();
|
||||
co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon);
|
||||
ClipperLib::Paths retval;
|
||||
co.Execute(retval, delta_scaled2);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
Polygons offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
|
||||
{
|
||||
return to_polygons(_offset2(polygons, delta1, delta2, joinType, miterLimit));
|
||||
}
|
||||
|
||||
ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
|
||||
{
|
||||
return ClipperPaths_to_Slic3rExPolygons(_offset2(polygons, delta1, delta2, joinType, miterLimit));
|
||||
}
|
||||
|
||||
//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper
|
||||
// conversions and unnecessary Clipper calls. It is not that bad now as Clipper uses Slic3r's own Point / Polygon types directly.
|
||||
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
return offset(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit);
|
||||
return to_polygons(offset_paths<ClipperLib::Paths>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
return offset_ex(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit);
|
||||
return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
ExPolygons offset2_ex(const Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
|
||||
return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(surfaces, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
|
||||
template<class TResult, class TSubj, class TClip>
|
||||
TResult _clipper_do(
|
||||
const ClipperLib::ClipType clipType,
|
||||
TSubj && subject,
|
||||
TClip && clip,
|
||||
const ClipperLib::PolyFillType fillType)
|
||||
// Offset outside, then inside produces morphological closing. All deltas should be positive.
|
||||
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true);
|
||||
TResult retval;
|
||||
clipper.Execute(clipType, retval, fillType, fillType);
|
||||
return retval;
|
||||
assert(delta1 > 0);
|
||||
assert(delta2 > 0);
|
||||
return to_polygons(shrink_paths<ClipperLib::Paths>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(delta1 > 0);
|
||||
assert(delta2 > 0);
|
||||
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(delta1 > 0);
|
||||
assert(delta2 > 0);
|
||||
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
|
||||
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
|
||||
template<class TResult, class TSubj, class TClip>
|
||||
TResult _clipper_do(
|
||||
const ClipperLib::ClipType clipType,
|
||||
TSubj && subject,
|
||||
TClip && clip,
|
||||
const ClipperLib::PolyFillType fillType,
|
||||
const ApplySafetyOffset do_safety_offset)
|
||||
// Offset inside, then outside produces morphological opening. All deltas should be positive.
|
||||
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
// Safety offset only allowed on intersection and difference.
|
||||
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
|
||||
return do_safety_offset == ApplySafetyOffset::Yes ?
|
||||
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
|
||||
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
|
||||
assert(delta1 > 0);
|
||||
assert(delta2 > 0);
|
||||
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(delta1 > 0);
|
||||
assert(delta2 > 0);
|
||||
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::ExPolygonsProvider(expolygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
assert(delta1 > 0);
|
||||
assert(delta2 > 0);
|
||||
//FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces).
|
||||
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
|
||||
}
|
||||
|
||||
// Fix of #117: A large fractal pyramid takes ages to slice
|
||||
@ -457,29 +508,22 @@ TResult _clipper_do(
|
||||
// 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
|
||||
// 2) Run Clipper Union once again to extract the PolyTree from the result of 1).
|
||||
template<typename PathProvider1, typename PathProvider2>
|
||||
inline ClipperLib::PolyTree _clipper_do_polytree2(
|
||||
inline ClipperLib::PolyTree clipper_do_polytree(
|
||||
const ClipperLib::ClipType clipType,
|
||||
PathProvider1 &&subject,
|
||||
PathProvider2 &&clip,
|
||||
const ClipperLib::PolyFillType fillType)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(std::forward<PathProvider1>(subject), ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(std::forward<PathProvider2>(clip), ClipperLib::ptClip, true);
|
||||
// Perform the operation with the output to input_subject.
|
||||
// This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library
|
||||
// if there are overapping edges.
|
||||
ClipperLib::Paths input_subject;
|
||||
clipper.Execute(clipType, input_subject, fillType, fillType);
|
||||
if (auto output = clipper_do<ClipperLib::Paths>(clipType, subject, clip, fillType); ! output.empty())
|
||||
// Perform an additional Union operation to generate the PolyTree ordering.
|
||||
clipper.Clear();
|
||||
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
|
||||
ClipperLib::PolyTree retval;
|
||||
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
|
||||
return retval;
|
||||
return clipper_union<ClipperLib::PolyTree>(output, fillType);
|
||||
return ClipperLib::PolyTree();
|
||||
}
|
||||
template<typename PathProvider1, typename PathProvider2>
|
||||
inline ClipperLib::PolyTree _clipper_do_polytree2(
|
||||
inline ClipperLib::PolyTree clipper_do_polytree(
|
||||
const ClipperLib::ClipType clipType,
|
||||
PathProvider1 &&subject,
|
||||
PathProvider2 &&clip,
|
||||
@ -488,14 +532,14 @@ inline ClipperLib::PolyTree _clipper_do_polytree2(
|
||||
{
|
||||
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
|
||||
return do_safety_offset == ApplySafetyOffset::Yes ?
|
||||
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
|
||||
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
|
||||
clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
|
||||
clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
|
||||
}
|
||||
|
||||
template<class TSubj, class TClip>
|
||||
static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset)
|
||||
{
|
||||
return to_polygons(_clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset));
|
||||
return to_polygons(clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset));
|
||||
}
|
||||
|
||||
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
@ -506,6 +550,8 @@ Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons
|
||||
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
@ -529,7 +575,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons
|
||||
|
||||
template <typename TSubject, typename TClip>
|
||||
static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero)
|
||||
{ return PolyTreeToExPolygons(_clipper_do_polytree2(clipType, std::forward<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); }
|
||||
{ return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); }
|
||||
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
@ -578,9 +624,9 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type)
|
||||
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject)
|
||||
{ return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
|
||||
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject)
|
||||
{ return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
|
||||
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
|
||||
|
||||
template<typename PathsProvider1, typename PathsProvider2>
|
||||
Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip)
|
||||
@ -692,14 +738,15 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
|
||||
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
|
||||
ClipperLib::PolyTree union_pt(const Polygons &subject)
|
||||
{
|
||||
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
|
||||
return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
|
||||
}
|
||||
|
||||
ClipperLib::PolyTree union_pt(const ExPolygons &subject)
|
||||
{
|
||||
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
|
||||
return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
|
||||
}
|
||||
|
||||
// Simple spatial ordering of Polynodes
|
||||
@ -730,7 +777,7 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou
|
||||
});
|
||||
}
|
||||
|
||||
static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||
static void traverse_pt_outside_in(ClipperLib::PolyNodes &&nodes, Polygons *retval)
|
||||
{
|
||||
// collect ordering points
|
||||
Points ordering_points;
|
||||
@ -740,22 +787,20 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons
|
||||
|
||||
// Perform the ordering, push results recursively.
|
||||
//FIXME pass the last point to chain_clipper_polynodes?
|
||||
for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
|
||||
retval->emplace_back(node->Contour);
|
||||
for (ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
|
||||
retval->emplace_back(std::move(node->Contour));
|
||||
if (node->IsHole())
|
||||
// Orient a hole, which is clockwise oriented, to CCW.
|
||||
retval->back().reverse();
|
||||
// traverse the next depth
|
||||
traverse_pt_outside_in(node->Childs, retval);
|
||||
traverse_pt_outside_in(std::move(node->Childs), retval);
|
||||
}
|
||||
}
|
||||
|
||||
Polygons union_pt_chained_outside_in(const Polygons &subject)
|
||||
{
|
||||
ClipperLib::PolyTree polytree = union_pt(subject);
|
||||
|
||||
Polygons retval;
|
||||
traverse_pt_outside_in(polytree.Childs, &retval);
|
||||
traverse_pt_outside_in(union_pt(subject).Childs, &retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@ -12,16 +12,26 @@ using Slic3r::ClipperLib::jtMiter;
|
||||
using Slic3r::ClipperLib::jtRound;
|
||||
using Slic3r::ClipperLib::jtSquare;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static constexpr const float ClipperSafetyOffset = 10.f;
|
||||
|
||||
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
|
||||
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
|
||||
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
|
||||
// However such a high limit causes issues with large positive or negative offsets, where a sharp corner
|
||||
// is extended excessively.
|
||||
static constexpr const double DefaultMiterLimit = 3.;
|
||||
|
||||
static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Slic3r::ClipperLib::jtSquare;
|
||||
// Miter limit is ignored for jtSquare.
|
||||
static constexpr const double DefaultLineMiterLimit = 0.;
|
||||
|
||||
enum class ApplySafetyOffset {
|
||||
No,
|
||||
Yes
|
||||
};
|
||||
|
||||
#define CLIPPERUTILS_UNSAFE_OFFSET
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace ClipperUtils {
|
||||
class PathsProviderIteratorBase {
|
||||
public:
|
||||
@ -81,6 +91,33 @@ namespace ClipperUtils {
|
||||
static Points s_end;
|
||||
};
|
||||
|
||||
template<typename PathType>
|
||||
class PathsProvider {
|
||||
public:
|
||||
PathsProvider(const std::vector<PathType> &paths) : m_paths(paths) {}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(typename std::vector<PathType>::const_iterator it) : m_it(it) {}
|
||||
const Points& operator*() const { return *m_it; }
|
||||
bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
const Points& operator++(int) { return *(m_it ++); }
|
||||
iterator& operator++() { ++ m_it; return *this; }
|
||||
private:
|
||||
typename std::vector<PathType>::const_iterator m_it;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_paths.begin()); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(m_paths.end()); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return m_paths.size(); }
|
||||
|
||||
private:
|
||||
const std::vector<PathType> &m_paths;
|
||||
};
|
||||
|
||||
template<typename MultiPointType>
|
||||
class MultiPointsProvider {
|
||||
public:
|
||||
@ -261,36 +298,82 @@ namespace ClipperUtils {
|
||||
};
|
||||
}
|
||||
|
||||
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input);
|
||||
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
|
||||
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false);
|
||||
|
||||
// offset Polygons
|
||||
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
|
||||
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
|
||||
// offset Polylines
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3);
|
||||
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
|
||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
|
||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
|
||||
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }
|
||||
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) { return offset_ex(expolygons, ClipperSafetyOffset); }
|
||||
|
||||
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons);
|
||||
Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons);
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons);
|
||||
|
||||
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
// Aliases for the various offset(...) functions, conveying the purpose of the offset.
|
||||
inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); }
|
||||
// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
|
||||
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
|
||||
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
|
||||
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons);
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
|
||||
#endif // CLIPPERUTILS_UNSAFE_OFFSET
|
||||
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
|
||||
// Offset outside, then inside produces morphological closing. All deltas should be positive.
|
||||
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return closing(polygons, delta, delta, joinType, miterLimit); }
|
||||
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return closing_ex(polygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); }
|
||||
|
||||
// Offset inside, then outside produces morphological opening. All deltas should be positive.
|
||||
// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(polygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(expolygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(surfaces, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); }
|
||||
|
||||
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
|
||||
|
||||
@ -299,6 +382,7 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons
|
||||
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
@ -366,6 +450,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
|
||||
|
||||
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
|
||||
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject);
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);
|
||||
|
||||
|
@ -252,11 +252,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
// Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
|
||||
Polygons collapsed = diff(
|
||||
surfaces_polygons,
|
||||
offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2 + ClipperSafetyOffset));
|
||||
opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset)));
|
||||
//FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being
|
||||
// added if two offsetted void regions merge.
|
||||
// polygons_append(voids, collapsed);
|
||||
ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, ApplySafetyOffset::Yes);
|
||||
ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes);
|
||||
// Now find an internal infill SurfaceFill to add these extrusions to.
|
||||
SurfaceFill *internal_solid_fill = nullptr;
|
||||
unsigned int region_id = 0;
|
||||
|
@ -402,19 +402,19 @@ public:
|
||||
hole.rotate(angle);
|
||||
}
|
||||
|
||||
double mitterLimit = 3.;
|
||||
double miterLimit = DefaultMiterLimit;
|
||||
// for the infill pattern, don't cut the corners.
|
||||
// default miterLimt = 3
|
||||
//double mitterLimit = 10.;
|
||||
//double miterLimit = 10.;
|
||||
assert(aoffset1 < 0);
|
||||
assert(aoffset2 <= 0);
|
||||
assert(aoffset2 == 0 || aoffset2 < aoffset1);
|
||||
// bool sticks_removed =
|
||||
remove_sticks(polygons_src);
|
||||
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
|
||||
polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, mitterLimit);
|
||||
polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
|
||||
if (aoffset2 < 0)
|
||||
polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), ClipperLib::jtMiter, mitterLimit);
|
||||
polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit);
|
||||
// Filter out contours with zero area or small area, contours with 2 points only.
|
||||
const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
|
||||
remove_small(polygons_outer, min_area_threshold);
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <numeric>
|
||||
#include <unordered_set>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -33,6 +34,16 @@ struct Intersection
|
||||
float distance;
|
||||
};
|
||||
|
||||
struct ClosestLine
|
||||
{
|
||||
// Index of the polygon containing this line.
|
||||
size_t border_idx;
|
||||
// Index of this line on the polygon containing it.
|
||||
size_t line_idx;
|
||||
// Closest point on the line.
|
||||
Point point;
|
||||
};
|
||||
|
||||
// Finding all intersections of a set of contours with a line segment.
|
||||
struct AllIntersectionsVisitor
|
||||
{
|
||||
@ -53,7 +64,7 @@ struct AllIntersectionsVisitor
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix)
|
||||
{
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
// Called with a row and column of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||
Point intersection_point;
|
||||
@ -82,7 +93,7 @@ struct FirstIntersectionVisitor
|
||||
{
|
||||
assert(pt_current != nullptr);
|
||||
assert(pt_next != nullptr);
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
// Called with a row and column of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
this->intersect = false;
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||
@ -103,6 +114,180 @@ struct FirstIntersectionVisitor
|
||||
bool intersect = false;
|
||||
};
|
||||
|
||||
// Visitor to create a list of closet lines to a defined point.
|
||||
struct MinDistanceVisitor
|
||||
{
|
||||
explicit MinDistanceVisitor(const EdgeGrid::Grid &grid, const Point ¢er, double max_distance_squared)
|
||||
: grid(grid), center(center), max_distance_squared(max_distance_squared)
|
||||
{}
|
||||
|
||||
void init()
|
||||
{
|
||||
this->closest_lines.clear();
|
||||
this->closest_lines_set.clear();
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix)
|
||||
{
|
||||
// Called with a row and column of the grid cell, which is inside a bounding box.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = grid.segment(*it_contour_and_segment);
|
||||
Point closest_point;
|
||||
if (closest_lines_set.find(*it_contour_and_segment) == closest_lines_set.end() &&
|
||||
line_alg::distance_to_squared(Line(segment.first, segment.second), center, &closest_point) <= this->max_distance_squared) {
|
||||
closest_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, closest_point});
|
||||
closest_lines_set.insert(*it_contour_and_segment);
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid & grid;
|
||||
const Slic3r::Point center;
|
||||
std::vector<ClosestLine> closest_lines;
|
||||
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> closest_lines_set;
|
||||
double max_distance_squared = std::numeric_limits<double>::max();
|
||||
};
|
||||
|
||||
// Returns sorted list of closest lines to a passed point within a passed radius
|
||||
static std::vector<ClosestLine> get_closest_lines_in_radius(const EdgeGrid::Grid &grid, const Point ¢er, float search_radius)
|
||||
{
|
||||
Point radius_vector(search_radius, search_radius);
|
||||
MinDistanceVisitor visitor(grid, center, search_radius * search_radius);
|
||||
grid.visit_cells_intersecting_box(BoundingBox(center - radius_vector, center + radius_vector), visitor);
|
||||
std::sort(visitor.closest_lines.begin(), visitor.closest_lines.end(), [¢er](const auto &l, const auto &r) {
|
||||
return (center - l.point).template cast<double>().squaredNorm() < (center - r.point).template cast<double>().squaredNorm();
|
||||
});
|
||||
|
||||
return visitor.closest_lines;
|
||||
}
|
||||
|
||||
// When the offset is too big, then original travel doesn't have to cross created boundaries.
|
||||
// For these cases, this function adds another intersection with lines around the start and the end point of the original travel.
|
||||
static std::vector<Intersection> extend_for_closest_lines(const std::vector<Intersection> &intersections,
|
||||
const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
const float search_radius)
|
||||
{
|
||||
const std::vector<ClosestLine> start_lines = get_closest_lines_in_radius(boundary.grid, start, search_radius);
|
||||
const std::vector<ClosestLine> end_lines = get_closest_lines_in_radius(boundary.grid, end, search_radius);
|
||||
|
||||
// Compute distance to the closest point in the ClosestLine from begin of contour.
|
||||
auto compute_distance = [&boundary](const ClosestLine &closest_line) -> float {
|
||||
float dist_from_line_begin = (closest_line.point - boundary.boundaries[closest_line.border_idx][closest_line.line_idx]).cast<float>().norm();
|
||||
return boundary.boundaries_params[closest_line.border_idx][closest_line.line_idx] + dist_from_line_begin;
|
||||
};
|
||||
|
||||
// It tries to find closest lines for both start point and end point of the travel which has the same border_idx
|
||||
auto endpoints_close_to_same_boundary = [&start_lines, &end_lines]() -> std::pair<size_t, size_t> {
|
||||
std::unordered_set<size_t> boundaries_from_start;
|
||||
for (const ClosestLine &cl_start : start_lines)
|
||||
boundaries_from_start.insert(cl_start.border_idx);
|
||||
for (const ClosestLine &cl_end : end_lines)
|
||||
if (boundaries_from_start.find(cl_end.border_idx) != boundaries_from_start.end())
|
||||
for (const ClosestLine &cl_start : start_lines)
|
||||
if (cl_start.border_idx == cl_end.border_idx) {
|
||||
size_t cl_start_idx = &cl_start - &start_lines.front();
|
||||
size_t cl_end_idx = &cl_end - &end_lines.front();
|
||||
return std::make_pair(cl_start_idx, cl_end_idx);
|
||||
}
|
||||
return std::make_pair(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max());
|
||||
};
|
||||
|
||||
// If the existing two lines within the search radius start and end point belong to the same boundary,
|
||||
// discard all intersection points because the whole detour could be on one boundary.
|
||||
if (!start_lines.empty() && !end_lines.empty()) {
|
||||
std::pair<size_t, size_t> cl_indices = endpoints_close_to_same_boundary();
|
||||
if (cl_indices.first != std::numeric_limits<size_t>::max()) {
|
||||
assert(cl_indices.second != std::numeric_limits<size_t>::max());
|
||||
const ClosestLine &cl_start = start_lines[cl_indices.first];
|
||||
const ClosestLine &cl_end = end_lines[cl_indices.second];
|
||||
std::vector<Intersection> new_intersections;
|
||||
new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
|
||||
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
|
||||
return new_intersections;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns ClosestLine which is closer to the point "close_to" then point inside passed Intersection.
|
||||
auto get_closer = [&search_radius](const std::vector<ClosestLine> &closest_lines, const Intersection &intersection,
|
||||
const Point &close_to) -> size_t {
|
||||
for (const ClosestLine &cl : closest_lines) {
|
||||
double old_dist = (close_to - intersection.point).cast<float>().squaredNorm();
|
||||
if (cl.border_idx == intersection.border_idx && old_dist <= (search_radius * search_radius) &&
|
||||
(close_to - cl.point).cast<float>().squaredNorm() < old_dist)
|
||||
return &cl - &closest_lines.front();
|
||||
}
|
||||
return std::numeric_limits<size_t>::max();
|
||||
};
|
||||
|
||||
// Try to find ClosestLine with same boundary_idx as any existing Intersection
|
||||
auto find_closest_line_with_same_boundary_idx = [](const std::vector<ClosestLine> & closest_lines,
|
||||
const std::vector<Intersection> &intersections, const bool reverse) -> size_t {
|
||||
std::unordered_set<size_t> boundaries_indices;
|
||||
for (const ClosestLine &closest_line : closest_lines)
|
||||
boundaries_indices.insert(closest_line.border_idx);
|
||||
|
||||
// This function must be called only in the case that exists closest_line with boundary_idx equals to intersection.border_idx
|
||||
auto find_closest_line_index = [&closest_lines](const Intersection &intersection) -> size_t {
|
||||
for (const ClosestLine &closest_line : closest_lines)
|
||||
if (closest_line.border_idx == intersection.border_idx) return &closest_line - &closest_lines.front();
|
||||
// This is an invalid state.
|
||||
assert(false);
|
||||
return std::numeric_limits<size_t>::max();
|
||||
};
|
||||
|
||||
if (reverse) {
|
||||
for (const Intersection &intersection : boost::adaptors::reverse(intersections))
|
||||
if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end())
|
||||
return find_closest_line_index(intersection);
|
||||
} else {
|
||||
for (const Intersection &intersection : intersections)
|
||||
if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end())
|
||||
return find_closest_line_index(intersection);
|
||||
}
|
||||
return std::numeric_limits<size_t>::max();
|
||||
};
|
||||
|
||||
std::vector<Intersection> new_intersections = intersections;
|
||||
if (!intersections.empty() && !start_lines.empty()) {
|
||||
size_t cl_start_idx = get_closer(start_lines, new_intersections.front(), start);
|
||||
if (cl_start_idx != std::numeric_limits<size_t>::max()) {
|
||||
// If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine.
|
||||
const ClosestLine &cl_start = start_lines[cl_start_idx];
|
||||
new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)};
|
||||
} else {
|
||||
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
|
||||
// vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which
|
||||
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
|
||||
// use the first one, which is the closest one to the start point.
|
||||
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, intersections, true);
|
||||
const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits<size_t>::max()) ? start_lines[start_closest_lines_idx] : start_lines.front();
|
||||
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
|
||||
}
|
||||
} else if (!intersections.empty() && !end_lines.empty()) {
|
||||
size_t cl_end_idx = get_closer(end_lines, new_intersections.back(), end);
|
||||
if (cl_end_idx != std::numeric_limits<size_t>::max()) {
|
||||
// If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine.
|
||||
const ClosestLine &cl_end = end_lines[cl_end_idx];
|
||||
new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)};
|
||||
} else {
|
||||
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
|
||||
// vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which
|
||||
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
|
||||
// use the first one, which is the closest one to the end point.
|
||||
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, intersections, false);
|
||||
const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits<size_t>::max()) ? end_lines[end_closest_lines_idx] : end_lines.front();
|
||||
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
|
||||
}
|
||||
}
|
||||
return new_intersections;
|
||||
}
|
||||
|
||||
// point_idx is the index from which is different vertex is searched.
|
||||
template<bool forward>
|
||||
static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point)
|
||||
@ -268,10 +453,63 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
|
||||
return simplified_path;
|
||||
}
|
||||
|
||||
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
|
||||
static inline float get_default_perimeter_spacing(const PrintObject &print_object)
|
||||
{
|
||||
std::vector<unsigned int> printing_extruders = print_object.object_extruders();
|
||||
assert(!printing_extruders.empty());
|
||||
float avg_extruder = 0;
|
||||
for(unsigned int extruder_id : printing_extruders)
|
||||
avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)));
|
||||
avg_extruder /= printing_extruders.size();
|
||||
return avg_extruder;
|
||||
}
|
||||
|
||||
// called by get_boundary() / avoid_perimeters_inner()
|
||||
static float get_perimeter_spacing(const Layer &layer)
|
||||
{
|
||||
size_t regions_count = 0;
|
||||
float perimeter_spacing = 0.f;
|
||||
for (const LayerRegion *layer_region : layer.regions())
|
||||
if (layer_region != nullptr && !layer_region->slices.empty()) {
|
||||
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
|
||||
++regions_count;
|
||||
}
|
||||
|
||||
assert(perimeter_spacing >= 0.f);
|
||||
if (regions_count != 0)
|
||||
perimeter_spacing /= float(regions_count);
|
||||
else
|
||||
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
|
||||
return perimeter_spacing;
|
||||
}
|
||||
|
||||
// called by get_boundary_external()
|
||||
static float get_perimeter_spacing_external(const Layer &layer)
|
||||
{
|
||||
size_t regions_count = 0;
|
||||
float perimeter_spacing = 0.f;
|
||||
for (const PrintObject *object : layer.object()->print()->objects())
|
||||
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
|
||||
for (const LayerRegion *layer_region : l->regions())
|
||||
if (layer_region != nullptr && !layer_region->slices.empty()) {
|
||||
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
|
||||
++ regions_count;
|
||||
}
|
||||
|
||||
assert(perimeter_spacing >= 0.f);
|
||||
if (regions_count != 0)
|
||||
perimeter_spacing /= float(regions_count);
|
||||
else
|
||||
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
|
||||
return perimeter_spacing;
|
||||
}
|
||||
|
||||
// Called by avoid_perimeters() and by simplify_travel_heuristics().
|
||||
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
const Layer &layer,
|
||||
std::vector<TravelPoint> &result_out)
|
||||
{
|
||||
const Polygons &boundaries = boundary.boundaries;
|
||||
@ -288,23 +526,31 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
|
||||
intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin;
|
||||
}
|
||||
std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast<double>().dot(dir) > 0.; });
|
||||
|
||||
// Search radius should always be at least equals to the value of offset used for computing boundaries.
|
||||
const float search_radius = 2.f * get_perimeter_spacing(layer);
|
||||
// When the offset is too big, then original travel doesn't have to cross created boundaries.
|
||||
// These cases are fixed by calling extend_for_closest_lines.
|
||||
intersections = extend_for_closest_lines(intersections, boundary, start, end, search_radius);
|
||||
}
|
||||
|
||||
std::vector<TravelPoint> result;
|
||||
result.push_back({start, -1});
|
||||
|
||||
#if 0
|
||||
auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) {
|
||||
const Polygon &poly = boundary.boundaries[intersection.border_idx];
|
||||
Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast<double>();
|
||||
Vec2d intersection_vec = (intersection.point - start).cast<double>();
|
||||
return poly_line.normalized().dot(intersection_vec.normalized()) >= 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) {
|
||||
// The entry point to the boundary polygon
|
||||
const Intersection &intersection_first = *it_first;
|
||||
if(!crossing_boundary_from_inside(start, intersection_first))
|
||||
continue;
|
||||
// if(!crossing_boundary_from_inside(start, intersection_first))
|
||||
// continue;
|
||||
// Skip the it_first from the search for the farthest exit point from the boundary polygon
|
||||
auto it_last_item = std::make_reverse_iterator(it_first) - 1;
|
||||
// Search for the farthest intersection different from it_first but with the same border_idx
|
||||
@ -353,8 +599,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
|
||||
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_travel_to_svg(boundaries, Line(start, end), result, intersections,
|
||||
debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++));
|
||||
export_travel_to_svg(boundaries, Line(start, end), result, intersections, debug_out_path("AvoidCrossingPerimetersInner-initial-%d-%d.svg", layer.id(), iRun++));
|
||||
}
|
||||
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
||||
|
||||
@ -365,7 +610,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_travel_to_svg(boundaries, Line(start, end), result, intersections,
|
||||
debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++));
|
||||
debug_out_path("AvoidCrossingPerimetersInner-final-%d-%d.svg", layer.id(), iRun++));
|
||||
}
|
||||
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
||||
|
||||
@ -377,17 +622,18 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
|
||||
static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
const Layer &layer,
|
||||
Polyline &result_out)
|
||||
{
|
||||
// Travel line is completely or partially inside the bounding box.
|
||||
std::vector<TravelPoint> path;
|
||||
size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path);
|
||||
size_t num_intersections = avoid_perimeters_inner(boundary, start, end, layer, path);
|
||||
result_out = to_polyline(path);
|
||||
|
||||
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++));
|
||||
export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d-%d.svg", layer.id(), iRun ++));
|
||||
}
|
||||
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
||||
|
||||
@ -482,58 +728,6 @@ static bool need_wipe(const GCode &gcodegen,
|
||||
return wipe_needed;
|
||||
}
|
||||
|
||||
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
|
||||
static inline float get_default_perimeter_spacing(const PrintObject &print_object)
|
||||
{
|
||||
std::vector<unsigned int> printing_extruders = print_object.object_extruders();
|
||||
assert(!printing_extruders.empty());
|
||||
float avg_extruder = 0;
|
||||
for(unsigned int extruder_id : printing_extruders)
|
||||
avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)));
|
||||
avg_extruder /= printing_extruders.size();
|
||||
return avg_extruder;
|
||||
}
|
||||
|
||||
// called by get_boundary()
|
||||
static float get_perimeter_spacing(const Layer &layer)
|
||||
{
|
||||
size_t regions_count = 0;
|
||||
float perimeter_spacing = 0.f;
|
||||
for (const LayerRegion *layer_region : layer.regions())
|
||||
if (layer_region != nullptr && !layer_region->slices.empty()) {
|
||||
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
|
||||
++regions_count;
|
||||
}
|
||||
|
||||
assert(perimeter_spacing >= 0.f);
|
||||
if (regions_count != 0)
|
||||
perimeter_spacing /= float(regions_count);
|
||||
else
|
||||
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
|
||||
return perimeter_spacing;
|
||||
}
|
||||
|
||||
// called by get_boundary_external()
|
||||
static float get_perimeter_spacing_external(const Layer &layer)
|
||||
{
|
||||
size_t regions_count = 0;
|
||||
float perimeter_spacing = 0.f;
|
||||
for (const PrintObject *object : layer.object()->print()->objects())
|
||||
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
|
||||
for (const LayerRegion *layer_region : l->regions())
|
||||
if (layer_region != nullptr && !layer_region->slices.empty()) {
|
||||
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
|
||||
++ regions_count;
|
||||
}
|
||||
|
||||
assert(perimeter_spacing >= 0.f);
|
||||
if (regions_count != 0)
|
||||
perimeter_spacing /= float(regions_count);
|
||||
else
|
||||
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
|
||||
return perimeter_spacing;
|
||||
}
|
||||
|
||||
// Adds points around all vertices so that the offset affects only small sections around these vertices.
|
||||
static void resample_polygon(Polygon &polygon, double dist_from_vertex)
|
||||
{
|
||||
@ -795,14 +989,14 @@ static ExPolygons get_boundary(const Layer &layer)
|
||||
const float perimeter_spacing = get_perimeter_spacing(layer);
|
||||
const float perimeter_offset = perimeter_spacing / 2.f;
|
||||
auto const *support_layer = dynamic_cast<const SupportLayer *>(&layer);
|
||||
ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset));
|
||||
ExPolygons boundary = union_ex(inner_offset(layer.lslices, 1.5 * perimeter_spacing));
|
||||
if(support_layer) {
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset));
|
||||
append(boundary, inner_offset(support_layer->support_islands.expolygons, 1.5 * perimeter_spacing));
|
||||
#endif
|
||||
auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON);
|
||||
if (layer_below)
|
||||
append(boundary, inner_offset(layer_below->lslices, perimeter_offset));
|
||||
append(boundary, inner_offset(layer_below->lslices, 1.5 * perimeter_spacing));
|
||||
// After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons
|
||||
boundary = union_ex(boundary);
|
||||
}
|
||||
@ -868,7 +1062,7 @@ static Polygons get_boundary_external(const Layer &layer)
|
||||
}
|
||||
|
||||
// Used offset_ex for cases when another object will be in the hole of another polygon
|
||||
boundary = to_polygons(offset_ex(boundary, perimeter_offset));
|
||||
boundary = expand(boundary, perimeter_offset);
|
||||
// Reverse all polygons for making normals point from the polygon out.
|
||||
for (Polygon &poly : boundary)
|
||||
poly.reverse();
|
||||
@ -925,7 +1119,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
||||
|
||||
// Trim the travel line by the bounding box.
|
||||
if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) {
|
||||
travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl);
|
||||
travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), *gcodegen.layer(), result_pl);
|
||||
result_pl.points.front() = start;
|
||||
result_pl.points.back() = end;
|
||||
}
|
||||
@ -936,7 +1130,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
||||
|
||||
// Trim the travel line by the bounding box.
|
||||
if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) {
|
||||
travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl);
|
||||
travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), *gcodegen.layer(), result_pl);
|
||||
result_pl.points.front() = start;
|
||||
result_pl.points.back() = end;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ void CoolingBuffer::reset(const Vec3d &position)
|
||||
m_current_pos[1] = float(position.y());
|
||||
m_current_pos[2] = float(position.z());
|
||||
m_current_pos[4] = float(m_config.travel_speed.value);
|
||||
m_fan_speed = -1;
|
||||
}
|
||||
|
||||
struct CoolingLine
|
||||
@ -689,10 +690,9 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
// Second generate the adjusted G-code.
|
||||
std::string new_gcode;
|
||||
new_gcode.reserve(gcode.size() * 2);
|
||||
int fan_speed = -1;
|
||||
bool bridge_fan_control = false;
|
||||
int bridge_fan_speed = 0;
|
||||
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
|
||||
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() {
|
||||
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
|
||||
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
|
||||
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
|
||||
@ -733,9 +733,9 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
bridge_fan_speed = 0;
|
||||
fan_speed_new = 0;
|
||||
}
|
||||
if (fan_speed_new != fan_speed) {
|
||||
fan_speed = fan_speed_new;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed);
|
||||
if (fan_speed_new != m_fan_speed) {
|
||||
m_fan_speed = fan_speed_new;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed);
|
||||
}
|
||||
};
|
||||
|
||||
@ -759,7 +759,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed);
|
||||
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) {
|
||||
if (bridge_fan_control)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed);
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed);
|
||||
} else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
|
||||
// Just remove this comment.
|
||||
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
|
||||
|
@ -41,6 +41,8 @@ private:
|
||||
// X,Y,Z,E,F
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
// Cached from GCodeWriter.
|
||||
// Printing extruder IDs, zero based.
|
||||
std::vector<unsigned int> m_extruder_ids;
|
||||
|
@ -1267,7 +1267,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
||||
cancel_callback();
|
||||
}
|
||||
this->process_gcode_line(line, true);
|
||||
});
|
||||
}, m_result.lines_ends);
|
||||
|
||||
// Don't post-process the G-code to update time stamps.
|
||||
this->finalize(false);
|
||||
|
@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print)
|
||||
std::vector<float> deltas(input.points.size(), offset);
|
||||
input.make_counter_clockwise();
|
||||
out.front() = mittered_offset_path_scaled(input.points, deltas, 3.);
|
||||
return ClipperPaths_to_Slic3rExPolygons(out);
|
||||
return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union
|
||||
};
|
||||
|
||||
|
||||
|
@ -152,7 +152,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine
|
||||
auto it_end = it;
|
||||
for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end)
|
||||
if (*it_end == '\n')
|
||||
line_end_callback((it_end - buffer.begin()) + 1);
|
||||
line_end_callback(file_pos + (it_end - buffer.begin()) + 1);
|
||||
// End of line is indicated also if end of file was reached.
|
||||
eol |= eof && it_end == it_bufend;
|
||||
if (eol) {
|
||||
@ -173,7 +173,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine
|
||||
if (it != it_bufend && *it == '\r')
|
||||
++ it;
|
||||
if (it != it_bufend && *it == '\n') {
|
||||
line_end_callback((it - buffer.begin()) + 1);
|
||||
line_end_callback(file_pos + (it - buffer.begin()) + 1);
|
||||
++ it;
|
||||
}
|
||||
}
|
||||
|
@ -188,17 +188,23 @@ public:
|
||||
// Extrusion paths for the support base and for the support interface and contacts.
|
||||
ExtrusionEntityCollection support_fills;
|
||||
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
virtual bool has_extrusions() const { return ! support_fills.empty(); }
|
||||
|
||||
// Zero based index of an interface layer, used for alternating direction of interface / contact layers.
|
||||
size_t interface_id() const { return m_interface_id; }
|
||||
|
||||
protected:
|
||||
friend class PrintObject;
|
||||
|
||||
// The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower
|
||||
// between the raft and the object first layer.
|
||||
SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
|
||||
Layer(id, object, height, print_z, slice_z) {}
|
||||
SupportLayer(size_t id, size_t interface_id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
|
||||
Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id) {}
|
||||
virtual ~SupportLayer() = default;
|
||||
|
||||
size_t m_interface_id;
|
||||
};
|
||||
|
||||
template<typename LayerContainer>
|
||||
|
@ -431,9 +431,8 @@ void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_comp
|
||||
for (const Surface &surface : this->slices.surfaces)
|
||||
assert(surface.surface_type == stInternal);
|
||||
#endif /* NDEBUG */
|
||||
ExPolygons surfaces = to_expolygons(std::move(this->slices.surfaces));
|
||||
Polygons tmp = intersection(surfaces, trimming_polygons);
|
||||
append(tmp, diff(surfaces, offset(offset_ex(surfaces, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step)));
|
||||
Polygons tmp = intersection(this->slices.surfaces, trimming_polygons);
|
||||
append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step)));
|
||||
this->slices.set(union_ex(tmp), stInternal);
|
||||
}
|
||||
|
||||
|
@ -40,23 +40,42 @@ template<class L> auto get_b(L &&l) { return Traits<remove_cvref_t<L>>::get_b(l)
|
||||
|
||||
// Distance to the closest point of line.
|
||||
template<class L>
|
||||
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point)
|
||||
{
|
||||
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
if (l2 == 0.0)
|
||||
if (l2 == 0.0) {
|
||||
// a == b case
|
||||
*nearest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
}
|
||||
// Consider the line extending the segment, parameterized as a + t (b - a).
|
||||
// We find projection of this point onto the line.
|
||||
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
|
||||
const double t = va.dot(v) / l2;
|
||||
if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment
|
||||
else if (t > 1.0) return (point - get_b(line)).template cast<double>().squaredNorm(); // beyond the 'b' end of the segment
|
||||
if (t < 0.0) {
|
||||
// beyond the 'a' end of the segment
|
||||
*nearest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
} else if (t > 1.0) {
|
||||
// beyond the 'b' end of the segment
|
||||
*nearest_point = get_b(line);
|
||||
return (point - get_b(line)).template cast<double>().squaredNorm();
|
||||
}
|
||||
|
||||
*nearest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
|
||||
return (t * v - va).squaredNorm();
|
||||
}
|
||||
|
||||
// Distance to the closest point of line.
|
||||
template<class L>
|
||||
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
Vec<Dim<L>, Scalar<L>> nearest_point;
|
||||
return distance_to_squared<L>(line, point, &nearest_point);
|
||||
}
|
||||
|
||||
template<class L>
|
||||
double distance_to(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
@ -81,6 +100,7 @@ public:
|
||||
bool intersection_infinite(const Line &other, Point* point) const;
|
||||
bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; }
|
||||
double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); }
|
||||
double distance_to_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); }
|
||||
double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); }
|
||||
double perp_distance_to(const Point &point) const;
|
||||
bool parallel_to(double angle) const;
|
||||
|
@ -159,8 +159,9 @@ template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
int i = 0;
|
||||
Vec3i facet;
|
||||
for (auto v : vtc) {
|
||||
if (i > 2) { i = 0; break; }
|
||||
facet(i++) = v;
|
||||
int iv = v;
|
||||
if (i > 2 || iv < 0 || iv >= int(cgalmesh.vertices().size())) { i = 0; break; }
|
||||
facet(i++) = iv;
|
||||
}
|
||||
|
||||
if (i == 3)
|
||||
|
@ -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 (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
|
||||
// Clean up thin projections. They are not printable anyways.
|
||||
top_ex = offset2_ex(top_ex, - stat.small_region_threshold, + stat.small_region_threshold);
|
||||
top_ex = opening_ex(top_ex, stat.small_region_threshold);
|
||||
if (! top_ex.empty()) {
|
||||
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
|
||||
float offset = 0.f;
|
||||
@ -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) {
|
||||
offset -= stat.extrusion_width;
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = offset2_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)),
|
||||
- stat.small_region_threshold, + stat.small_region_threshold);
|
||||
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
@ -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 (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
|
||||
// Clean up thin projections. They are not printable anyways.
|
||||
bottom_ex = offset2_ex(bottom_ex, - stat.small_region_threshold, + stat.small_region_threshold);
|
||||
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
|
||||
if (! bottom_ex.empty()) {
|
||||
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
|
||||
float offset = 0.f;
|
||||
@ -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) {
|
||||
offset -= stat.extrusion_width;
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = offset2_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)),
|
||||
- stat.small_region_threshold, + stat.small_region_threshold);
|
||||
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
|
@ -347,10 +347,10 @@ void PerimeterGenerator::process()
|
||||
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
||||
// (actually, something larger than that still may exist due to mitering or other causes)
|
||||
coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3));
|
||||
ExPolygons expp = offset2_ex(
|
||||
ExPolygons expp = opening_ex(
|
||||
// medial axis requires non-overlapping geometry
|
||||
diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)),
|
||||
- float(min_width / 2.), float(min_width / 2.));
|
||||
float(min_width / 2.));
|
||||
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||
for (ExPolygon &ex : expp)
|
||||
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
|
||||
@ -495,7 +495,7 @@ void PerimeterGenerator::process()
|
||||
double max = 2. * perimeter_spacing;
|
||||
ExPolygons gaps_ex = diff_ex(
|
||||
//FIXME offset2 would be enough and cheaper.
|
||||
offset2_ex(gaps, - float(min / 2.), float(min / 2.)),
|
||||
opening_ex(gaps, float(min / 2.)),
|
||||
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
|
||||
ThickPolylines polylines;
|
||||
for (const ExPolygon &ex : gaps_ex)
|
||||
|
@ -1139,7 +1139,7 @@ void Print::_make_wipe_tower()
|
||||
// Insert the new support layer.
|
||||
double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z);
|
||||
//FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
|
||||
it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height);
|
||||
it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height);
|
||||
++ it_layer;
|
||||
}
|
||||
}
|
||||
|
@ -300,8 +300,8 @@ public:
|
||||
size_t support_layer_count() const { return m_support_layers.size(); }
|
||||
void clear_support_layers();
|
||||
SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; }
|
||||
SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z);
|
||||
SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z);
|
||||
SupportLayer* add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z);
|
||||
SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z);
|
||||
void delete_support_layer(int idx);
|
||||
|
||||
// Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters.
|
||||
|
@ -1853,7 +1853,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters "
|
||||
"(and thus any ooze will be probably invisible).");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("ooze_prevention", coBool);
|
||||
def->label = L("Enable");
|
||||
|
@ -461,15 +461,15 @@ void PrintObject::clear_support_layers()
|
||||
m_support_layers.clear();
|
||||
}
|
||||
|
||||
SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z)
|
||||
SupportLayer* PrintObject::add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z)
|
||||
{
|
||||
m_support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1));
|
||||
m_support_layers.emplace_back(new SupportLayer(id, interface_id, this, height, print_z, -1));
|
||||
return m_support_layers.back();
|
||||
}
|
||||
|
||||
SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z)
|
||||
SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z)
|
||||
{
|
||||
return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z));
|
||||
return m_support_layers.insert(pos, new SupportLayer(id, interface_id, this, height, print_z, slice_z));
|
||||
}
|
||||
|
||||
// Called by Print::apply().
|
||||
@ -774,7 +774,7 @@ void PrintObject::detect_surfaces_type()
|
||||
ExPolygons upper_slices = interface_shells ?
|
||||
diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
|
||||
diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
|
||||
surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop);
|
||||
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
|
||||
} else {
|
||||
// if no upper layer, all surfaces of this one are solid
|
||||
// we clone surfaces because we're going to clear the slices collection
|
||||
@ -792,15 +792,15 @@ void PrintObject::detect_surfaces_type()
|
||||
to_polygons(lower_layer->get_region(region_id)->slices.surfaces) :
|
||||
to_polygons(lower_layer->slices);
|
||||
surfaces_append(bottom,
|
||||
offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset),
|
||||
opening_ex(diff(layerm->slices.surfaces, lower_slices, true), offset),
|
||||
surface_type_bottom_other);
|
||||
#else
|
||||
// Any surface lying on the void is a true bottom bridge (an overhang)
|
||||
surfaces_append(
|
||||
bottom,
|
||||
offset2_ex(
|
||||
opening_ex(
|
||||
diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
|
||||
-offset, offset),
|
||||
offset),
|
||||
surface_type_bottom_other);
|
||||
// if user requested internal shells, we need to identify surfaces
|
||||
// lying on other slices not belonging to this region
|
||||
@ -809,12 +809,12 @@ void PrintObject::detect_surfaces_type()
|
||||
// on something else, excluding those lying on our own region
|
||||
surfaces_append(
|
||||
bottom,
|
||||
offset2_ex(
|
||||
opening_ex(
|
||||
diff_ex(
|
||||
intersection(layerm->slices.surfaces, lower_layer->lslices), // supported
|
||||
lower_layer->m_regions[region_id]->slices.surfaces,
|
||||
ApplySafetyOffset::Yes),
|
||||
-offset, offset),
|
||||
offset),
|
||||
stBottom);
|
||||
}
|
||||
#endif
|
||||
@ -1088,7 +1088,7 @@ void PrintObject::discover_vertical_shells()
|
||||
// For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
|
||||
if (perimeter_offset > 0.) {
|
||||
// The layer.lslices are forced to merge by expanding them first.
|
||||
polygons_append(cache.holes, offset(offset_ex(layer.lslices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing));
|
||||
polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing));
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices));
|
||||
@ -1325,7 +1325,7 @@ void PrintObject::discover_vertical_shells()
|
||||
#if 1
|
||||
// Intentionally inflate a bit more than how much the region has been shrunk,
|
||||
// so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill).
|
||||
shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
|
||||
shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
|
||||
if (shell.empty())
|
||||
continue;
|
||||
#else
|
||||
@ -1337,7 +1337,7 @@ void PrintObject::discover_vertical_shells()
|
||||
// get a triangle in $too_narrow; if we grow it below then the shell
|
||||
// would have a different shape from the external surface and we'd still
|
||||
// have the same angle, so the next shell would be grown even more and so on.
|
||||
Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true);
|
||||
Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true);
|
||||
if (! too_narrow.empty()) {
|
||||
// grow the collapsing parts and add the extra area to the neighbor layer
|
||||
// as well as to our original surfaces so that we support this
|
||||
@ -1453,7 +1453,7 @@ void PrintObject::bridge_over_infill()
|
||||
// The gaps will be filled by a separate region, which makes the infill less stable and it takes longer.
|
||||
{
|
||||
float min_width = float(bridge_flow.scaled_width()) * 3.f;
|
||||
to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width);
|
||||
to_bridge_pp = opening(to_bridge_pp, min_width);
|
||||
}
|
||||
|
||||
if (to_bridge_pp.empty()) continue;
|
||||
@ -1744,7 +1744,7 @@ void PrintObject::clip_fill_surfaces()
|
||||
for (const LayerRegion *layerm : layer->m_regions)
|
||||
pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width());
|
||||
// Append such thick perimeters to the areas that need support
|
||||
polygons_append(overhangs, offset2(perimeters, -pw, +pw));
|
||||
polygons_append(overhangs, opening(perimeters, pw));
|
||||
}
|
||||
// Find new internal infill.
|
||||
polygons_append(overhangs, std::move(upper_internal));
|
||||
@ -1884,7 +1884,7 @@ void PrintObject::discover_horizontal_shells()
|
||||
float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
|
||||
Polygons too_narrow = diff(
|
||||
new_internal_solid,
|
||||
offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, jtMiter, 5));
|
||||
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5));
|
||||
// Trim the regularized region by the original region.
|
||||
if (! too_narrow.empty())
|
||||
new_internal_solid = solid = diff(new_internal_solid, too_narrow);
|
||||
@ -1903,7 +1903,7 @@ void PrintObject::discover_horizontal_shells()
|
||||
// have the same angle, so the next shell would be grown even more and so on.
|
||||
Polygons too_narrow = diff(
|
||||
new_internal_solid,
|
||||
offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5));
|
||||
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5));
|
||||
if (! too_narrow.empty()) {
|
||||
// grow the collapsing parts and add the extra area to the neighbor layer
|
||||
// as well as to our original surfaces so that we support this
|
||||
@ -1915,7 +1915,7 @@ void PrintObject::discover_horizontal_shells()
|
||||
polygons_append(internal, to_polygons(surface.expolygon));
|
||||
polygons_append(new_internal_solid,
|
||||
intersection(
|
||||
offset(too_narrow, +margin),
|
||||
expand(too_narrow, +margin),
|
||||
// Discard bridges as they are grown for anchoring and we can't
|
||||
// remove such anchors. (This may happen when a bridge is being
|
||||
// anchored onto a wall where little space remains after the bridge
|
||||
|
@ -393,7 +393,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
||||
}
|
||||
}
|
||||
if (merged)
|
||||
expolygons = offset2_ex(expolygons, float(scale_(EPSILON)), -float(scale_(EPSILON)));
|
||||
expolygons = closing_ex(expolygons, float(scale_(EPSILON)));
|
||||
slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
|
||||
i = j;
|
||||
}
|
||||
@ -648,7 +648,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
|
||||
ByRegion &src = by_region[region_id];
|
||||
if (src.needs_merge)
|
||||
// Multiple regions were merged into one.
|
||||
src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON)));
|
||||
src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
|
||||
layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
|
||||
}
|
||||
}
|
||||
|
@ -186,8 +186,8 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
||||
// Produce 2 bands around the island, a safe band for dangling overhangs
|
||||
// and an unsafe band for sloped overhangs.
|
||||
// These masks include the original island
|
||||
auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
|
||||
auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare);
|
||||
auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
|
||||
auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare);
|
||||
|
||||
// Absolutely hopeless overhangs are those outside the unsafe band
|
||||
top.overhangs = diff_ex(*top.polygon, overh_mask);
|
||||
|
@ -367,6 +367,29 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
|
||||
// Object is printed with the same extruder as the support.
|
||||
m_support_params.can_merge_support_regions = true;
|
||||
}
|
||||
|
||||
|
||||
m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value));
|
||||
m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.));
|
||||
m_support_params.interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing();
|
||||
m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing);
|
||||
m_support_params.support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing();
|
||||
m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing);
|
||||
if (m_object_config->support_material_interface_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
m_support_params.interface_spacing = m_support_params.support_spacing;
|
||||
m_support_params.interface_density = m_support_params.support_density;
|
||||
}
|
||||
|
||||
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
|
||||
m_support_params.with_sheath = m_object_config->support_material_with_sheath;
|
||||
m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (m_support_params.support_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
m_support_params.contact_fill_pattern =
|
||||
(m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
|
||||
m_object_config->support_material_interface_pattern == smipConcentric ?
|
||||
ipConcentric :
|
||||
(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
}
|
||||
|
||||
// Using the std::deque as an allocator.
|
||||
@ -397,6 +420,11 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr
|
||||
dst.insert(dst.end(), src.begin(), src.end());
|
||||
}
|
||||
|
||||
// Support layer that is covered by some form of dense interface.
|
||||
static constexpr const std::initializer_list<PrintObjectSupportMaterial::SupporLayerType> support_types_interface {
|
||||
PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface
|
||||
};
|
||||
|
||||
void PrintObjectSupportMaterial::generate(PrintObject &object)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
|
||||
@ -546,6 +574,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
|
||||
// Sort the layers lexicographically by a raising print_z and a decreasing height.
|
||||
std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
|
||||
int layer_id = 0;
|
||||
int layer_id_interface = 0;
|
||||
assert(object.support_layers().empty());
|
||||
for (size_t i = 0; i < layers_sorted.size();) {
|
||||
// Find the last layer with roughly the same print_z, find the minimum layer height of all.
|
||||
@ -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 height_min = layers_sorted[i]->height;
|
||||
bool empty = true;
|
||||
// For snug supports, layers where the direction of the support interface shall change are accounted for.
|
||||
size_t num_interfaces = 0;
|
||||
size_t num_top_contacts = 0;
|
||||
double top_contact_bottom_z = 0;
|
||||
for (size_t u = i; u < j; ++u) {
|
||||
MyLayer &layer = *layers_sorted[u];
|
||||
if (! layer.polygons.empty())
|
||||
if (! layer.polygons.empty()) {
|
||||
empty = false;
|
||||
num_interfaces += one_of(layer.layer_type, support_types_interface);
|
||||
if (layer.layer_type == sltTopContact) {
|
||||
++ num_top_contacts;
|
||||
assert(num_top_contacts <= 1);
|
||||
// All top contact layers sharing this print_z shall also share bottom_z.
|
||||
//assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON);
|
||||
top_contact_bottom_z = layer.bottom_z;
|
||||
}
|
||||
}
|
||||
layer.print_z = zavg;
|
||||
height_min = std::min(height_min, layer.height);
|
||||
}
|
||||
if (! empty) {
|
||||
// Here the upper_layer and lower_layer pointers are left to null at the support layers,
|
||||
// as they are never used. These pointers are candidates for removal.
|
||||
object.add_support_layer(layer_id ++, height_min, zavg);
|
||||
bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces;
|
||||
size_t this_layer_id_interface = layer_id_interface;
|
||||
if (this_layer_contacts_only) {
|
||||
// Find a supporting layer for its interface ID.
|
||||
for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it)
|
||||
if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) {
|
||||
// other_layer supports this top contact layer. Assign a different support interface direction to this layer
|
||||
// from the layer that supports it.
|
||||
this_layer_id_interface = other_layer.interface_id() + 1;
|
||||
}
|
||||
}
|
||||
object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg);
|
||||
if (num_interfaces && ! this_layer_contacts_only)
|
||||
++ layer_id_interface;
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
@ -883,7 +938,14 @@ public:
|
||||
// Merge the support polygons by applying morphological closing and inwards smoothing.
|
||||
auto closing_distance = scaled<float>(m_support_material_closing_radius);
|
||||
auto smoothing_distance = scaled<float>(m_extrusion_width);
|
||||
return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance);
|
||||
#ifdef SLIC3R_DEBUG
|
||||
SVG::export_expolygons(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z),
|
||||
{ { { diff_ex(expand(*m_support_polygons, closing_distance), closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "closed", "blue", 0.5f } },
|
||||
{ { union_ex(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance)) }, { "regularized", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } },
|
||||
{ { union_ex(*m_support_polygons) }, { "src", "green", 0.5f } },
|
||||
});
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
return smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance);
|
||||
}
|
||||
assert(false);
|
||||
return Polygons();
|
||||
@ -1250,7 +1312,7 @@ namespace SupportMaterialInternal {
|
||||
Polygons bridges;
|
||||
{
|
||||
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
|
||||
Polygons lower_grown_slices = offset(lower_layer_polygons,
|
||||
Polygons lower_grown_slices = expand(lower_layer_polygons,
|
||||
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
|
||||
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))),
|
||||
SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
@ -1414,7 +1476,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
|
||||
overhang_polygons = to_polygons(layer.lslices);
|
||||
#endif
|
||||
// Expand for better stability.
|
||||
contact_polygons = offset(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
|
||||
contact_polygons = expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
|
||||
}
|
||||
else 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.
|
||||
#if 0
|
||||
//FIXME this solution will trigger stupid supports for sharp corners, see GH #4874
|
||||
diff_polygons = offset2(
|
||||
diff_polygons = opening(
|
||||
diff(layerm_polygons,
|
||||
// Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they
|
||||
// are not supporting this layer.
|
||||
// However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports.
|
||||
// For example, see GH issue #3094
|
||||
offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
|
||||
//FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to
|
||||
opening(lower_layer_polygons, 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
|
||||
//FIXME This opening is targeted to reduce very thin regions to support, but it may lead to
|
||||
// no support at all for not so steep overhangs.
|
||||
- 0.1f * fw, 0.1f * fw);
|
||||
0.1f * fw);
|
||||
#else
|
||||
diff_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
|
||||
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
|
||||
// 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
|
||||
// propagating these contact surfaces downwards.
|
||||
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);
|
||||
}
|
||||
//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
|
||||
// residues of diff_polygons that would then be supported.
|
||||
diff_polygons = diff(diff_polygons,
|
||||
offset(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON)));
|
||||
expand(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON)));
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
@ -1588,7 +1650,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
|
||||
#endif // SLIC3R_DEBUG
|
||||
enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]),
|
||||
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object.
|
||||
offset(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
#ifdef SLIC3R_DEBUG
|
||||
SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
|
||||
{ { layer.lslices, { "layer.lslices", "gray", 0.2f } },
|
||||
@ -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.
|
||||
//FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
|
||||
// and it may actually make sense to do it with a thinner layer than the first layer height.
|
||||
const coordf_t min_print_z = slicing_params.raft_layers() > 1 ? slicing_params.raft_interface_top_z + support_layer_height_min + EPSILON : slicing_params.first_print_layer_height - EPSILON;
|
||||
if (print_z < min_print_z) {
|
||||
if (print_z < slicing_params.first_print_layer_height - EPSILON) {
|
||||
// This contact layer is below the first layer height, therefore not printable. Don't support this surface.
|
||||
return std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupportMaterial::MyLayer*>(nullptr, nullptr);
|
||||
} else if (print_z < slicing_params.first_print_layer_height + EPSILON) {
|
||||
// Align the layer with the 1st layer height.
|
||||
print_z = slicing_params.first_print_layer_height;
|
||||
bottom_z = 0;
|
||||
height = slicing_params.first_print_layer_height;
|
||||
}
|
||||
const bool has_raft = slicing_params.raft_layers() > 1;
|
||||
const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height;
|
||||
if (print_z < min_print_z + support_layer_height_min) {
|
||||
// Align the layer with the 1st layer height or the raft contact layer.
|
||||
// With raft active, any contact layer below the raft_contact_top_z will be brought to raft_contact_top_z to extend the raft area.
|
||||
print_z = min_print_z;
|
||||
bottom_z = has_raft ? slicing_params.raft_interface_top_z : 0;
|
||||
height = has_raft ? slicing_params.contact_raft_layer_height : min_print_z;
|
||||
} else {
|
||||
// Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and
|
||||
// its height will be set adaptively later on.
|
||||
@ -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;
|
||||
if (bridging_print_z >= min_print_z) {
|
||||
// Not below the first layer height means this layer is printable.
|
||||
if (print_z < slicing_params.first_print_layer_height + EPSILON) {
|
||||
// Align the layer with the 1st layer height.
|
||||
bridging_print_z = slicing_params.first_print_layer_height;
|
||||
if (print_z < min_print_z + support_layer_height_min) {
|
||||
// Align the layer with the 1st layer height or the raft contact layer.
|
||||
bridging_print_z = min_print_z;
|
||||
}
|
||||
if (bridging_print_z < print_z - EPSILON) {
|
||||
// Allocate the new layer.
|
||||
@ -1720,7 +1785,7 @@ static inline void fill_contact_layer(
|
||||
if (lower_layer_polygons_for_dense_interface_cache.empty())
|
||||
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.
|
||||
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;
|
||||
};
|
||||
|
||||
@ -1733,7 +1798,7 @@ static inline void fill_contact_layer(
|
||||
#endif // SLIC3R_DEBUG
|
||||
));
|
||||
// 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) {
|
||||
// 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());
|
||||
@ -1741,7 +1806,7 @@ static inline void fill_contact_layer(
|
||||
dense_interface_polygons =
|
||||
diff(
|
||||
// Regularize the contour.
|
||||
offset(dense_interface_polygons, no_interface_offset * 0.1f),
|
||||
expand(dense_interface_polygons, no_interface_offset * 0.1f),
|
||||
slices_margin.polygons);
|
||||
// Support islands, to be stretched into a grid.
|
||||
//FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
|
||||
@ -1799,7 +1864,7 @@ static inline void fill_contact_layer(
|
||||
dense_interface_polygons =
|
||||
diff(
|
||||
// Regularize the contour.
|
||||
offset(dense_interface_polygons, no_interface_offset * 0.1f),
|
||||
expand(dense_interface_polygons, no_interface_offset * 0.1f),
|
||||
slices_margin.all_polygons);
|
||||
// Support islands, to be stretched into a grid.
|
||||
//FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
|
||||
@ -1937,7 +2002,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
||||
);
|
||||
|
||||
// Now apply the contact areas to the layer where they need to be made.
|
||||
if (! contact_polygons.empty()) {
|
||||
if (! contact_polygons.empty() || ! overhang_polygons.empty()) {
|
||||
// Allocate the two empty layers.
|
||||
auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex);
|
||||
if (new_layer) {
|
||||
@ -2041,8 +2106,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
|
||||
layer_new.idx_object_layer_below = layer_id;
|
||||
layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges;
|
||||
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
|
||||
//FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
|
||||
layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
|
||||
if (! slicing_params.soluble_interface) {
|
||||
// Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
|
||||
@ -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.
|
||||
//FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage?
|
||||
touching = offset(touching, float(SCALED_EPSILON));
|
||||
touching = expand(touching, float(SCALED_EPSILON));
|
||||
for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
|
||||
const Layer &layer_above = *object.layers()[layer_id_above];
|
||||
if (layer_above.print_z > layer_new.print_z - EPSILON)
|
||||
@ -2249,7 +2313,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
||||
#endif
|
||||
// These are the overhang surfaces. They are touching the object and they are not expanded away from the object.
|
||||
// Use a slight positive offset to overlap the touching regions.
|
||||
polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON)));
|
||||
polygons_append(polygons_new, expand(*top_contact.overhang_polygons, float(SCALED_EPSILON)));
|
||||
polygons_append(overhangs_projection, union_(polygons_new));
|
||||
polygons_append(enforcers_projection, enforcers_new);
|
||||
}
|
||||
@ -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.
|
||||
// Intermediate layers are always printed with a normal etrusion flow (non-bridging).
|
||||
size_t idx_layer_object = 0;
|
||||
for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) {
|
||||
size_t idx_extreme_first = 0;
|
||||
if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) {
|
||||
// This is a raft contact layer, its height has been decided in this->top_contact_layers().
|
||||
// Ignore this layer when calculating the intermediate support layers.
|
||||
assert(extremes.front()->layer_type == sltTopContact);
|
||||
++ idx_extreme_first;
|
||||
}
|
||||
for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) {
|
||||
MyLayer *extr2 = extremes[idx_extreme];
|
||||
coordf_t extr2z = extr2->extreme_z();
|
||||
if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) {
|
||||
// This is a raft contact layer, its height has been decided in this->top_contact_layers().
|
||||
assert(extr2->layer_type == sltTopContact);
|
||||
continue;
|
||||
}
|
||||
if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) {
|
||||
// This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers().
|
||||
assert(extr2->layer_type == sltTopContact);
|
||||
@ -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.first_print_layer_height + EPSILON);
|
||||
MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1];
|
||||
MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1];
|
||||
// Fuse a support layer firmly to the raft top interface (not to the raft contacts).
|
||||
coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z();
|
||||
assert(extr2z >= extr1z);
|
||||
@ -2736,7 +2802,6 @@ void PrintObjectSupportMaterial::generate_base_layers(
|
||||
ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons
|
||||
layer_intermediate.layer_type = sltBase;
|
||||
|
||||
// For snug supports, expand the interfaces into the intermediate layer to make it printable.
|
||||
#if 0
|
||||
// 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.
|
||||
@ -2868,7 +2933,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
|
||||
if (brim_inner) {
|
||||
Polygons holes = ex.holes;
|
||||
polygons_reverse(holes);
|
||||
holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
polygons_reverse(holes);
|
||||
polygons_append(brim, std::move(holes));
|
||||
} else
|
||||
@ -2900,11 +2965,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
|
||||
|
||||
Polygons interface_polygons;
|
||||
if (contacts != nullptr && ! contacts->polygons.empty())
|
||||
polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
if (interfaces != nullptr && ! interfaces->polygons.empty())
|
||||
polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
if (base_interfaces != nullptr && ! base_interfaces->polygons.empty())
|
||||
polygons_append(interface_polygons, offset(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
|
||||
// Output vector.
|
||||
MyLayersPtr raft_layers;
|
||||
@ -2931,7 +2996,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
|
||||
new_layer.print_z = m_slicing_params.first_print_layer_height;
|
||||
new_layer.height = m_slicing_params.first_print_layer_height;
|
||||
new_layer.bottom_z = 0.;
|
||||
new_layer.polygons = inflate_factor_1st_layer > 0 ? offset(base, inflate_factor_1st_layer) : base;
|
||||
new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base;
|
||||
}
|
||||
// Insert the base layers.
|
||||
for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) {
|
||||
@ -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())));
|
||||
float step = inflate_factor_1st_layer / nsteps;
|
||||
for (int i = 0; i < nsteps; ++ i)
|
||||
raft = diff(offset(raft, step), trimming);
|
||||
raft = diff(expand(raft, step), trimming);
|
||||
} else
|
||||
raft = diff(raft, trimming);
|
||||
if (contacts != nullptr)
|
||||
@ -3028,18 +3093,32 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
|
||||
interface_layers.assign(intermediate_layers.size(), nullptr);
|
||||
if (num_base_interface_layers_top || num_base_interface_layers_bottom)
|
||||
base_interface_layers.assign(intermediate_layers.size(), nullptr);
|
||||
auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5;
|
||||
auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density;
|
||||
auto closing_distance = smoothing_distance; // scaled<float>(m_object_config->support_material_closing_radius.value);
|
||||
tbb::spin_mutex layer_storage_mutex;
|
||||
// Insert a new layer into base_interface_layers, if intersection with base exists.
|
||||
auto insert_layer = [&layer_storage, &layer_storage_mutex](MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) {
|
||||
auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius](
|
||||
MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* {
|
||||
assert(! bottom.empty() || ! top.empty());
|
||||
// Merge top into bottom, unite them with a safety offset.
|
||||
append(bottom, std::move(top));
|
||||
// Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners).
|
||||
bottom = intersection(
|
||||
snug_supports ?
|
||||
smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
|
||||
union_safety_offset(std::move(bottom)),
|
||||
intermediate_layer.polygons);
|
||||
if (! bottom.empty()) {
|
||||
//FIXME Remove non-printable tiny islands, let them be printed using the base support.
|
||||
//bottom = opening(std::move(bottom), minimum_island_radius);
|
||||
if (! bottom.empty()) {
|
||||
MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type);
|
||||
layer_new.polygons = std::move(bottom);
|
||||
layer_new.print_z = intermediate_layer.print_z;
|
||||
layer_new.bottom_z = intermediate_layer.bottom_z;
|
||||
layer_new.height = intermediate_layer.height;
|
||||
layer_new.bridging = intermediate_layer.bridging;
|
||||
// Merge top into bottom, unite them with a safety offset.
|
||||
append(bottom, std::move(top));
|
||||
layer_new.polygons = intersection(union_safety_offset(std::move(bottom)), intermediate_layer.polygons);
|
||||
// Subtract the interface from the base regions.
|
||||
intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons);
|
||||
if (subtract)
|
||||
@ -3048,6 +3127,9 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
|
||||
//FIXME filter layer_new.polygons islands by a minimum area?
|
||||
// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
|
||||
return &layer_new;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
|
||||
[&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer,
|
||||
@ -3196,7 +3278,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
|
||||
return;
|
||||
|
||||
if (! with_sheath) {
|
||||
fill_expolygons_generate_paths(dst, offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)), filler, density, role, flow);
|
||||
fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -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.
|
||||
double clip_length = spacing * 0.15;
|
||||
|
||||
for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) {
|
||||
for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) {
|
||||
// Don't reorder the skirt and its infills.
|
||||
std::unique_ptr<ExtrusionEntityCollection> eec;
|
||||
if (no_sort) {
|
||||
@ -3462,9 +3544,9 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
|
||||
Polygons loop_polygons = loops0;
|
||||
for (int i = 1; i < n_contact_loops; ++ i)
|
||||
polygons_append(loop_polygons,
|
||||
offset2(
|
||||
opening(
|
||||
loops0,
|
||||
- i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(),
|
||||
i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(),
|
||||
0.5f * flow.scaled_spacing()));
|
||||
// Clip such loops to the side oriented towards the object.
|
||||
// Collect split points, so they will be recognized after the clipping.
|
||||
@ -3476,7 +3558,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
|
||||
map_split_points[it->first_point()] = -1;
|
||||
loop_lines.push_back(it->split_at_first_point());
|
||||
}
|
||||
loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)));
|
||||
loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)));
|
||||
// Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces.
|
||||
// Try to connect them.
|
||||
for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) {
|
||||
@ -3816,27 +3898,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width());
|
||||
loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0;
|
||||
|
||||
float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value));
|
||||
float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.));
|
||||
coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing();
|
||||
coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing);
|
||||
coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing();
|
||||
coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing);
|
||||
if (m_object_config->support_material_interface_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
interface_spacing = support_spacing;
|
||||
interface_density = support_density;
|
||||
}
|
||||
|
||||
// Prepare fillers.
|
||||
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
|
||||
bool with_sheath = m_object_config->support_material_with_sheath;
|
||||
InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
std::vector<float> angles;
|
||||
angles.push_back(base_angle);
|
||||
|
||||
if (support_pattern == smpRectilinearGrid)
|
||||
angles.push_back(interface_angle);
|
||||
std::vector<float> angles { m_support_params.base_angle };
|
||||
if (m_object_config->support_material_pattern == smpRectilinearGrid)
|
||||
angles.push_back(m_support_params.interface_angle);
|
||||
|
||||
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
|
||||
|
||||
@ -3848,16 +3912,16 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
float raft_angle_interface = 0.f;
|
||||
if (m_slicing_params.base_raft_layers > 1) {
|
||||
// There are all raft layer types (1st layer, base, interface & contact layers) available.
|
||||
raft_angle_1st_layer = interface_angle;
|
||||
raft_angle_base = base_angle;
|
||||
raft_angle_interface = interface_angle;
|
||||
raft_angle_1st_layer = m_support_params.interface_angle;
|
||||
raft_angle_base = m_support_params.base_angle;
|
||||
raft_angle_interface = m_support_params.interface_angle;
|
||||
} else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) {
|
||||
// 1st layer, interface & contact layers available.
|
||||
raft_angle_1st_layer = base_angle;
|
||||
raft_angle_1st_layer = m_support_params.base_angle;
|
||||
if (this->has_support())
|
||||
// Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer.
|
||||
raft_angle_1st_layer += 0.7854f;
|
||||
raft_angle_interface = interface_angle;
|
||||
raft_angle_interface = m_support_params.interface_angle;
|
||||
} else if (m_slicing_params.interface_raft_layers == 1) {
|
||||
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
|
||||
assert(m_slicing_params.base_raft_layers == 0);
|
||||
@ -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));
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
|
||||
[this, &support_layers, &raft_layers,
|
||||
infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath]
|
||||
&bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor]
|
||||
(const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
|
||||
{
|
||||
@ -3883,8 +3947,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
assert(support_layer.support_fills.entities.empty());
|
||||
MyLayer &raft_layer = *raft_layers[support_layer_id];
|
||||
|
||||
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(ipRectilinear));
|
||||
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
|
||||
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.interface_fill_pattern));
|
||||
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
|
||||
filler_interface->set_bounding_box(bbox_object);
|
||||
filler_support->set_bounding_box(bbox_object);
|
||||
|
||||
@ -3900,17 +3964,17 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
Fill * filler = filler_support.get();
|
||||
filler->angle = raft_angle_base;
|
||||
filler->spacing = m_support_params.support_material_flow.spacing();
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density));
|
||||
fill_expolygons_with_sheath_generate_paths(
|
||||
// Destination
|
||||
support_layer.support_fills.entities,
|
||||
// Regions to fill
|
||||
to_infill_polygons,
|
||||
// Filler and its parameters
|
||||
filler, float(support_density),
|
||||
filler, float(m_support_params.support_density),
|
||||
// Extrusion parameters
|
||||
erSupportMaterial, flow,
|
||||
with_sheath, false);
|
||||
m_support_params.with_sheath, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3929,7 +3993,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
filler->spacing = m_support_params.support_material_flow.spacing();
|
||||
assert(! raft_layer.bridging);
|
||||
flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter());
|
||||
density = float(interface_density);
|
||||
density = float(m_support_params.interface_density);
|
||||
} else
|
||||
continue;
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
|
||||
@ -3970,15 +4034,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
};
|
||||
std::vector<LayerCache> layer_caches(support_layers.size());
|
||||
|
||||
|
||||
const auto fill_type_interface =
|
||||
(m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
|
||||
m_object_config->support_material_interface_pattern == smipConcentric ?
|
||||
ipConcentric : ipRectilinear;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()),
|
||||
[this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor,
|
||||
infill_pattern, &bbox_object, support_density, fill_type_interface, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath]
|
||||
&bbox_object, &angles, link_max_length_factor]
|
||||
(const tbb::blocked_range<size_t>& range) {
|
||||
// Indices of the 1st layer in their respective container at the support layer height.
|
||||
size_t idx_layer_bottom_contact = size_t(-1);
|
||||
@ -3987,14 +4045,14 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
size_t idx_layer_interface = size_t(-1);
|
||||
size_t idx_layer_base_interface = size_t(-1);
|
||||
const auto fill_type_first_layer = ipRectilinear;
|
||||
auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(fill_type_interface));
|
||||
auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
|
||||
// Filler for the 1st layer interface, if different from filler_interface.
|
||||
auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && fill_type_interface != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr);
|
||||
auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr);
|
||||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
|
||||
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
|
||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear));
|
||||
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
|
||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase));
|
||||
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
|
||||
filler_interface->set_bounding_box(bbox_object);
|
||||
if (filler_first_layer_ptr)
|
||||
filler_first_layer_ptr->set_bounding_box(bbox_object);
|
||||
@ -4005,6 +4063,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
{
|
||||
SupportLayer &support_layer = *support_layers[support_layer_id];
|
||||
LayerCache &layer_cache = layer_caches[support_layer_id];
|
||||
float interface_angle_delta = m_object_config->support_material_style.value == smsSnug ?
|
||||
(support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) :
|
||||
0;
|
||||
|
||||
// Find polygons with the same print_z.
|
||||
MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
|
||||
@ -4084,8 +4145,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
// If zero interface layers are configured, use the same angle as for the base layers.
|
||||
angles[support_layer_id % angles.size()] :
|
||||
// Use interface angle for the interface layers.
|
||||
interface_angle;
|
||||
double density = interface_as_base ? support_density : interface_density;
|
||||
m_support_params.interface_angle + interface_angle_delta;
|
||||
double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density;
|
||||
filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing();
|
||||
filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density));
|
||||
fill_expolygons_generate_paths(
|
||||
@ -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)
|
||||
assert(! base_interface_layer.layer->bridging);
|
||||
Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
|
||||
filler->angle = interface_angle;
|
||||
filler->angle = m_support_params.interface_angle + interface_angle_delta;
|
||||
filler->spacing = m_support_params.support_material_interface_flow.spacing();
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density));
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density));
|
||||
fill_expolygons_generate_paths(
|
||||
// Destination
|
||||
base_interface_layer.extrusions,
|
||||
@ -4116,7 +4177,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
// Regions to fill
|
||||
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
|
||||
// Filler and its parameters
|
||||
filler, float(interface_density),
|
||||
filler, float(m_support_params.interface_density),
|
||||
// Extrusion parameters
|
||||
erSupportMaterial, interface_flow);
|
||||
}
|
||||
@ -4130,9 +4191,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
assert(! base_layer.layer->bridging);
|
||||
auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height));
|
||||
filler->spacing = m_support_params.support_material_flow.spacing();
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
|
||||
float density = float(support_density);
|
||||
bool sheath = with_sheath;
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density));
|
||||
float density = float(m_support_params.support_density);
|
||||
bool sheath = m_support_params.with_sheath;
|
||||
bool no_sort = false;
|
||||
if (base_layer.layer->bottom_z < EPSILON) {
|
||||
// Base flange (the 1st layer).
|
||||
|
@ -132,6 +132,18 @@ public:
|
||||
// coordf_t support_layer_height_max;
|
||||
|
||||
coordf_t gap_xy;
|
||||
|
||||
float base_angle;
|
||||
float interface_angle;
|
||||
coordf_t interface_spacing;
|
||||
coordf_t interface_density;
|
||||
coordf_t support_spacing;
|
||||
coordf_t support_density;
|
||||
|
||||
InfillPattern base_fill_pattern;
|
||||
InfillPattern interface_fill_pattern;
|
||||
InfillPattern contact_fill_pattern;
|
||||
bool with_sheath;
|
||||
};
|
||||
|
||||
// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained
|
||||
|
@ -208,7 +208,7 @@ void name_tbb_thread_pool_threads_set_locale()
|
||||
nthreads = 1;
|
||||
#endif
|
||||
|
||||
std::atomic<size_t> nthreads_running(0);
|
||||
size_t nthreads_running(0);
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
auto master_thread_id = std::this_thread::get_id();
|
||||
@ -216,13 +216,13 @@ void name_tbb_thread_pool_threads_set_locale()
|
||||
tbb::blocked_range<size_t>(0, nthreads, 1),
|
||||
[&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) {
|
||||
assert(range.begin() + 1 == range.end());
|
||||
if (nthreads_running.fetch_add(1) + 1 == nthreads) {
|
||||
if (std::unique_lock<std::mutex> lk(cv_m); ++nthreads_running == nthreads) {
|
||||
lk.unlock();
|
||||
// All threads are spinning.
|
||||
// Wake them up.
|
||||
cv.notify_all();
|
||||
} else {
|
||||
// Wait for the last thread to wake the others.
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;});
|
||||
}
|
||||
auto thread_id = std::this_thread::get_id();
|
||||
|
@ -1617,7 +1617,7 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c
|
||||
|
||||
/* The following line is commented out because it can generate wrong polygons,
|
||||
see for example issue #661 */
|
||||
//ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
|
||||
//ExPolygons ex_slices = closing(p_slices, safety_offset);
|
||||
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
size_t holes_count = 0;
|
||||
|
@ -127,7 +127,8 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
|
||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
const Vec3f& source, float radius,
|
||||
CursorType cursor_type, EnforcerBlockerType new_state,
|
||||
const Transform3d& trafo, bool triangle_splitting)
|
||||
const Transform3d& trafo, const Transform3d& trafo_no_translate,
|
||||
bool triangle_splitting, float highlight_by_angle_deg)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
|
||||
@ -143,6 +144,9 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
m_old_cursor_radius_sqr = m_cursor.radius_sqr;
|
||||
}
|
||||
|
||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
|
||||
|
||||
// Now start with the facet the pointer points to and check all adjacent facets.
|
||||
std::vector<int> facets_to_check;
|
||||
facets_to_check.reserve(16);
|
||||
@ -154,21 +158,24 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
int facet_idx = 0;
|
||||
while (facet_idx < int(facets_to_check.size())) {
|
||||
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)) {
|
||||
// add neighboring facets to list to be proccessed later
|
||||
for (int neighbor_idx : m_neighbors[facet]) {
|
||||
// add neighboring facets to list to be processed later
|
||||
for (int neighbor_idx : m_neighbors[facet])
|
||||
if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx)))
|
||||
facets_to_check.push_back(neighbor_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
visited[facet] = true;
|
||||
++facet_idx;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -183,13 +190,16 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
|
||||
facet_queue.push(facet_start);
|
||||
|
||||
const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
|
||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
|
||||
|
||||
// Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor.
|
||||
while (!facet_queue.empty()) {
|
||||
int current_facet = facet_queue.front();
|
||||
facet_queue.pop();
|
||||
|
||||
if (!visited[current_facet]) {
|
||||
const Vec3f &facet_normal = m_face_normals[m_triangles[current_facet].source_triangle];
|
||||
if (!visited[current_facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) {
|
||||
if (m_triangles[current_facet].is_split()) {
|
||||
for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) {
|
||||
assert(split_triangle_idx < int(m_triangles[current_facet].children.size()));
|
||||
|
@ -45,11 +45,15 @@ public:
|
||||
CursorType type, // current type of cursor
|
||||
EnforcerBlockerType new_state, // enforcer or blocker?
|
||||
const Transform3d &trafo, // matrix to get from mesh to world
|
||||
bool triangle_splitting); // If triangles will be split base on the cursor or not
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
bool triangle_splitting, // If triangles will be split base on the cursor or not
|
||||
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
|
||||
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
|
||||
float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||
|
||||
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
|
@ -334,19 +334,7 @@ void GLVolume::SinkingContours::update()
|
||||
MeshSlicingParams slicing_params;
|
||||
slicing_params.trafo = m_parent.world_matrix();
|
||||
Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params));
|
||||
for (Polygon& polygon : polygons) {
|
||||
if (polygon.is_clockwise())
|
||||
polygon.reverse();
|
||||
Polygons outer_polys = offset(polygon, float(scale_(HalfWidth)));
|
||||
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);
|
||||
|
||||
for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) {
|
||||
GUI::GLModel::InitializationData::Entity entity;
|
||||
entity.type = GUI::GLModel::PrimitiveType::Triangles;
|
||||
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
|
||||
|
@ -456,7 +456,7 @@ private:
|
||||
GLGizmosManager m_gizmos;
|
||||
GLToolbar m_main_toolbar;
|
||||
GLToolbar m_undoredo_toolbar;
|
||||
ClippingPlane m_clipping_planes[2];
|
||||
std::array<ClippingPlane, 2> m_clipping_planes;
|
||||
ClippingPlane m_camera_clipping_plane;
|
||||
bool m_use_clipping_planes;
|
||||
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 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 refresh_camera_scene_box();
|
||||
|
@ -61,39 +61,28 @@ std::pair<bool, std::string> GLShadersManager::init()
|
||||
// used to render extrusion and travel paths as lines in gcode preview
|
||||
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
|
||||
// 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 (platform_flavor() == PlatformFlavor::OSXOnArm)
|
||||
valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" }, { "FLIP_TRIANGLE_NORMALS"sv
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
, "ENABLE_ENVIRONMENT_MAP"sv
|
||||
#endif
|
||||
});
|
||||
else
|
||||
// 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" }
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
, { "ENABLE_ENVIRONMENT_MAP"sv }
|
||||
#endif
|
||||
#else
|
||||
if (platform_flavor() == PlatformFlavor::OSXOnArm)
|
||||
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" }
|
||||
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
, { "ENABLE_ENVIRONMENT_MAP"sv }
|
||||
#endif
|
||||
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
);
|
||||
// used to render variable layers heights in 3d editor
|
||||
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
|
||||
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 };
|
||||
}
|
||||
|
@ -676,16 +676,18 @@ void GUI_App::post_init()
|
||||
if (this->preset_updater) {
|
||||
this->check_updates(false);
|
||||
CallAfter([this] {
|
||||
this->config_wizard_startup();
|
||||
bool cw_showed = this->config_wizard_startup();
|
||||
this->preset_updater->slic3r_update_notify();
|
||||
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
|
||||
// Sets window property to mainframe so other instances can indentify it.
|
||||
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"));
|
||||
}
|
||||
|
||||
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;
|
||||
{
|
||||
// the dialog needs to be destroyed before the call to recreate_GUI()
|
||||
// or sometimes the application crashes into wxDialogBase() destructor
|
||||
// so we put it into an inner scope
|
||||
PreferencesDialog dlg(mainframe, open_on_tab);
|
||||
PreferencesDialog dlg(mainframe, open_on_tab, highlight_option);
|
||||
dlg.ShowModal();
|
||||
app_layout_changed = dlg.settings_layout_changed();
|
||||
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
|
||||
|
@ -259,7 +259,7 @@ public:
|
||||
wxString current_language_code_safe() const;
|
||||
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;
|
||||
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
|
||||
|
@ -79,6 +79,8 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/)
|
||||
wxLC_ICON | wxSIMPLE_BORDER);
|
||||
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_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_selected_items.clear();
|
||||
select(event);
|
||||
@ -111,19 +113,11 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/)
|
||||
this->Bind(wxEVT_BUTTON, method, this, ID);
|
||||
};
|
||||
|
||||
auto enable_del_fn = [this]() {
|
||||
if (m_selected_items.empty())
|
||||
return false;
|
||||
for (const Item& item : m_selected_items)
|
||||
if (item.is_system)
|
||||
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);
|
||||
size_t btn_pos = 0;
|
||||
add_btn(btn_pos++, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
|
||||
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(); });
|
||||
//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(); });
|
||||
buttons->InsertStretchSpacer(btn_pos, 2* BORDER_W);
|
||||
|
||||
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)
|
||||
{
|
||||
const int& em = em_unit();
|
||||
@ -405,7 +414,7 @@ void GalleryDialog::add_custom_shapes(wxEvent& event)
|
||||
load_files(input_files);
|
||||
}
|
||||
|
||||
void GalleryDialog::del_custom_shapes(wxEvent& event)
|
||||
void GalleryDialog::del_custom_shapes()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
void GalleryDialog::replace_custom_png(wxEvent& event)
|
||||
void GalleryDialog::change_thumbnail()
|
||||
{
|
||||
if (m_selected_items.size() != 1 || m_selected_items[0].is_system)
|
||||
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; }));
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
m_selected_items.clear();
|
||||
|
@ -33,10 +33,17 @@ class GalleryDialog : public DPIDialog
|
||||
|
||||
void load_label_icon_list();
|
||||
void add_custom_shapes(wxEvent& event);
|
||||
void del_custom_shapes(wxEvent& event);
|
||||
void replace_custom_png(wxEvent& event);
|
||||
void del_custom_shapes();
|
||||
void del_custom_shapes(wxEvent& event) { del_custom_shapes(); }
|
||||
void change_thumbnail();
|
||||
void change_thumbnail(wxEvent& event) { change_thumbnail(); }
|
||||
void select(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();
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
||||
|
||||
@ -20,7 +21,7 @@ namespace Slic3r::GUI {
|
||||
|
||||
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.toggle_model_objects_visibility(true);
|
||||
}
|
||||
@ -52,7 +53,7 @@ bool GLGizmoFdmSupports::on_init()
|
||||
m_desc["circle"] = _L("Circle");
|
||||
m_desc["sphere"] = _L("Sphere");
|
||||
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["cancel"] = _L("Cancel");
|
||||
|
||||
@ -62,6 +63,9 @@ bool GLGizmoFdmSupports::on_init()
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -89,7 +93,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
if (! m_c->selection_info()->model_object())
|
||||
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);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
@ -99,8 +103,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
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);
|
||||
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 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_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_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 total_text_max = 0.f;
|
||||
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);
|
||||
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 window_width = minimal_slider_width + sliders_width;
|
||||
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_left_width;
|
||||
window_width = std::max(window_width, total_text_max);
|
||||
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, 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));
|
||||
@ -144,38 +154,53 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
float position_before_text_y = ImGui::GetCursorPos().y;
|
||||
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();
|
||||
float position_after_text_y = ImGui::GetCursorPos().y;
|
||||
std::string format_str = std::string("%.f") + I18N::translate_utf8("°",
|
||||
"Degree sign to use in the respective slider in FDM supports gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_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);
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width);
|
||||
|
||||
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()) {
|
||||
m_parent.use_slope(true);
|
||||
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::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f));
|
||||
if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) {
|
||||
select_facets_by_angle(m_angle_threshold_deg, false);
|
||||
m_angle_threshold_deg = 0.f;
|
||||
select_facets_by_angle(m_highlight_by_angle_threshold_deg, false);
|
||||
m_highlight_by_angle_threshold_deg = 0.f;
|
||||
m_parent.use_slope(false);
|
||||
}
|
||||
ImGui::SameLine(window_width - buttons_width);
|
||||
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_imgui->disabled_end();
|
||||
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
|
||||
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))
|
||||
m_tool_type = ToolType::BRUSH;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
|
||||
ImGui::PushItemWidth(tool_type_radio_smart_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL))
|
||||
m_tool_type = ToolType::SMART_FILL;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
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();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
|
||||
|
||||
m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only);
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width);
|
||||
|
||||
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))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
|
||||
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))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
|
||||
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))
|
||||
m_cursor_type = TriangleSelector::CursorType::POINTER;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
|
||||
|
||||
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width);
|
||||
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
|
||||
|
||||
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
|
||||
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
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();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width);
|
||||
|
||||
m_imgui->disabled_end();
|
||||
} else {
|
||||
assert(m_tool_type == ToolType::SMART_FILL);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
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_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width);
|
||||
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data()))
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
@ -319,19 +307,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width);
|
||||
auto clp_dist = float(m_c->object_clipper()->get_position());
|
||||
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
|
@ -35,7 +35,6 @@ private:
|
||||
PainterGizmoType get_painter_type() const override;
|
||||
|
||||
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
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
|
@ -547,13 +547,9 @@ RENDER_AGAIN:
|
||||
ImGui::SameLine(settings_sliders_left);
|
||||
ImGui::PushItemWidth(window_width - settings_sliders_left);
|
||||
m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted((_utf8(opts[0].second->tooltip)).c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width);
|
||||
|
||||
bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider
|
||||
bool slider_edited = ImGui::IsItemEdited(); // someone is dragging 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"));
|
||||
ImGui::SameLine(settings_sliders_left);
|
||||
m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted((_utf8(opts[1].second->tooltip)).c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width);
|
||||
|
||||
slider_clicked |= ImGui::IsItemClicked();
|
||||
slider_edited |= ImGui::IsItemEdited();
|
||||
slider_released |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
@ -580,13 +572,9 @@ RENDER_AGAIN:
|
||||
m_imgui->text(m_desc.at("closing_distance"));
|
||||
ImGui::SameLine(settings_sliders_left);
|
||||
m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted((_utf8(opts[2].second->tooltip)).c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width);
|
||||
|
||||
slider_clicked |= ImGui::IsItemClicked();
|
||||
slider_edited |= ImGui::IsItemEdited();
|
||||
slider_released |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
|
@ -129,6 +129,7 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
m_desc["tool_bucket_fill"] = _L("Bucket fill");
|
||||
|
||||
m_desc["smart_fill_angle"] = _L("Smart fill angle");
|
||||
m_desc["split_triangles"] = _L("Split triangles");
|
||||
|
||||
init_extruders_data();
|
||||
|
||||
@ -142,7 +143,7 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_triangles(selection, false);
|
||||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->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,
|
||||
const std::vector<std::string> &extruders,
|
||||
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_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 total_text_max = 0.f;
|
||||
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;
|
||||
window_width = std::max(window_width, total_text_max);
|
||||
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, 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));
|
||||
@ -331,13 +372,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
|
||||
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()) {
|
||||
ImGui::BeginTooltip();
|
||||
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();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_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()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints neighboring facets that have the same color."), max_tooltip_width);
|
||||
|
||||
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))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
|
||||
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))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
|
||||
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))
|
||||
m_cursor_type = TriangleSelector::CursorType::POINTER;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
|
||||
|
||||
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::PushItemWidth(window_width - sliders_width);
|
||||
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
|
||||
|
||||
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
|
||||
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
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();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
|
||||
|
||||
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"))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
@ -600,13 +591,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
|
||||
auto *shader = wxGetApp().get_current_shader();
|
||||
if (!shader)
|
||||
return;
|
||||
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||
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);
|
||||
assert(shader->get_name() == "mm_gouraud");
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++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()) {
|
||||
ScopeGuard guard_gouraud([shader]() { shader->start_using(); });
|
||||
ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); });
|
||||
shader->stop_using();
|
||||
|
||||
auto *contour_shader = wxGetApp().get_shader("mm_contour");
|
||||
|
@ -89,6 +89,8 @@ public:
|
||||
|
||||
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.
|
||||
// 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
|
||||
|
@ -43,32 +43,29 @@ void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool use_polygon_offset_fill) const
|
||||
GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const
|
||||
{
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}};
|
||||
// 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)
|
||||
bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.;
|
||||
float clp_dataf[4] = {0.f, 0.f, 1.f, FLT_MAX};
|
||||
if (clipping_plane_active) {
|
||||
if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) {
|
||||
const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane();
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
clp_dataf[i] = -1.f * float(clp->get_data()[i]);
|
||||
clp_dataf[3] = float(clp->get_data()[3]);
|
||||
clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]);
|
||||
clp_data_out.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
|
||||
auto* shader = wxGetApp().get_shader("gouraud_mod");
|
||||
#else
|
||||
@ -83,9 +80,10 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool
|
||||
#else
|
||||
shader->set_uniform("print_box.actived", false);
|
||||
#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(); });
|
||||
|
||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
int mesh_id = -1;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
@ -265,7 +263,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
: std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
|
||||
m_parent.set_as_dirty();
|
||||
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_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||
}
|
||||
@ -300,7 +303,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
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 &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.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
@ -326,9 +330,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
std::vector<Transform3d> trafo_matrices;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (mv->is_model_part())
|
||||
std::vector<Transform3d> trafo_matrices_not_translate;
|
||||
for (const ModelVolume *mv : mo->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
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.
|
||||
@ -352,6 +358,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
}
|
||||
|
||||
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):
|
||||
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)) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
|
||||
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)
|
||||
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)
|
||||
@ -369,7 +377,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} 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,
|
||||
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_last_mouse_click = mouse_position;
|
||||
@ -386,13 +395,17 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
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 & 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.
|
||||
std::vector<Transform3d> trafo_matrices;
|
||||
std::vector<Transform3d> trafo_matrices_not_translate;
|
||||
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_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
|
||||
}
|
||||
|
||||
// Now "click" into all the prepared points and spill paint around them.
|
||||
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)
|
||||
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()));
|
||||
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)
|
||||
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)
|
||||
@ -576,7 +592,8 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
||||
#else
|
||||
assert(shader->get_name() == "gouraud");
|
||||
#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),
|
||||
std::make_pair(&m_iva_blockers, blockers_color)}) {
|
||||
if (iva.first->has_VBOs()) {
|
||||
@ -602,7 +619,7 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
||||
auto *contour_shader = wxGetApp().get_shader("mm_contour");
|
||||
contour_shader->start_using();
|
||||
|
||||
glsafe(::glDepthFunc(GL_GEQUAL));
|
||||
glsafe(::glDepthFunc(GL_LEQUAL));
|
||||
m_paint_contour.render();
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
|
||||
|
@ -126,7 +126,7 @@ public:
|
||||
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
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_circle() const;
|
||||
void render_cursor_sphere(const Transform3d& trafo) const;
|
||||
@ -159,6 +159,9 @@ protected:
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
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 SmartFillAngleMax = 90.f;
|
||||
static constexpr float SmartFillAngleStep = 1.f;
|
||||
@ -173,6 +176,14 @@ protected:
|
||||
Right
|
||||
};
|
||||
|
||||
struct ClippingPlaneDataWrapper
|
||||
{
|
||||
std::array<float, 4> clp_dataf;
|
||||
std::array<float, 2> z_range;
|
||||
};
|
||||
|
||||
ClippingPlaneDataWrapper get_clipping_plane_data() const;
|
||||
|
||||
private:
|
||||
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
|
||||
void update_raycast_cache(const Vec2d& mouse_position,
|
||||
|
@ -129,13 +129,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
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))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
|
||||
ImGui::PushItemWidth(cursor_type_radio_circle);
|
||||
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
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"))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
|
@ -24,15 +24,16 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
||||
, m_obj_index(0)
|
||||
, m_need_reload(false)
|
||||
, m_show_wireframe(false)
|
||||
|
||||
// translation for GUI size
|
||||
, tr_mesh_name(_u8L("Mesh name"))
|
||||
, tr_triangles(_u8L("Triangles"))
|
||||
, tr_preview(_u8L("Preview"))
|
||||
, tr_detail_level(_u8L("Detail level"))
|
||||
, tr_decimate_ratio(_u8L("Decimate ratio"))
|
||||
|
||||
// for wireframe
|
||||
, m_wireframe_VBO_id(0)
|
||||
, m_wireframe_IBO_id(0)
|
||||
, m_wireframe_IBO_size(0)
|
||||
{}
|
||||
|
||||
GLGizmoSimplify::~GLGizmoSimplify() {
|
||||
@ -41,10 +42,11 @@ GLGizmoSimplify::~GLGizmoSimplify() {
|
||||
free_gpu();
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_init()
|
||||
{
|
||||
//m_grabbers.emplace_back();
|
||||
//m_shortcut_key = WXK_CONTROL_C;
|
||||
bool GLGizmoSimplify::on_esc_key_down() {
|
||||
if (m_state == State::settings || m_state == State::canceling)
|
||||
return false;
|
||||
|
||||
m_state = State::canceling;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -53,10 +55,6 @@ std::string GLGizmoSimplify::on_get_name() const
|
||||
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)
|
||||
{
|
||||
create_gui_cfg();
|
||||
@ -143,8 +141,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
ImGui::Separator();
|
||||
|
||||
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
|
||||
m_is_valid_result = false;
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
live_preview();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
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);
|
||||
static int reduction = 2;
|
||||
if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) {
|
||||
m_is_valid_result = false;
|
||||
if (reduction < 0) reduction = 0;
|
||||
if (reduction > 4) reduction = 4;
|
||||
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 4: m_configuration.max_error = 1.f; break;
|
||||
}
|
||||
live_preview();
|
||||
}
|
||||
m_imgui->disabled_end(); // !use_count
|
||||
|
||||
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
|
||||
m_is_valid_result = false;
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
live_preview();
|
||||
}
|
||||
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);
|
||||
const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
|
||||
((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
|
||||
|
||||
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)
|
||||
m_configuration.decimate_ratio = 0.01f;
|
||||
if (m_configuration.decimate_ratio > 100.f)
|
||||
m_configuration.decimate_ratio = 100.f;
|
||||
m_configuration.fix_count_by_ratio(triangle_count);
|
||||
live_preview();
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
@ -206,27 +205,32 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_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();
|
||||
else free_gpu();
|
||||
}
|
||||
|
||||
if (m_state == State::settings) {
|
||||
bool is_canceling = m_state == State::canceling;
|
||||
m_imgui->disabled_begin(is_canceling);
|
||||
if (m_imgui->button(_L("Cancel"))) {
|
||||
if (m_state == State::settings) {
|
||||
if (m_original_its.has_value()) {
|
||||
set_its(*m_original_its);
|
||||
m_state = State::close_on_end;
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
} else {
|
||||
m_state = State::canceling;
|
||||
}
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
if (m_imgui->button(_L("Preview"))) {
|
||||
m_state = State::preview;
|
||||
// simplify but not apply on mesh
|
||||
process();
|
||||
}
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling)
|
||||
ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str());
|
||||
m_imgui->disabled_end(); // state canceling
|
||||
|
||||
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;
|
||||
@ -237,12 +241,12 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
} 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();
|
||||
} 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);
|
||||
// draw progress bar
|
||||
char buf[32];
|
||||
@ -251,6 +255,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
}
|
||||
m_imgui->end();
|
||||
|
||||
// refresh view when needed
|
||||
if (m_need_reload) {
|
||||
m_need_reload = false;
|
||||
bool close_on_end = (m_state == State::close_on_end);
|
||||
@ -280,6 +285,22 @@ void GLGizmoSimplify::close() {
|
||||
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()
|
||||
{
|
||||
@ -408,6 +429,7 @@ void GLGizmoSimplify::create_gui_cfg() {
|
||||
cfg.input_width = cfg.bottom_left_width * 1.5;
|
||||
cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2;
|
||||
cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5;
|
||||
|
||||
m_gui_cfg = cfg;
|
||||
}
|
||||
|
||||
|
@ -24,21 +24,25 @@ class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GL
|
||||
public:
|
||||
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
virtual ~GLGizmoSimplify();
|
||||
bool on_esc_key_down();
|
||||
protected:
|
||||
virtual bool on_init() 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 bool on_is_activable() const override;
|
||||
virtual bool on_is_selectable() const override { return false; }
|
||||
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
|
||||
virtual void render_painter_gizmo() const override{ render_wireframe(); }
|
||||
private:
|
||||
void after_apply();
|
||||
void close();
|
||||
void live_preview();
|
||||
void process();
|
||||
void set_its(indexed_triangle_set &its);
|
||||
void create_gui_cfg();
|
||||
|
@ -924,6 +924,10 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||
case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -919,7 +919,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp
|
||||
}
|
||||
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);
|
||||
|
@ -280,10 +280,10 @@ void ImGuiWrapper::render()
|
||||
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);
|
||||
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str());
|
||||
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width);
|
||||
|
||||
/*#ifdef __linux__
|
||||
size.x *= m_style_scaling;
|
||||
@ -293,6 +293,13 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text)
|
||||
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)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
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 ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power);
|
||||
|
@ -53,7 +53,9 @@ public:
|
||||
|
||||
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 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_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 std::string& 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).
|
||||
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);
|
||||
|
@ -60,6 +60,7 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat
|
||||
_u8L("Undo desktop integration was successful.") },
|
||||
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_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::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
|
||||
@ -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) {
|
||||
ImGui::SetNextWindowFocus();
|
||||
// Uncomment if imgui window focus is needed on hover. I cant find any case.
|
||||
//ImGui::SetNextWindowFocus();
|
||||
set_hovered();
|
||||
}
|
||||
|
||||
@ -1151,6 +1153,8 @@ bool NotificationManager::SlicingProgressNotification::set_progress_state(Notifi
|
||||
m_sp_state = state;
|
||||
return true;
|
||||
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);
|
||||
m_has_cancel_button = false;
|
||||
m_has_print_info = false;
|
||||
@ -1508,6 +1512,16 @@ void NotificationManager::push_notification(NotificationType type,
|
||||
int duration = get_standart_duration(level);
|
||||
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)
|
||||
{
|
||||
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)]() {
|
||||
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;
|
||||
}
|
||||
|
||||
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( push_notification_data(std::move(notification), 0))
|
||||
|
@ -110,7 +110,9 @@ enum class NotificationType
|
||||
// Contains ObjectID for closing when object is deleted
|
||||
SimplifySuggestion,
|
||||
// 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
|
||||
@ -151,6 +153,10 @@ public:
|
||||
// ErrorNotificationLevel and ImportantNotificationLevel are never faded out.
|
||||
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);
|
||||
// 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.
|
||||
void push_validate_error_notification(const std::string& text);
|
||||
// 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.
|
||||
// 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.
|
||||
// Also note that no control of same notification is done during push_delayed_notification 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);
|
||||
// Removes all notifications of type from m_waiting_notifications
|
||||
void stop_delayed_notifications_of_type(const NotificationType type);
|
||||
// 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_data(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval);
|
||||
//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);
|
||||
// Put the more important notifications to the bottom of the list.
|
||||
|
@ -597,6 +597,8 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
|
||||
{
|
||||
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;
|
||||
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
|
||||
if (option_set.front().opt.full_width && field->getWindow())
|
||||
field->getWindow()->SetSize(ctrl->GetSize().x - h_pos, -1);
|
||||
|
@ -4032,6 +4032,7 @@ void Plater::priv::on_export_began(wxCommandEvent& evt)
|
||||
{
|
||||
if (show_warning_dialog)
|
||||
warnings_dialog();
|
||||
notification_manager->push_delayed_notification(NotificationType::ExportOngoing, [](){return true;}, 1000, 1000);
|
||||
}
|
||||
void Plater::priv::on_slicing_began()
|
||||
{
|
||||
@ -4164,6 +4165,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
if(wxGetApp().get_mode() == comSimple) {
|
||||
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 (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) {
|
||||
show_action_buttons(false);
|
||||
|
@ -43,7 +43,7 @@ namespace Slic3r {
|
||||
|
||||
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,
|
||||
wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
{
|
||||
@ -51,6 +51,8 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) :
|
||||
isOSX = true;
|
||||
#endif
|
||||
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)
|
||||
@ -450,8 +452,10 @@ void PreferencesDialog::build(size_t selected_tab)
|
||||
|
||||
activate_options_tab(m_optgroup_gui);
|
||||
// set Field for notify_release to its value to activate the object
|
||||
if (is_editor) {
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
|
||||
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
|
||||
} // Slic3r
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/timer.h>
|
||||
#include <map>
|
||||
|
||||
class wxColourPickerCtrl;
|
||||
@ -20,6 +21,7 @@ namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class ConfigOptionsGroup;
|
||||
class OG_CustomCtrl;
|
||||
|
||||
class PreferencesDialog : public DPIDialog
|
||||
{
|
||||
@ -39,7 +41,7 @@ class PreferencesDialog : public DPIDialog
|
||||
bool m_recreate_GUI{false};
|
||||
|
||||
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;
|
||||
|
||||
bool settings_layout_changed() const { return m_settings_layout_changed; }
|
||||
@ -55,6 +57,22 @@ protected:
|
||||
void create_icon_size_slider();
|
||||
void create_settings_mode_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
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
@ -17,6 +18,7 @@
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/trim_all.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/uuid/detail/md5.hpp>
|
||||
|
||||
@ -37,11 +39,16 @@
|
||||
#pragma comment(lib, "iphlpapi.lib")
|
||||
#elif __APPLE__
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#else // Linux/BSD
|
||||
#include <charconv>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
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:
|
||||
@ -52,8 +59,8 @@ std::string gl_get_string_safe(GLenum param, const std::string& default_value);
|
||||
class SendSystemInfoDialog : public DPIDialog
|
||||
{
|
||||
enum {
|
||||
MIN_WIDTH = 80,
|
||||
MIN_HEIGHT = 50
|
||||
MIN_WIDTH = 70,
|
||||
MIN_HEIGHT = 34
|
||||
};
|
||||
|
||||
public:
|
||||
@ -129,20 +136,36 @@ public:
|
||||
// current version is newer. Only major and minor versions are compared.
|
||||
static bool should_dialog_be_shown()
|
||||
{
|
||||
return false;
|
||||
|
||||
std::string last_sent_version = wxGetApp().app_config->get("version_system_info_sent");
|
||||
Semver semver_current(SLIC3R_VERSION);
|
||||
Semver semver_last_sent;
|
||||
if (! last_sent_version.empty())
|
||||
semver_last_sent = Semver(last_sent_version);
|
||||
|
||||
if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha")
|
||||
return false; // Don't show in alphas.
|
||||
// set whether to show in alpha builds, or only betas/rcs/finals:
|
||||
const bool show_in_alphas = true;
|
||||
|
||||
// Show the dialog if current > last, but they differ in more than just patch.
|
||||
return ((semver_current.maj() > semver_last_sent.maj())
|
||||
if (! show_in_alphas && semver_current.prerelease()
|
||||
&& 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() ));
|
||||
|
||||
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;
|
||||
|
||||
int idx = -1;
|
||||
constexpr DWORD bufsize_ = 200;
|
||||
DWORD bufsize = bufsize_;
|
||||
constexpr DWORD bufsize_ = 500;
|
||||
DWORD bufsize = bufsize_-1; // Ensure a terminating zero.
|
||||
char buf[bufsize_] = "";
|
||||
memset(buf, 0, bufsize_);
|
||||
const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\";
|
||||
std::string reg_path = reg_dir;
|
||||
|
||||
@ -186,7 +210,7 @@ static std::map<std::string, std::string> get_cpu_info_from_registry()
|
||||
}
|
||||
++idx;
|
||||
reg_path = reg_dir + std::to_string(idx) + "\\";
|
||||
bufsize = bufsize_;
|
||||
bufsize = bufsize_-1;
|
||||
}
|
||||
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)
|
||||
{
|
||||
std::map<std::string, std::string> out;
|
||||
constexpr size_t max_len = 100;
|
||||
constexpr size_t max_len = 1000;
|
||||
char cline[max_len] = "";
|
||||
FILE* fp = popen(name.data(), "r");
|
||||
if (fp != NULL) {
|
||||
@ -260,10 +284,12 @@ static std::string get_unique_id()
|
||||
char buf[buf_size] = "";
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
|
||||
if (ioRegistryRoot != MACH_PORT_NULL) {
|
||||
CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0);
|
||||
IOObjectRelease(ioRegistryRoot);
|
||||
CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman);
|
||||
CFRelease(uuidCf);
|
||||
}
|
||||
// Now convert the string to std::vector<unsigned char>.
|
||||
for (char* c = buf; *c != 0; ++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();
|
||||
|
||||
// Get system language.
|
||||
std::string sys_language = "Unknown";
|
||||
std::string sys_language = "Unknown"; // important to init, see the __APPLE__ block.
|
||||
#ifndef __APPLE__
|
||||
// Following apparently does not work on macOS.
|
||||
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.
|
||||
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("TranslationLanguage: ", wxGetApp().app_config->get("translation_language"));
|
||||
|
||||
|
||||
pt::ptree hw_node;
|
||||
{
|
||||
hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName());
|
||||
hw_node.put("RAM_MB", size_t(Slic3r::total_physical_memory()/1000000));
|
||||
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:
|
||||
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("Vendor", sysctl["machdep.cpu.vendor"]);
|
||||
#else // linux/BSD
|
||||
std::map<std::string, std::string> lscpu = parse_lscpu_etc("lscpu", ':');
|
||||
cpu_node.put("Arch", lscpu["Architecture"]);
|
||||
cpu_node.put("Cores", lscpu["CPU(s)"]);
|
||||
cpu_node.put("Model", lscpu["Model name"]);
|
||||
cpu_node.put("Vendor", lscpu["Vendor ID"]);
|
||||
std::map<std::string, std::string> lscpu = parse_lscpu_etc("cat /proc/cpuinfo", ':');
|
||||
if (auto ncpu_it = lscpu.find("processor"); ncpu_it != lscpu.end()) {
|
||||
std::string& ncpu = ncpu_it->second;
|
||||
if (int num=0; std::from_chars(ncpu.data(), ncpu.data() + ncpu.size(), num).ec != std::errc::invalid_argument)
|
||||
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
|
||||
hw_node.add_child("CPU", cpu_node);
|
||||
|
||||
pt::ptree monitors_node;
|
||||
for (int i=0; i<int(wxDisplay::GetCount()); ++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
|
||||
monitor_node.put("width", display.GetGeometry().GetWidth());
|
||||
monitor_node.put("height", display.GetGeometry().GetHeight());
|
||||
|
||||
// Only get the scaling on Win, it is not reliable on other platforms.
|
||||
#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));
|
||||
}
|
||||
hw_node.add_child("Monitors", monitors_node);
|
||||
@ -435,15 +475,23 @@ static std::string generate_system_info_json()
|
||||
pt::ptree root;
|
||||
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.
|
||||
std::stringstream ss;
|
||||
pt::write_json(ss, root);
|
||||
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,
|
||||
wxDEFAULT_DIALOG_STYLE)
|
||||
{
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
|
||||
// Get current PrusaSliver version info.
|
||||
std::string app_name;
|
||||
{
|
||||
@ -500,7 +550,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
|
||||
std::string("<i>") + filename + "</i>");
|
||||
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(
|
||||
"<html><body bgcolor=%1%><font color=%2%>"
|
||||
"<table><tr><td>"
|
||||
@ -514,7 +564,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
|
||||
+ "<b><a href=\"show\">" + label3 + "</a></b><br />"
|
||||
+ "</font></body></html>", bgr_clr_str, text_clr_str);
|
||||
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));
|
||||
dlg.ShowModal();
|
||||
});
|
||||
@ -526,7 +576,6 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
|
||||
m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info"));
|
||||
|
||||
auto* hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
hsizer->Add(m_btn_ask_later);
|
||||
hsizer->AddSpacer(em);
|
||||
hsizer->Add(m_btn_dont_send);
|
||||
@ -548,6 +597,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
|
||||
SetSize(std::max(size.GetWidth(), MIN_WIDTH * em),
|
||||
std::max(size.GetHeight(), MIN_HEIGHT * em));
|
||||
|
||||
CenterOnParent();
|
||||
|
||||
m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&)
|
||||
{
|
||||
if (send_info()) {
|
||||
@ -592,15 +643,16 @@ bool SendSystemInfoDialog::send_info()
|
||||
} result; // No synchronization needed, UI thread reads only after worker is joined.
|
||||
|
||||
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(url);
|
||||
Http http = Http::post(SEND_SYSTEM_INFO_URL);
|
||||
http.header("Content-Type", "application/json")
|
||||
.timeout_max(6) // seconds
|
||||
.set_post_body(data)
|
||||
.on_complete([&result](std::string body, unsigned status) {
|
||||
result = { Result::Success, _L("System info sent successfully. Thank you.") };
|
||||
})
|
||||
.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) {
|
||||
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
|
||||
sending_thread.join(); // and wait until it terminates.
|
||||
|
||||
if (result.value != Result::Cancelled) { // user knows he cancelled, no need to tell him.
|
||||
InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str);
|
||||
info_dlg.ShowModal();
|
||||
}
|
||||
return result.value == Result::Success;
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,7 @@ struct Http::priv
|
||||
{
|
||||
enum {
|
||||
DEFAULT_TIMEOUT_CONNECT = 10,
|
||||
DEFAULT_TIMEOUT_MAX = 0,
|
||||
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);
|
||||
|
||||
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 set_post_body(const fs::path &path);
|
||||
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_max(DEFAULT_TIMEOUT_MAX);
|
||||
::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_ERRORBUFFER, &error_buffer.front());
|
||||
@ -253,6 +256,11 @@ void Http::priv::set_timeout_connect(long 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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (p) { p->limit = sizeLimit; }
|
||||
|
@ -58,6 +58,8 @@ public:
|
||||
|
||||
// Sets a maximum connection timeout in seconds
|
||||
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.
|
||||
// A value of zero sets the default limit, which is is 5MB.
|
||||
Http& size_limit(size_t sizeLimit);
|
||||
|
@ -46,10 +46,6 @@ using Slic3r::GUI::Config::SnapshotDB;
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
enum {
|
||||
SLIC3R_VERSION_BODY_MAX = 256,
|
||||
};
|
||||
|
||||
static const char *INDEX_FILENAME = "index.idx";
|
||||
static const char *TMP_EXTENSION = ".download";
|
||||
|
||||
|
@ -13,6 +13,8 @@ class AppConfig;
|
||||
class PresetBundle;
|
||||
class Semver;
|
||||
|
||||
const int SLIC3R_VERSION_BODY_MAX = 256;
|
||||
|
||||
class PresetUpdater
|
||||
{
|
||||
public:
|
||||
|
@ -34,7 +34,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
|
||||
}
|
||||
}
|
||||
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") {
|
||||
REQUIRE(result == ExPolygons { {
|
||||
{ { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } },
|
||||
|
@ -49,7 +49,7 @@ offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, mit
|
||||
Slic3r::ClipperLib::JoinType joinType
|
||||
double miterLimit
|
||||
CODE:
|
||||
RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit);
|
||||
RETVAL = offset2_ex(union_ex(polygons), delta1, delta2, joinType, miterLimit);
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user