mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 18:31:11 +00:00
Merge branch 'main' into feature/wipetower2_travel_path
This commit is contained in:
@@ -80,8 +80,12 @@ endif()
|
||||
|
||||
if (DEFINED BBL_RELEASE_TO_PUBLIC)
|
||||
add_compile_definitions("BBL_RELEASE_TO_PUBLIC=${BBL_RELEASE_TO_PUBLIC}")
|
||||
if (BBL_RELEASE_TO_PUBLIC)
|
||||
add_compile_definitions(WXINSPECTOR_DISABLE)
|
||||
endif ()
|
||||
else ()
|
||||
add_compile_definitions("BBL_RELEASE_TO_PUBLIC=$<CONFIG:Release>")
|
||||
add_compile_definitions("$<$<CONFIG:Release>:WXINSPECTOR_DISABLE>")
|
||||
endif ()
|
||||
|
||||
find_package(Git)
|
||||
|
||||
Vendored
+8
@@ -52,6 +52,14 @@ ExternalProject_Add(dep_OpenSSL
|
||||
CONFIGURE_COMMAND ${_conf_cmd} ${_cross_arch}
|
||||
"--openssldir=${DESTDIR}"
|
||||
"--prefix=${DESTDIR}"
|
||||
# OpenSSL's linux-x86_64 target sets multilib=64, so it installs to
|
||||
# <prefix>/lib64 while every other dep uses <prefix>/lib. CPython's
|
||||
# --with-openssl only ever emits -L<dir>/lib, so it misses the bundled
|
||||
# static libs and silently links the system OpenSSL instead -- which,
|
||||
# against 1.1.1w headers, leaves _ssl.so with an undefined
|
||||
# SSL_get_peer_certificate (removed in OpenSSL 3.x). Pin libdir so the
|
||||
# prefix stays single-layout.
|
||||
"--libdir=lib"
|
||||
${_cross_comp_prefix_line}
|
||||
no-shared
|
||||
no-asm
|
||||
|
||||
@@ -465,6 +465,57 @@ static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *stat
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
|
||||
y = r.ymin;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In multi-line mode, clamp y to stay within the text vertical bounds.
|
||||
// This lets the click still land at a valid location if the mouse is slightly
|
||||
// above or below the text.
|
||||
StbTexteditRow r;
|
||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
||||
int i = 0;
|
||||
float base_y = 0, y_min, y_max;
|
||||
|
||||
// Get the first row to establish y_min and start the iteration
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
|
||||
if (r.num_chars <= 0)
|
||||
{
|
||||
state->cursor = 0;
|
||||
state->select_start = state->cursor;
|
||||
state->select_end = state->cursor;
|
||||
state->has_preferred_x = 0;
|
||||
return;
|
||||
}
|
||||
y_min = r.ymin;
|
||||
y_max = base_y + r.ymax;
|
||||
i = r.num_chars;
|
||||
base_y += r.baseline_y_delta;
|
||||
|
||||
// Walk the remaining rows to find the bottom of the last row
|
||||
while (i < n)
|
||||
{
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
|
||||
if (r.num_chars <= 0)
|
||||
break;
|
||||
y_max = base_y + r.ymax;
|
||||
i += r.num_chars;
|
||||
base_y += r.baseline_y_delta;
|
||||
}
|
||||
|
||||
// If the text ends with a newline, account for the empty trailing line
|
||||
// so the cursor can be placed on it
|
||||
if (n > 0 && STB_TEXTEDIT_GETCHAR(str, n - 1) == STB_TEXTEDIT_NEWLINE)
|
||||
{
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, n);
|
||||
y_max = base_y + r.ymax;
|
||||
}
|
||||
|
||||
// Subtract half the last line height to avoid rounding issues when the mouse
|
||||
// is just barely below the last line (keep cursor on the last line, not after the text)
|
||||
y_max -= (r.ymax - r.ymin) * 0.5f;
|
||||
|
||||
if (y < y_min) y = y_min;
|
||||
if (y > y_max) y = y_max;
|
||||
}
|
||||
|
||||
state->cursor = stb_text_locate_coord(str, x, y);
|
||||
state->select_start = state->cursor;
|
||||
@@ -485,6 +536,50 @@ static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
|
||||
y = r.ymin;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In multi-line mode, clamp y to stay within the text vertical bounds.
|
||||
// This lets the drag keep working if the mouse goes off the top or bottom of the text.
|
||||
StbTexteditRow r;
|
||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
||||
int i = 0;
|
||||
float base_y = 0, y_min, y_max;
|
||||
|
||||
// Get the first row to establish y_min and start the iteration
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
|
||||
if (r.num_chars <= 0)
|
||||
return;
|
||||
y_min = r.ymin;
|
||||
y_max = base_y + r.ymax;
|
||||
i = r.num_chars;
|
||||
base_y += r.baseline_y_delta;
|
||||
|
||||
// Walk the remaining rows to find the bottom of the last row
|
||||
while (i < n)
|
||||
{
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
|
||||
if (r.num_chars <= 0)
|
||||
break;
|
||||
y_max = base_y + r.ymax;
|
||||
i += r.num_chars;
|
||||
base_y += r.baseline_y_delta;
|
||||
}
|
||||
|
||||
// If the text ends with a newline, account for the empty trailing line
|
||||
// so the cursor can be placed on it
|
||||
if (n > 0 && STB_TEXTEDIT_GETCHAR(str, n - 1) == STB_TEXTEDIT_NEWLINE)
|
||||
{
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, n);
|
||||
y_max = base_y + r.ymax;
|
||||
}
|
||||
|
||||
// Subtract half the last line height to avoid rounding issues when the mouse
|
||||
// is just barely below the last line (keep cursor on the last line, not after the text)
|
||||
y_max -= (r.ymax - r.ymin) * 0.5f;
|
||||
|
||||
if (y < y_min) y = y_min;
|
||||
if (y > y_max) y = y_max;
|
||||
}
|
||||
|
||||
if (state->select_start == state->select_end)
|
||||
state->select_start = state->cursor;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,7 @@
|
||||
#version 140
|
||||
// Multisample depth texture for the anti-aliased outline (see 3DScene.cpp render_with_outline).
|
||||
// Optional on the GLSL 140 path: if unavailable, fallback to a non-multisample depth texture.
|
||||
#extension GL_ARB_texture_multisample : enable
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
//BBS: add grey and orange
|
||||
@@ -36,7 +39,14 @@ uniform SlopeDetection slope;
|
||||
|
||||
//BBS: add outline_color
|
||||
uniform bool is_outline;
|
||||
// The outline is a per-fragment discard mask, which the framebuffer MSAA cannot smooth, so the
|
||||
// silhouette is resolved per sample from a multisample copy of the outlined model's depth buffer.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
uniform sampler2DMS depth_tex;
|
||||
uniform int msaa_samples; // samples in depth_tex, 1 when MSAA is off
|
||||
#else
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
uniform vec2 screen_size;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
@@ -99,45 +109,87 @@ float GetTolerance(float d, float k)
|
||||
return -k*(d+A)*(d+A)/B;
|
||||
}
|
||||
|
||||
float DetectSilho(vec2 fragCoord, vec2 dir)
|
||||
// Depth of sample s at integer pixel coord.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
// texelFetch has no wrap mode, so clamp to the edge texel (sampler2D used CLAMP_TO_EDGE).
|
||||
ivec2 sz = textureSize(depth_tex);
|
||||
return abs(texelFetch(depth_tex, clamp(coord, ivec2(0), sz - 1), s).r);
|
||||
}
|
||||
#else
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
return abs(texture(depth_tex, (vec2(coord) + 0.5) / screen_size).r);
|
||||
}
|
||||
#endif
|
||||
|
||||
float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
{
|
||||
// -------------------------------------------
|
||||
// x0 ___ x1----o
|
||||
// :\ :
|
||||
// x0 ___ x1----o
|
||||
// :\ :
|
||||
// r0 : \ : r1
|
||||
// : \ :
|
||||
// : \ :
|
||||
// o---x2 ___ x3
|
||||
//
|
||||
// r0 and r1 are the differences between actual
|
||||
// and expected (as if x0..3 where on the same
|
||||
// plane) depth values.
|
||||
// -------------------------------------------
|
||||
|
||||
float x0 = abs(texture(depth_tex, (fragCoord + dir*-2.0) / screen_size).r);
|
||||
float x1 = abs(texture(depth_tex, (fragCoord + dir*-1.0) / screen_size).r);
|
||||
float x2 = abs(texture(depth_tex, (fragCoord + dir* 0.0) / screen_size).r);
|
||||
float x3 = abs(texture(depth_tex, (fragCoord + dir* 1.0) / screen_size).r);
|
||||
|
||||
float x0 = FetchDepth(coord + dir*-2, s);
|
||||
float x1 = FetchDepth(coord + dir*-1, s);
|
||||
float x2 = FetchDepth(coord, s);
|
||||
float x3 = FetchDepth(coord + dir* 1, s);
|
||||
|
||||
float d0 = (x1-x0);
|
||||
float d1 = (x2-x3);
|
||||
|
||||
|
||||
float r0 = x1 + d0 - x2;
|
||||
float r1 = x2 + d1 - x1;
|
||||
|
||||
float tol = GetTolerance(x2, 0.04);
|
||||
|
||||
return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0));
|
||||
|
||||
float tol = GetTolerance(x2, 0.04);
|
||||
|
||||
return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0));
|
||||
}
|
||||
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
float DetectSilho(ivec2 coord, int s)
|
||||
{
|
||||
return max(
|
||||
DetectSilho(fragCoord, vec2(1,0)), // Horizontal
|
||||
DetectSilho(fragCoord, vec2(0,1)) // Vertical
|
||||
DetectSilho(coord, ivec2(1,0), s), // Horizontal
|
||||
DetectSilho(coord, ivec2(0,1), s) // Vertical
|
||||
);
|
||||
}
|
||||
|
||||
// Full response of one sample. Reduce the max() per sample and average only afterwards:
|
||||
// max(mean) <= mean(max), and averaging first hollows out diagonal and curved lines.
|
||||
float DetectSilhoSample(ivec2 coord, int s)
|
||||
{
|
||||
float v = DetectSilho(coord, s);
|
||||
// Makes silhouettes thicker.
|
||||
for (int i = 1; i <= INFLATE; ++i)
|
||||
{
|
||||
v = max(v, DetectSilho(coord + ivec2(i, 0), s));
|
||||
v = max(v, DetectSilho(coord + ivec2(0, i), s));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Average the per-sample coverage into the sub-pixel anti-aliasing of the line.
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
{
|
||||
ivec2 coord = ivec2(fragCoord);
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
int n = max(msaa_samples, 1);
|
||||
#else
|
||||
const int n = 1;
|
||||
#endif
|
||||
float acc = 0.0;
|
||||
for (int s = 0; s < n; ++s)
|
||||
acc += DetectSilhoSample(coord, s);
|
||||
return acc / float(n);
|
||||
}
|
||||
|
||||
// Returns a lighting multiplier in [1 - shadow_intensity, 1]: < 1 where the fragment is
|
||||
// occluded from the light in the shadow map. 3x3 PCF softens the edges.
|
||||
float shadow_shade()
|
||||
@@ -224,14 +276,7 @@ void main()
|
||||
//BBS: add outline_color
|
||||
if (is_outline) {
|
||||
color = vec4((vec3(intensity.y) + color.rgb * intensity.x) * shade, color.a);
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
float s = DetectSilho(fragCoord);
|
||||
// Makes silhouettes thicker.
|
||||
for(int i=1;i<=INFLATE; i++)
|
||||
{
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0)));
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(0, i)));
|
||||
}
|
||||
float s = DetectSilho(gl_FragCoord.xy);
|
||||
if (s < 0.01)
|
||||
discard;
|
||||
out_color = vec4(mix(color.rgb, getBackfaceColor(color.rgb), s), color.a);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#version 140
|
||||
// Multisample depth texture for the anti-aliased outline (see 3DScene.cpp render_with_outline).
|
||||
// Optional on the GLSL 140 path: if unavailable, fallback to a non-multisample depth texture.
|
||||
#extension GL_ARB_texture_multisample : enable
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const vec3 LightRed = vec3(0.78, 0.0, 0.0);
|
||||
@@ -51,7 +54,14 @@ uniform SlopeDetection slope;
|
||||
|
||||
//BBS: add outline_color
|
||||
uniform bool is_outline;
|
||||
// The outline is a per-fragment discard mask, which the framebuffer MSAA cannot smooth, so the
|
||||
// silhouette is resolved per sample from a multisample copy of the outlined model's depth buffer.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
uniform sampler2DMS depth_tex;
|
||||
uniform int msaa_samples; // samples in depth_tex, 1 when MSAA is off
|
||||
#else
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
uniform vec2 screen_size;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
@@ -100,12 +110,27 @@ float GetTolerance(float d, float k)
|
||||
return -k*(d+A)*(d+A)/B;
|
||||
}
|
||||
|
||||
float DetectSilho(vec2 fragCoord, vec2 dir)
|
||||
// Depth of sample s at integer pixel coord.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
float x0 = abs(texture(depth_tex, (fragCoord + dir*-2.0) / screen_size).r);
|
||||
float x1 = abs(texture(depth_tex, (fragCoord + dir*-1.0) / screen_size).r);
|
||||
float x2 = abs(texture(depth_tex, (fragCoord + dir* 0.0) / screen_size).r);
|
||||
float x3 = abs(texture(depth_tex, (fragCoord + dir* 1.0) / screen_size).r);
|
||||
// texelFetch has no wrap mode, so clamp to the edge texel (sampler2D used CLAMP_TO_EDGE).
|
||||
ivec2 sz = textureSize(depth_tex);
|
||||
return abs(texelFetch(depth_tex, clamp(coord, ivec2(0), sz - 1), s).r);
|
||||
}
|
||||
#else
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
return abs(texture(depth_tex, (vec2(coord) + 0.5) / screen_size).r);
|
||||
}
|
||||
#endif
|
||||
|
||||
float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
{
|
||||
float x0 = FetchDepth(coord + dir*-2, s);
|
||||
float x1 = FetchDepth(coord + dir*-1, s);
|
||||
float x2 = FetchDepth(coord, s);
|
||||
float x3 = FetchDepth(coord + dir* 1, s);
|
||||
|
||||
float d0 = (x1-x0);
|
||||
float d1 = (x2-x3);
|
||||
@@ -116,17 +141,45 @@ float DetectSilho(vec2 fragCoord, vec2 dir)
|
||||
float tol = GetTolerance(x2, 0.04);
|
||||
|
||||
return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0));
|
||||
|
||||
}
|
||||
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
float DetectSilho(ivec2 coord, int s)
|
||||
{
|
||||
return max(
|
||||
DetectSilho(fragCoord, vec2(1,0)),
|
||||
DetectSilho(fragCoord, vec2(0,1))
|
||||
DetectSilho(coord, ivec2(1,0), s),
|
||||
DetectSilho(coord, ivec2(0,1), s)
|
||||
);
|
||||
}
|
||||
|
||||
// Full response of one sample. Reduce the max() per sample and average only afterwards:
|
||||
// max(mean) <= mean(max), and averaging first hollows out diagonal and curved lines.
|
||||
float DetectSilhoSample(ivec2 coord, int s)
|
||||
{
|
||||
float v = DetectSilho(coord, s);
|
||||
// Makes silhouettes thicker.
|
||||
for (int i = 1; i <= INFLATE; ++i)
|
||||
{
|
||||
v = max(v, DetectSilho(coord + ivec2(i, 0), s));
|
||||
v = max(v, DetectSilho(coord + ivec2(0, i), s));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Average the per-sample coverage into the sub-pixel anti-aliasing of the line.
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
{
|
||||
ivec2 coord = ivec2(fragCoord);
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
int n = max(msaa_samples, 1);
|
||||
#else
|
||||
const int n = 1;
|
||||
#endif
|
||||
float acc = 0.0;
|
||||
for (int s = 0; s < n; ++s)
|
||||
acc += DetectSilhoSample(coord, s);
|
||||
return acc / float(n);
|
||||
}
|
||||
|
||||
float compute_ssao_factor(vec3 normal, vec3 view_dir, vec3 eye_pos)
|
||||
{
|
||||
vec3 normal_dx = dFdx(normal);
|
||||
@@ -270,13 +323,7 @@ void main()
|
||||
if (is_outline) {
|
||||
vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade;
|
||||
vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a);
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
float s = DetectSilho(fragCoord);
|
||||
for(int i=1;i<=INFLATE; i++)
|
||||
{
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0)));
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(0, i)));
|
||||
}
|
||||
float s = DetectSilho(gl_FragCoord.xy);
|
||||
if (s < 0.01)
|
||||
discard;
|
||||
out_color = vec4(mix(shaded_color.rgb, getBackfaceColor(shaded_color.rgb), s), shaded_color.a);
|
||||
|
||||
@@ -46,12 +46,16 @@ def no_duplicates_object_pairs_hook(pairs):
|
||||
return seen
|
||||
|
||||
# NOTE: currently Orca expects compatible_printers to be a defined in every instantiation profile, inheritation is not supported in Profile page
|
||||
def check_filament_compatible_printers(vendor_folder):
|
||||
def check_filament_compatible_printers(vendor, vendor_folder):
|
||||
"""
|
||||
Checks JSON files in the vendor folder for missing or empty 'compatible_printers'
|
||||
when 'instantiation' is flagged as true.
|
||||
|
||||
In the OrcaFilamentLibrary 'compatible_printers' is optional: a profile without it is generic and
|
||||
offered on every printer, while a profile that lists printers supersedes the generic one there.
|
||||
|
||||
Parameters:
|
||||
vendor (str): The vendor name the folder belongs to.
|
||||
vendor_folder (str or Path): The directory to search for JSON profile files.
|
||||
|
||||
Returns:
|
||||
@@ -115,7 +119,7 @@ def check_filament_compatible_printers(vendor_folder):
|
||||
|
||||
for profile in profiles.values():
|
||||
instantiation = str(profile['content'].get("instantiation", "")).lower() == "true"
|
||||
if instantiation:
|
||||
if instantiation and vendor != 'OrcaFilamentLibrary':
|
||||
try:
|
||||
compatible_printers = get_property(profile, "compatible_printers")
|
||||
if not compatible_printers or (isinstance(compatible_printers, list) and not compatible_printers):
|
||||
@@ -571,7 +575,7 @@ def main():
|
||||
vendor_path = profiles_dir / vendor_name
|
||||
|
||||
if args.check_filaments or not (args.check_materials and not args.check_filaments):
|
||||
errors_found += check_filament_compatible_printers(vendor_path / "filament")
|
||||
errors_found += check_filament_compatible_printers(vendor_name, vendor_path / "filament")
|
||||
|
||||
if args.check_materials:
|
||||
new_errors, new_warnings = check_machine_default_materials(profiles_dir, vendor_name)
|
||||
|
||||
@@ -31,8 +31,9 @@ namespace AABBTreeLines {
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType& squared_distance) const
|
||||
{
|
||||
Vec<LineType::Dim, typename LineType::Scalar> nearest_point;
|
||||
Vec<LineType::Dim, typename LineType::Scalar> cast_origin = origin.template cast<typename LineType::Scalar>();
|
||||
const LineType& line = lines[primitive_index];
|
||||
squared_distance = line_alg::distance_to_squared(line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
|
||||
squared_distance = line_alg::distance_to_squared(line, cast_origin, &nearest_point);
|
||||
return nearest_point.template cast<ScalarType>();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
BoundingBoxBase(It from, It to) : BoundingBoxBase()
|
||||
{ construct(*this, from, to); }
|
||||
|
||||
BoundingBoxBase(const PointsType &points)
|
||||
|
||||
+6
-10
@@ -349,7 +349,7 @@ static ExPolygons make_brim_ears_auto(const ExPolygons& obj_expoly, coord_t size
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
|
||||
static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim)
|
||||
static ExPolygons make_brim_ears(const PrintObject* object)
|
||||
{
|
||||
ExPolygons mouse_ears_ex;
|
||||
BrimPoints brim_ear_points = object->model_object()->brim_points;
|
||||
@@ -373,12 +373,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
Vec3f world_pos = pt.transform(trsf.get_matrix());
|
||||
if ( world_pos.z() > 0) continue;
|
||||
Polygon point_round;
|
||||
float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2;
|
||||
if (is_outer_brim) {
|
||||
double flowWidthScale = flowWidth / SCALING_FACTOR;
|
||||
brim_width = floor(brim_width / flowWidthScale / 2) * flowWidthScale * 2;
|
||||
}
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
const coord_t size_ear = scale_(pt.head_front_radius);
|
||||
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
|
||||
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
|
||||
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
|
||||
@@ -452,7 +447,8 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
bool has_brim_auto = object->config().brim_type == btAutoBrim;
|
||||
const bool use_auto_brim_ears = object->config().brim_type == btEar;
|
||||
const bool use_brim_ears = object->config().brim_type == btPainted;
|
||||
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_auto_brim_ears || use_brim_ears;
|
||||
const bool use_inner_brim_ears = (use_auto_brim_ears || use_brim_ears) && !object->config().brim_ears_outer_only.value;
|
||||
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_inner_brim_ears;
|
||||
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
|
||||
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
|
||||
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
|
||||
@@ -531,7 +527,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
|
||||
ExPolygons outerExpoly;
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true);
|
||||
outerExpoly = make_brim_ears(object);
|
||||
//outerExpoly = offset_ex(outerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width_mod - brim_offset - flow.scaled_spacing());
|
||||
@@ -545,7 +541,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
ExPolygons outerExpoly;
|
||||
auto innerExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, false);
|
||||
outerExpoly = make_brim_ears(object);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
outerExpoly = make_brim_ears_auto(offset_ex(ex_poly_holes_reversed, -brim_offset), size_ear, ear_detection_length, brim_ears_max_angle, false);
|
||||
|
||||
@@ -278,6 +278,9 @@ struct SurfaceFillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" wave.
|
||||
bool gyroid_optimized = false;
|
||||
|
||||
// Orca: corner smoothing factor in the range [0, 1].
|
||||
double smooth_factor { 0. };
|
||||
|
||||
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
|
||||
bool separated_infills{false};
|
||||
|
||||
@@ -316,6 +319,7 @@ struct SurfaceFillParams
|
||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
|
||||
RETURN_COMPARE_NON_EQUAL(smooth_factor);
|
||||
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
|
||||
RETURN_COMPARE_NON_EQUAL(separated_infills);
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, fill_order);
|
||||
@@ -348,6 +352,7 @@ struct SurfaceFillParams
|
||||
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
|
||||
this->separated_infills == rhs.separated_infills &&
|
||||
this->gyroid_optimized == rhs.gyroid_optimized &&
|
||||
this->smooth_factor == rhs.smooth_factor &&
|
||||
this->fill_order == rhs.fill_order;
|
||||
}
|
||||
};
|
||||
@@ -964,6 +969,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
|
||||
region_config.sparse_infill_rotate_template.value);
|
||||
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
|
||||
|
||||
// Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
|
||||
// FillHilbertCurve::generate clamps and validates the value itself.
|
||||
if (params.pattern == ipHilbertCurve)
|
||||
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
|
||||
} else {
|
||||
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
|
||||
const bool bottom_layer_direction_set = surface.is_bottom() && region_config.bottom_layer_direction.value >= 0.;
|
||||
@@ -1328,6 +1338,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
|
||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||
|
||||
// BBS
|
||||
params.flow = surface_fill.params.flow;
|
||||
@@ -1569,6 +1580,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
|
||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||
params.multiline = surface_fill.params.multiline;
|
||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
|
||||
@@ -1857,12 +1857,12 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI
|
||||
const bool first = graph.first(cp);
|
||||
int extend_next_idx = -1;
|
||||
int extend_prev_idx = -1;
|
||||
coord_t dist_y_prev;
|
||||
coord_t dist_y_next;
|
||||
double arc_len_prev;
|
||||
double arc_len_next;
|
||||
coord_t dist_y_prev = 0;
|
||||
coord_t dist_y_next = 0;
|
||||
double arc_len_prev = 0;
|
||||
double arc_len_next = 0;
|
||||
|
||||
if (! graph.next_vertical(cp)){
|
||||
if (! graph.next_vertical(cp)) {
|
||||
size_t i = cp.point_idx;
|
||||
size_t j = next_idx_modulo(i, contour);
|
||||
while (j != cp.next_on_contour->point_idx) {
|
||||
|
||||
@@ -82,6 +82,9 @@ struct FillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" variant.
|
||||
bool gyroid_optimized { false };
|
||||
|
||||
// Orca: corner smoothing factor in the range [0, 1].
|
||||
double smooth_factor { 0. };
|
||||
|
||||
// For Lateral lattice
|
||||
coordf_t lateral_lattice_angle_1 { 0.f };
|
||||
coordf_t lateral_lattice_angle_2 { 0.f };
|
||||
|
||||
@@ -114,12 +114,12 @@ void FillPlanePath::_fill_surface_single(
|
||||
// Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box.
|
||||
snug_bounding_box.translate(-shift.x(), -shift.y());
|
||||
InfillPolylineClipper output(snug_bounding_box, distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
|
||||
polyline.points = std::move(output.result());
|
||||
} else {
|
||||
// Filling in a snug bounding box, no need to clip.
|
||||
InfillPolylineOutput output(distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
|
||||
polyline.points = std::move(output.result());
|
||||
}
|
||||
}
|
||||
@@ -288,6 +288,147 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x,
|
||||
}
|
||||
}
|
||||
|
||||
using QuinticBezier = std::array<Vec2d, 6>;
|
||||
|
||||
static bool is_bezier_flat(const QuinticBezier &curve, const double deviation)
|
||||
{
|
||||
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
|
||||
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
|
||||
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
|
||||
// comparing squared values avoids a square root.
|
||||
const Vec2d chord = curve.back() - curve.front();
|
||||
const double chord_length_sq = chord.squaredNorm();
|
||||
const double max_cross_sq = deviation * deviation * chord_length_sq;
|
||||
|
||||
for (size_t i = 1; i + 1 < curve.size(); ++i) {
|
||||
const Vec2d offset = curve[i] - curve.front();
|
||||
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
|
||||
if (cross * cross > max_cross_sq)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
|
||||
{
|
||||
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
|
||||
// control point to the left half and one to the right half; the latter is filled backwards to keep
|
||||
// both resulting control polygons in their original parameter direction.
|
||||
QuinticBezier subdivision = curve;
|
||||
left.front() = subdivision.front();
|
||||
right.back() = subdivision.back();
|
||||
for (size_t level = 1; level < curve.size(); ++level) {
|
||||
for (size_t i = 0; i + level < curve.size(); ++i)
|
||||
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
|
||||
left[level] = subdivision.front();
|
||||
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
|
||||
}
|
||||
}
|
||||
|
||||
static void flatten_bezier(const QuinticBezier &curve, const double deviation, std::vector<Vec2d> &output)
|
||||
{
|
||||
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
|
||||
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
|
||||
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
|
||||
static constexpr size_t max_depth = 16;
|
||||
|
||||
std::vector<QuinticBezier> subcurves(2);
|
||||
subdivide_bezier(curve, subcurves[0], subcurves[1]);
|
||||
|
||||
for (size_t depth = 1; depth < max_depth; ++depth) {
|
||||
bool all_flat = true;
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
if (!is_bezier_flat(c, deviation)) {
|
||||
all_flat = false;
|
||||
break;
|
||||
}
|
||||
if (all_flat)
|
||||
break;
|
||||
std::vector<QuinticBezier> finer(subcurves.size() * 2);
|
||||
for (size_t i = 0; i < subcurves.size(); ++i)
|
||||
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
|
||||
subcurves = std::move(finer);
|
||||
}
|
||||
|
||||
// The curve start is deliberately omitted so consecutive curve pieces can share it without duplication.
|
||||
output.reserve(output.size() + subcurves.size());
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
output.emplace_back(c.back());
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_smooth_hilbert_curve(
|
||||
coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const double corner_distance, Output &output)
|
||||
{
|
||||
// A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
|
||||
// generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
|
||||
// clipper or the later region intersection removes the padded part of the traversal.
|
||||
size_t sz = 2;
|
||||
const size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
|
||||
while (sz < sz0)
|
||||
sz <<= 1;
|
||||
|
||||
const size_t point_count = sz * sz;
|
||||
output.reserve(point_count);
|
||||
|
||||
// The caller normalizes resolution to the unit Hilbert grid; retain a finite positive tolerance
|
||||
// if this helper is invoked with an invalid resolution.
|
||||
const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
|
||||
// Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
|
||||
// At each end, the first three control points are collinear and equally spaced: the tangent follows
|
||||
// the adjoining straight leg and the second derivative is zero. The endpoint curvature is therefore
|
||||
// zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
|
||||
// it only once to the requested chordal-deviation tolerance.
|
||||
const QuinticBezier corner_curve {{
|
||||
{-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
|
||||
{0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
|
||||
}};
|
||||
std::vector<Vec2d> curve_coefficients;
|
||||
flatten_bezier(corner_curve, deviation, curve_coefficients);
|
||||
|
||||
auto translated_point = [min_x, min_y](size_t idx) {
|
||||
Point p = hilbert_n_to_xy(idx);
|
||||
return Point(p.x() + min_x, p.y() + min_y);
|
||||
};
|
||||
auto to_vec2d = [](const Point &p) { return Vec2d(double(p.x()), double(p.y())); };
|
||||
bool has_last_output = false;
|
||||
Vec2d last_output;
|
||||
// Fully smoothed adjacent corners may meet at the same segment midpoint. Suppress such duplicates
|
||||
// to avoid emitting zero-length extrusion segments.
|
||||
auto add_point = [&output, &has_last_output, &last_output](const Vec2d &point) {
|
||||
if (!has_last_output || point.x() != last_output.x() || point.y() != last_output.y()) {
|
||||
output.add_point(point);
|
||||
last_output = point;
|
||||
has_last_output = true;
|
||||
}
|
||||
};
|
||||
|
||||
Vec2d previous = to_vec2d(translated_point(0));
|
||||
Vec2d corner = to_vec2d(translated_point(1));
|
||||
add_point(previous);
|
||||
// Replace each non-collinear Hilbert vertex by the canonical curve expressed in the local basis of
|
||||
// its incoming and outgoing unit vectors. Collinear vertices remain part of the straight polyline.
|
||||
for (size_t i = 1; i + 1 < point_count; ++i) {
|
||||
const Vec2d next = to_vec2d(translated_point(i + 1));
|
||||
const Vec2d incoming = (corner - previous).normalized();
|
||||
const Vec2d outgoing = (next - corner).normalized();
|
||||
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
|
||||
|
||||
if (std::abs(cross) < EPSILON) {
|
||||
add_point(corner);
|
||||
} else {
|
||||
add_point(corner - corner_distance * incoming);
|
||||
for (const Vec2d &coefficient : curve_coefficients)
|
||||
add_point(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
|
||||
}
|
||||
|
||||
previous = corner;
|
||||
corner = next;
|
||||
}
|
||||
add_point(corner);
|
||||
}
|
||||
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
@@ -296,6 +437,24 @@ void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coo
|
||||
generate_hilbert_curve(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output)
|
||||
{
|
||||
const double smooth_factor = std::isfinite(params.smooth_factor) ?
|
||||
std::clamp(params.smooth_factor, 0., 1.) : 0.;
|
||||
if (smooth_factor == 0.) {
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
return;
|
||||
}
|
||||
|
||||
const double corner_distance = 0.5 * smooth_factor;
|
||||
if (output.clips())
|
||||
generate_smooth_hilbert_curve(
|
||||
min_x, min_y, max_x, max_y, resolution, corner_distance, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
|
||||
{
|
||||
|
||||
@@ -53,6 +53,11 @@ protected:
|
||||
friend class InfillPolylineClipper;
|
||||
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0;
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams & /* params */, InfillPolylineOutput &output)
|
||||
{
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
}
|
||||
};
|
||||
|
||||
class FillArchimedeanChords : public FillPlanePath
|
||||
@@ -75,6 +80,8 @@ public:
|
||||
protected:
|
||||
bool centered() const override { return false; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
class FillOctagramSpiral : public FillPlanePath
|
||||
|
||||
+20
-17
@@ -2917,6 +2917,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
|
||||
print.get_layered_nozzle_group_result());
|
||||
const bool is_bbl_printers = print.is_BBL_printer();
|
||||
const bool skip_config_block = print.config().gcode_skip_config_block;
|
||||
const WipeTowerType wipe_tower_type = print.wipe_tower_type();
|
||||
m_calib_config.clear();
|
||||
// resets analyzer's tracking data
|
||||
@@ -3092,7 +3093,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// as configuration key / value pairs to be parsable by older versions of
|
||||
// PrusaSlicer G-code viewer.
|
||||
{
|
||||
if (is_bbl_printers) {
|
||||
if (is_bbl_printers && !skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
@@ -4119,23 +4120,25 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
|
||||
.c_str());
|
||||
file.write("\n");
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
if (!skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple info
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
|
||||
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
|
||||
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
|
||||
|
||||
//SF TODO
|
||||
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
|
||||
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
// SoftFever: write compatiple info
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
|
||||
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
|
||||
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
|
||||
|
||||
//SF TODO
|
||||
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
|
||||
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
} // !skip_config_block
|
||||
|
||||
}
|
||||
file.write("\n");
|
||||
|
||||
@@ -185,7 +185,7 @@ struct LayerResult {
|
||||
// It is used for the pressure equalizer because it needs to buffer one layer back.
|
||||
bool nop_layer_result { false };
|
||||
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<size_t>::max(), false, false, true}; }
|
||||
};
|
||||
|
||||
class GCode {
|
||||
|
||||
@@ -5926,8 +5926,11 @@ void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line)
|
||||
GCodeReader::GCodeLine g10;
|
||||
g10.set(Axis::E, -this->m_parser.config().retraction_length.get_at(m_extruder_id));
|
||||
g10.set(Axis::F, this->m_parser.config().retraction_speed.get_at(m_extruder_id) * 60);
|
||||
//Orca: Firmware retract emulation must not change the modal G1 feedrate.
|
||||
const float feedrate = m_feedrate;
|
||||
--m_g1_line_id;
|
||||
process_G1(g10);
|
||||
m_feedrate = feedrate;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
|
||||
@@ -5936,8 +5939,11 @@ void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
|
||||
GCodeReader::GCodeLine g11;
|
||||
g11.set(Axis::E, this->m_parser.config().retraction_length.get_at(m_extruder_id) + this->m_parser.config().retract_restart_extra.get_at(m_extruder_id));
|
||||
g11.set(Axis::F, this->m_parser.config().deretraction_speed.get_at(m_extruder_id) * 60);
|
||||
// Orca: Firmware unretract emulation must not change the modal G1 feedrate.
|
||||
const float feedrate = m_feedrate;
|
||||
--m_g1_line_id;
|
||||
process_G1(g11);
|
||||
m_feedrate = feedrate;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "I18N.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry/ArcWelder.hpp"
|
||||
#include "Line.hpp"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
@@ -1018,45 +1019,48 @@ std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, c
|
||||
}
|
||||
|
||||
if (!this->config.enable_arc_fitting) { // Orca: if arc fitting is disabled, approximate the arc with small linear segments
|
||||
std::ostringstream oss;
|
||||
const double z_start = m_pos(2); // starting Z height
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Determine number of segments based on Resolution
|
||||
// --------------------------------------------------------------------
|
||||
const double ref_resolution = 0.01; // reference resolution in mm
|
||||
const double ref_segments = 8.0; // reference number of segments at reference resolution
|
||||
|
||||
// number of linear segments to use for approximating the arc, clamp between 4 and 16
|
||||
const int segments = std::clamp(int(std::round(ref_segments * (ref_resolution / m_resolution))), 4, 16);
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
const double px = m_pos(0) - m_x_offset; // take plate offset into consideration
|
||||
const double py = m_pos(1) - m_y_offset; // take plate offset into consideration
|
||||
const double cx = px + ij_offset(0); // center x
|
||||
const double cy = py + ij_offset(1); // center y
|
||||
const double radius = ij_offset.norm(); // radius
|
||||
|
||||
// Number of linear segments approximating the circle, chosen so that a chord never deviates
|
||||
// from the true arc by more than the slicing resolution. A resolution of 0 means "no
|
||||
// simplification", which has no finite segment count, so it takes the upper bound.
|
||||
constexpr size_t min_segments = 8; // keep a small spiral visibly round
|
||||
constexpr size_t max_segments = 128; // bound the emitted G-code
|
||||
const int segments = int(m_resolution > 0. ?
|
||||
std::clamp(Geometry::ArcWelder::arc_discretization_steps(radius, 2. * M_PI, m_resolution), min_segments, max_segments) :
|
||||
max_segments);
|
||||
|
||||
const double a0 = std::atan2(py - cy, px - cx); // start angle
|
||||
const double delta = 2.0 * M_PI; // CCW full circle
|
||||
|
||||
if (full_gcode_comment)
|
||||
oss << ";" << comment << "\n";
|
||||
auto emit_point = [&output](const Vec3d &point) {
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
output += w.string();
|
||||
};
|
||||
|
||||
oss << "G1 F" << (speed * 60.0) << "\n"; // set feedrate
|
||||
output.reserve(size_t(segments) * 40); // ~40 characters per emitted G1 line
|
||||
|
||||
GCodeG1Formatter w; // set feedrate
|
||||
w.emit_f(speed * 60.0);
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
output += w.string();
|
||||
|
||||
// approximate the arc with small linear segments (without the last point which is added later to ensure exactness)
|
||||
for (int i = 1; i < segments; ++i) {
|
||||
double t = double(i) / segments; // parametric position along arc
|
||||
double a = a0 + delta * t; // CCW arc param
|
||||
double x = cx + radius * std::cos(a); // point on circle
|
||||
double y = cy + radius * std::sin(a); // point on circle
|
||||
double zz = z_start + (z - z_start) * t; // interpolated Z height
|
||||
|
||||
oss << "G1 X" << x << " Y" << y << " Z" << zz << "\n";
|
||||
const double t = double(i) / segments; // parametric position along arc
|
||||
const double a = a0 + 2. * M_PI * t; // CCW arc param, full circle
|
||||
emit_point(Vec3d(cx + radius * std::cos(a), // point on circle
|
||||
cy + radius * std::sin(a),
|
||||
z_start + (z - z_start) * t)); // interpolated Z height
|
||||
}
|
||||
|
||||
oss << "G1 X" << px << " Y" << py << " Z" << z << "\n"; // final point to ensure exactness
|
||||
output = oss.str();
|
||||
emit_point(Vec3d(px, py, z)); // final point to ensure exactness
|
||||
} else { // Orca: if arc fitting is enabled emit a G2/G3 command for the spiral lift
|
||||
output = std::string("G17") + (full_gcode_comment ? " ; XY plane for arc\n" : "\n");
|
||||
|
||||
|
||||
@@ -1977,7 +1977,7 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
|
||||
if (!unsupported.empty()) {
|
||||
//remove small overhangs
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
|
||||
ExPolygons unsupported_filtered = opening_ex(unsupported, perimeter_spacing);
|
||||
|
||||
if (!unsupported_filtered.empty()) {
|
||||
//to_draw.insert(to_draw.end(), last.begin(), last.end());
|
||||
@@ -2090,35 +2090,40 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
//TODO: add other polys as holes inside this one (-margin)
|
||||
} else { // if(this->config->counterbore_hole_bridging.value == chbBridges)
|
||||
// Orca: Partial counterbore bridging is mask-based. Preserve the supported
|
||||
// remainder (`last`) and use simplified BridgeDetector coverage to derive the
|
||||
// remainder and use simplified BridgeDetector coverage to derive the
|
||||
// bridgeable counterbore span. The span is grown from supported material,
|
||||
// shrunk back, stripped from `last`, and expanded back. It is then prevented
|
||||
// from intruding deeper into `last` than the explicit anchor overlap.
|
||||
// Finally, add the allowed anchor band from `last` then remove the
|
||||
// shrunk back, stripped from the remaining normal surface, and expanded back.
|
||||
// It is then prevented from intruding deeper into it than the explicit anchor overlap.
|
||||
// Finally, add the allowed anchor band from it then remove the
|
||||
// narrow hole-side wall contact, which must remain unbridgeable.
|
||||
|
||||
last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
const ExPolygons remaining = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons bridgeable_filtered;
|
||||
|
||||
for (ExPolygon& poly : bridgeable) {
|
||||
poly.simplify(perimeter_spacing, &bridgeable_filtered);
|
||||
}
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width);
|
||||
|
||||
// Get rid of coarseness of the resulted bridgeable area by using the original supported area as reference.
|
||||
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
|
||||
bridgeable_filtered = union_ex(offset_ex(last, perimeter_spacing), bridgeable_filtered);
|
||||
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
|
||||
bridgeable_filtered = union_ex(offset_ex(remaining, perimeter_spacing), bridgeable_filtered);
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, last, ApplySafetyOffset::Yes);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, remaining, ApplySafetyOffset::Yes);
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, perimeter_spacing); // filter noise from the diff_ex
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area
|
||||
// Safety measure: Keep the bridge mask from intruding deeper into the
|
||||
// supported anchor region (`last`) than the explicit anchor overlap.
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(last, -bridge_anchor_offset));
|
||||
// supported anchor region than the explicit anchor overlap.
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(remaining, -bridge_anchor_offset));
|
||||
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(last, offset_ex(unsupported_filtered, bridge_anchor_offset));
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(remaining, offset_ex(unsupported_filtered, bridge_anchor_offset));
|
||||
unsupported_filtered = union_ex(bridgeable_filtered, bridge_anchor_areas); // add bridge anchor
|
||||
unsupported_filtered = opening_ex(unsupported_filtered, bridge_anchor_offset); // remove anchor area from hole-side walls, it must remain unbridgeable
|
||||
|
||||
// update 'last' only if we have a valid bridgeable area, otherwise we will lose the original unsupported area
|
||||
if (!unsupported_filtered.empty())
|
||||
last = remaining;
|
||||
// TODO: Fix the case with thin outer walls around the bridge (1~2 walls) where classic wall
|
||||
// might generate two walls in a tiny space or non at all if "Detect thin walls" is not activated
|
||||
}
|
||||
|
||||
@@ -1791,6 +1791,8 @@ namespace client
|
||||
// from UTF8 to UTF16 don't bail out.
|
||||
msg += boost::nowide::narrow(boost::nowide::widen(error_line));
|
||||
msg += '\n';
|
||||
// The error dialog (MsgDialog.cpp) renders this excerpt monospaced. It recognizes a source
|
||||
// line directly above a caret line of spaces and a single '^'.
|
||||
for (size_t i = 0; i < error_pos; ++ i)
|
||||
msg += ' ';
|
||||
msg += "^\n";
|
||||
|
||||
@@ -1037,6 +1037,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"fill_multiline",
|
||||
"gyroid_optimized",
|
||||
"sparse_infill_pattern",
|
||||
"sparse_infill_smooth_factor",
|
||||
"lateral_lattice_angle_1",
|
||||
"lateral_lattice_angle_2",
|
||||
"infill_overhang_angle",
|
||||
@@ -1090,7 +1091,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
|
||||
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
|
||||
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
|
||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "brim_ears_outer_only", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
|
||||
// BBS
|
||||
@@ -1405,7 +1406,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
static std::vector<std::string> s_Preset_printer_options {
|
||||
"printer_technology",
|
||||
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
|
||||
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||
"gcode_skip_config_block", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
|
||||
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
|
||||
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
|
||||
@@ -3785,12 +3786,14 @@ void PresetCollection::update_library_profile_excluded_from()
|
||||
}
|
||||
|
||||
// Check all presets that has the same alias as the filament presets with empty compatible_printers in Orca Filament Library.
|
||||
// A printer specific profile supersedes the generic one, no matter whether it lives in a vendor bundle or in the
|
||||
// library itself.
|
||||
for (const Preset& preset : m_presets) {
|
||||
if (preset.vendor == nullptr || preset.vendor->name == PresetBundle::ORCA_FILAMENT_LIBRARY)
|
||||
if (preset.vendor == nullptr)
|
||||
continue;
|
||||
|
||||
const auto* compatible_printers = dynamic_cast<const ConfigOptionStrings*>(preset.config.option("compatible_printers"));
|
||||
// All profiles in concrete vendor profile shouldn't have empty compatible_printers, but here we check it for safety.
|
||||
// Profiles with empty compatible_printers are the generic ones, they never supersede anything.
|
||||
if (compatible_printers == nullptr || compatible_printers->values.empty())
|
||||
continue;
|
||||
auto itr = excluded_froms.find(preset.alias);
|
||||
|
||||
@@ -1939,6 +1939,13 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1));
|
||||
|
||||
def = this->add("brim_ears_outer_only", coBool);
|
||||
def->label = L("Brim ears outer only");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("Generate mouse ears only on the outer contour of the model, excluding holes and enclosed sections.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("compatible_printers", coStrings);
|
||||
def->label = L("Select printers");
|
||||
def->mode = comAdvanced;
|
||||
@@ -3459,6 +3466,18 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipCrossHatch));
|
||||
|
||||
def = this->add("sparse_infill_smooth_factor", coPercent);
|
||||
def->label = L("Sparse infill smooth factor");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original right-angle path, "
|
||||
"while 100% produces the largest possible curves between adjacent infill lines. "
|
||||
"Currently applies only to the Hilbert Curve.");
|
||||
def->sidetext = "%";
|
||||
def->min = 0;
|
||||
def->max = 100;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionPercent(0));
|
||||
|
||||
def = this->add("top_surface_acceleration", coFloats);
|
||||
def->label = L("Top surface");
|
||||
def->category = L("Speed");
|
||||
@@ -4242,6 +4261,15 @@ void PrintConfigDef::init_fff_params()
|
||||
def->readonly = false;
|
||||
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfMarlinLegacy));
|
||||
|
||||
def = this->add("gcode_skip_config_block", coBool);
|
||||
def->label = L("Skip G-code config block");
|
||||
def->tooltip = L("Do not write the CONFIG_BLOCK (slicer configuration key/value pairs) into the G-code file. "
|
||||
"This can help with printers whose firmware crashes when parsing these comment lines "
|
||||
"(e.g. Anycubic go-klipper). Note: the G-code file will no longer contain slicer settings, "
|
||||
"so importing it back into OrcaSlicer will not restore the configuration.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("pellet_modded_printer", coBool);
|
||||
def->label = L("Pellet Modded Printer");
|
||||
def->tooltip = L("Enable this option if your printer uses pellets instead of filaments.");
|
||||
@@ -4275,7 +4303,7 @@ void PrintConfigDef::init_fff_params()
|
||||
"slow down.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(0));
|
||||
|
||||
|
||||
//BBS
|
||||
def = this->add("infill_combination", coBool);
|
||||
def->label = L("Infill combination");
|
||||
|
||||
@@ -1082,6 +1082,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, brim_width))
|
||||
((ConfigOptionFloat, brim_ears_detection_length))
|
||||
((ConfigOptionFloat, brim_ears_max_angle))
|
||||
((ConfigOptionBool, brim_ears_outer_only))
|
||||
((ConfigOptionFloat, skirt_start_angle))
|
||||
((ConfigOptionBool, bridge_no_support))
|
||||
((ConfigOptionFloat, elefant_foot_compensation))
|
||||
@@ -1264,6 +1265,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionString, sparse_infill_rotate_template))
|
||||
((ConfigOptionPercent, sparse_infill_density))
|
||||
((ConfigOptionEnum<InfillPattern>, sparse_infill_pattern))
|
||||
((ConfigOptionPercent, sparse_infill_smooth_factor))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_1))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_2))
|
||||
((ConfigOptionFloat, infill_overhang_angle))
|
||||
@@ -1545,7 +1547,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, gcode_add_line_number))
|
||||
((ConfigOptionBool, bbl_bed_temperature_gcode))
|
||||
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
|
||||
|
||||
((ConfigOptionBool, gcode_skip_config_block))
|
||||
((ConfigOptionFloat, time_cost))
|
||||
((ConfigOptionString, layer_change_gcode))
|
||||
((ConfigOptionString, time_lapse_gcode))
|
||||
|
||||
@@ -1175,6 +1175,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "brim_type"
|
||||
|| opt_key == "brim_ears_max_angle"
|
||||
|| opt_key == "brim_ears_detection_length"
|
||||
|| opt_key == "brim_ears_outer_only"
|
||||
// BBS: brim generation depends on printing speed
|
||||
|| opt_key == "outer_wall_speed"
|
||||
|| opt_key == "small_perimeter_speed"
|
||||
@@ -1409,6 +1410,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_overhang_angle") {
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "sparse_infill_pattern"
|
||||
|| opt_key == "sparse_infill_smooth_factor"
|
||||
|| opt_key == "symmetric_infill_y_axis"
|
||||
|| opt_key == "infill_shift_step"
|
||||
|| opt_key == "sparse_infill_rotate_template"
|
||||
|
||||
@@ -51,9 +51,4 @@
|
||||
// Enable extension of tool position imgui dialog to show actual speed profile
|
||||
#define ENABLE_ACTUAL_SPEED_DEBUG 1
|
||||
|
||||
// Disable layout inspector for public release
|
||||
#if BBL_RELEASE_TO_PUBLIC
|
||||
#define WXINSPECTOR_DISABLE
|
||||
#endif
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
||||
+64
-14
@@ -70,6 +70,9 @@ float FullTransparentModdifiedToFixAlpha = 0.3f;
|
||||
// value like 0.18f could not because in C++ (int)(0.18f * 255) == 45 however in OpenGL it renders this as 46
|
||||
// which breaks the `SelectMachineDialog::record_edge_pixels_data()` function!
|
||||
float FULL_BLACK_THRESHOLD = 0.2f;
|
||||
// Keep depth_tex away from texture unit 0 to avoid sampler-type aliasing with
|
||||
// shadow/environment samplers when realistic view is disabled.
|
||||
static constexpr int OUTLINE_DEPTH_TEX_UNIT = 5;
|
||||
|
||||
Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors)
|
||||
{
|
||||
@@ -518,6 +521,37 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glStencilMask(0xFF));
|
||||
glsafe(::glDisable(GL_STENCIL_TEST));
|
||||
// render the outline using depth buffer and discard the pixels that are not on the outline
|
||||
// The silhouette is resolved per sample in the shader (see DetectSilho in gouraud.fs/phong.fs).
|
||||
// That needs the GL 3.2 entry points and a shader that declares depth_tex as sampler2DMS, which
|
||||
// only the 140 ones do and only under GL_ARB_texture_multisample - so ask the compiled program
|
||||
// rather than the GL version, or a sampler2D ends up bound to a multisample texture.
|
||||
// Only the Arb branch below allocates a multisample texture, so keep the target consistent with it.
|
||||
const bool use_msaa_outline = framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb &&
|
||||
GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 2) &&
|
||||
shader->get_uniform_location("msaa_samples") >= 0;
|
||||
const GLenum depth_tex_target = use_msaa_outline ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
||||
// Keep the depth texture off image unit 0. The object shaders leave shadow_map (and
|
||||
// environment_tex) at the default sampler value 0 whenever the shadow pass is skipped - which is
|
||||
// the case with realistic view off - and GL forbids two sampler types referring to the same image
|
||||
// unit. A sampler2DMS on unit 0 then makes every draw fail with INVALID_OPERATION on drivers that
|
||||
// enforce it (Mesa), i.e. the model disappears entirely. Unit 5 is unused (shadow_map takes 4).
|
||||
const int depth_tex_unit = OUTLINE_DEPTH_TEX_UNIT;
|
||||
int aa_samples = 1;
|
||||
if (use_msaa_outline) {
|
||||
if (const AppConfig* app_config = GUI::wxGetApp().app_config; app_config != nullptr) {
|
||||
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
|
||||
if (value == "2" || value == "4" || value == "8" || value == "16")
|
||||
aa_samples = ::atoi(value.c_str());
|
||||
}
|
||||
// Never request more samples than the driver supports for depth textures (a 1-sample texture
|
||||
// is used when MSAA is disabled, keeping a single code path for the sampler2DMS shader).
|
||||
GLint max_samples = 1;
|
||||
glsafe(::glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &max_samples));
|
||||
if (aa_samples > max_samples)
|
||||
aa_samples = max_samples < 1 ? 1 : max_samples;
|
||||
if (aa_samples < 1)
|
||||
aa_samples = 1;
|
||||
}
|
||||
// 1st. render pass, render the model into a separate render target that has only depth buffer
|
||||
GLuint depth_fbo = 0;
|
||||
GLuint depth_tex = 0;
|
||||
@@ -525,21 +559,26 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glGenFramebuffers(1, &depth_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
if (use_msaa_outline) {
|
||||
// Multisample textures do not take filter/wrap parameters.
|
||||
glsafe(::glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, aa_samples, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), GL_TRUE));
|
||||
} else {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
}
|
||||
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_tex_target, depth_tex, 0));
|
||||
} else {
|
||||
glsafe(::glGenFramebuffersEXT(1, &depth_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fbo));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
@@ -550,12 +589,15 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
}
|
||||
// Unbind before drawing: the texture is this framebuffer's depth attachment, so leaving it bound
|
||||
// to a sampled unit would be a feedback loop.
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
model.render(shader);
|
||||
else
|
||||
model.render(this->tverts_range, shader);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
// 2nd. render pass, just a normal render with the depth buffer passed as a texture
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
@@ -565,13 +607,17 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
}
|
||||
shader->set_uniform("is_outline", true);
|
||||
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
shader->set_uniform("depth_tex", 0);
|
||||
shader->set_uniform("msaa_samples", aa_samples);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("depth_tex", depth_tex_unit);
|
||||
simple_render(shader, model_objects, colors);
|
||||
|
||||
// Some clean up to do
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("is_outline", false);
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
@@ -1075,6 +1121,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
|
||||
const float support_normal_z = get_selection_support_normal_z();
|
||||
|
||||
// Prime depth_tex on every frame so non-outline draws do not keep the
|
||||
// default sampler unit 0, which can conflict with other sampler types.
|
||||
shader->set_uniform("depth_tex", OUTLINE_DEPTH_TEX_UNIT);
|
||||
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
||||
if (type == ERenderType::Transparent) {
|
||||
|
||||
@@ -70,6 +70,12 @@ void ConfigManipulation::toggle_line(const std::string& opt_key, const bool togg
|
||||
cb_toggle_line(opt_key, toggle, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::set_option_label(const std::string& opt_key, const wxString& label, int opt_index)
|
||||
{
|
||||
if (cb_set_option_label)
|
||||
cb_set_option_label(opt_key, label, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) {
|
||||
if (is_msg_dlg_already_exist)
|
||||
return;
|
||||
@@ -707,6 +713,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
||||
toggle_line("sparse_infill_smooth_factor", pattern == ipHilbertCurve);
|
||||
toggle_field("top_surface_pattern", has_top_shell);
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell_layers);
|
||||
@@ -807,14 +814,19 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("outer_wall_filament_id", have_perimeters || have_brim);
|
||||
toggle_field("inner_wall_filament_id", have_perimeters || have_brim);
|
||||
|
||||
bool have_brim_ear = (config->opt_enum<BrimType>("brim_type") == btEar);
|
||||
const BrimType brim_type = config->opt_enum<BrimType>("brim_type");
|
||||
const bool have_auto_brim_ear = brim_type == btEar;
|
||||
const bool have_painted_brim_ear = brim_type == btPainted;
|
||||
set_option_label("brim_width", have_auto_brim_ear ? _L("Brim ear radius") : _L("Brim width"));
|
||||
const auto brim_width = config->opt_float("brim_width");
|
||||
// disable brim_ears_max_angle and brim_ears_detection_length if brim_width is 0
|
||||
// Automatic brim ear settings require a non-zero brim width.
|
||||
toggle_field("brim_ears_max_angle", brim_width > 0.0f);
|
||||
toggle_field("brim_ears_detection_length", brim_width > 0.0f);
|
||||
// hide brim_ears_max_angle and brim_ears_detection_length if brim_ear is not selected
|
||||
toggle_line("brim_ears_max_angle", have_brim_ear);
|
||||
toggle_line("brim_ears_detection_length", have_brim_ear);
|
||||
// Painted ears carry their own radius and do not depend on brim_width.
|
||||
toggle_field("brim_ears_outer_only", have_painted_brim_ear || brim_width > 0.0f);
|
||||
toggle_line("brim_ears_max_angle", have_auto_brim_ear);
|
||||
toggle_line("brim_ears_detection_length", have_auto_brim_ear);
|
||||
toggle_line("brim_ears_outer_only", have_auto_brim_ear || have_painted_brim_ear);
|
||||
|
||||
// Hide Elephant foot compensation layers if elefant_foot_compensation is not enabled
|
||||
toggle_line("elefant_foot_compensation_layers", config->opt_float("elefant_foot_compensation") > 0 || config->option<ConfigOptionPercent>("elefant_foot_layers_density")->get_abs_value(1.0f) < 1.0f);
|
||||
|
||||
@@ -29,6 +29,7 @@ class ConfigManipulation
|
||||
std::function<void()> load_config = nullptr;
|
||||
std::function<void (const std::string&, bool toggle, int opt_index)> cb_toggle_field = nullptr;
|
||||
std::function<void(const std::string &, bool toggle, int opt_index)> cb_toggle_line = nullptr;
|
||||
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr;
|
||||
// callback to propagation of changed value, if needed
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
|
||||
//BBS: change local config to const DynamicPrintConfig
|
||||
@@ -45,10 +46,12 @@ public:
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change,
|
||||
//BBS: change local config to DynamicPrintConfig
|
||||
const DynamicPrintConfig* local_config = nullptr,
|
||||
wxWindow* msg_dlg_parent = nullptr) :
|
||||
wxWindow* msg_dlg_parent = nullptr,
|
||||
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr) :
|
||||
load_config(load_config),
|
||||
cb_toggle_field(cb_toggle_field),
|
||||
cb_toggle_line(cb_toggle_line),
|
||||
cb_set_option_label(cb_set_option_label),
|
||||
cb_value_change(cb_value_change),
|
||||
m_msg_dlg_parent(msg_dlg_parent),
|
||||
local_config(local_config) {}
|
||||
@@ -58,6 +61,7 @@ public:
|
||||
load_config = nullptr;
|
||||
cb_toggle_field = nullptr;
|
||||
cb_toggle_line = nullptr;
|
||||
cb_set_option_label = nullptr;
|
||||
cb_value_change = nullptr;
|
||||
}
|
||||
|
||||
@@ -67,6 +71,7 @@ public:
|
||||
t_config_option_keys const &applying_keys() const;
|
||||
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
void toggle_line(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
void set_option_label(const std::string& field_key, const wxString& label, int opt_index = -1);
|
||||
|
||||
// FFF print
|
||||
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false, const bool is_plate_config = false);
|
||||
|
||||
@@ -1842,6 +1842,10 @@ void GLCanvas3D::enable_separator_toolbar(bool enable)
|
||||
m_separator_toolbar.set_enabled(enable);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::has_mouse_capture() const {
|
||||
return m_canvas != nullptr && m_canvas->HasCapture();
|
||||
}
|
||||
|
||||
void GLCanvas3D::zoom_to_bed()
|
||||
{
|
||||
BoundingBoxf3 box = m_bed.build_volume().bounding_volume();
|
||||
@@ -2182,7 +2186,7 @@ void GLCanvas3D::render(bool only_init)
|
||||
|
||||
// Negative coordinate means out of the window, likely because the window was deactivated.
|
||||
// In that case the tooltip should be hidden.
|
||||
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) {
|
||||
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0. || has_mouse_capture()) { // ORCA continue to capture mouse pos mid drag
|
||||
if (tooltip.empty())
|
||||
tooltip = m_layers_editing.get_tooltip(*this);
|
||||
|
||||
@@ -4170,6 +4174,23 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// BBS: single snapshot
|
||||
Plater::SingleSnapshot single(wxGetApp().plater());
|
||||
|
||||
#ifdef __WXMAC__
|
||||
// On macOS, the mouse key state is only present for mouse btn related events such as wxEVT_LEFT_DOWN.
|
||||
// For other events, all buttons are reported as non-pressed, such as window leaving event. This causes
|
||||
// imgui stopped responding if cursor moved out of window, such as
|
||||
// https://github.com/OrcaSlicer/OrcaSlicer/pull/14999#issuecomment-5151344759
|
||||
// We solve this by correcting the state of the event from the actual mouse state querying with `wxGetMouseState()`
|
||||
// so it works like on other platforms.
|
||||
{
|
||||
const auto state = wxGetMouseState();
|
||||
evt.SetLeftDown(state.LeftIsDown());
|
||||
evt.SetMiddleDown(state.MiddleIsDown());
|
||||
evt.SetRightDown(state.RightIsDown());
|
||||
evt.SetAux1Down(state.Aux1IsDown());
|
||||
evt.SetAux2Down(state.Aux2IsDown());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
const float scale = m_retina_helper->get_scale_factor();
|
||||
evt.SetX(evt.GetX() * scale);
|
||||
@@ -4183,11 +4204,27 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// ignore left up events coming from imgui windows and not processed by them
|
||||
m_mouse.ignore_left_up = true;
|
||||
m_tooltip.set_in_imgui(false);
|
||||
if (imgui->update_mouse_data(evt)) {
|
||||
|
||||
// while a non-ImGui drag is already in progress (gizmo grabber, object move, rectangle selection, layer editing),
|
||||
// don't let ImGui/ImGuizmo claim the event just because the cursor is hovering something like the navigator cube
|
||||
// that incorrectly suppresses the active drag's tooltip and can interrupt its processing. The active drag always takes priority.
|
||||
const bool other_drag_active = m_gizmos.is_dragging() || m_mouse.dragging || m_rectangle_selection.is_dragging() || m_layers_editing.state == LayersEditing::Editing;
|
||||
|
||||
if (imgui->update_mouse_data(evt) && !other_drag_active) {
|
||||
if ((evt.LeftDown() || (evt.Moving() && (evt.AltDown() || evt.ShiftDown()))) && m_canvas != nullptr)
|
||||
m_canvas->SetFocus();
|
||||
m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast<double>();
|
||||
m_tooltip.set_in_imgui(true);
|
||||
|
||||
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
const bool imgui_dragging_active = (GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0) || m_navigator_dragging;
|
||||
if (!has_mouse_capture() && imgui_dragging_active)
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// release capture as soon as the button goes up
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
render();
|
||||
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
||||
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
|
||||
@@ -4284,6 +4321,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_main_toolbar.on_mouse(evt2, *this);
|
||||
}
|
||||
|
||||
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
if (!has_mouse_capture() && evt.LeftIsDown() && m_gizmos.is_dragging())
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
@@ -4389,6 +4430,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// Start editing the layer height.
|
||||
m_layers_editing.state = LayersEditing::Editing;
|
||||
_perform_layer_editing_action(&evt);
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -4402,6 +4446,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin) {
|
||||
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -4469,6 +4517,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_mouse.drag.start_position_3D = m_mouse.scene_position;
|
||||
m_sequential_print_clearance_first_displacement = true;
|
||||
m_moving = true;
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4477,6 +4528,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
}
|
||||
else if (evt.Dragging() && evt.LeftIsDown() && m_mouse.drag.move_volume_idx != -1 && m_layers_editing.state == LayersEditing::Unknown) {
|
||||
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
if (!m_mouse.drag.move_requires_threshold) {
|
||||
m_mouse.dragging = true;
|
||||
Vec3d cur_pos = m_mouse.drag.start_position_3D;
|
||||
@@ -4528,6 +4583,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
|
||||
//BBS not in assemble view
|
||||
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
m_rectangle_selection.dragging(pos.cast<double>());
|
||||
m_dirty = true;
|
||||
}
|
||||
@@ -4537,12 +4596,19 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
|
||||
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
|
||||
if (m_layers_editing.state == LayersEditing::Editing) {
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
_perform_layer_editing_action(&evt);
|
||||
m_mouse.position = pos.cast<double>();
|
||||
}
|
||||
}
|
||||
// do not process the dragging if the left mouse was set down in another canvas
|
||||
else if (is_camera_rotate(evt, button_mappings)) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// Orca: Sphere rotation for painting view
|
||||
// if dragging over blank area with left button or other button mapped to rotate, then rotate
|
||||
bool middle_or_right_button_used_as_rotate = (evt.MiddleIsDown() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) ||
|
||||
@@ -4622,6 +4688,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
|
||||
}
|
||||
else if (is_camera_pan(evt, button_mappings)) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
|
||||
if (m_mouse.is_start_position_2D_defined()) {
|
||||
// get point in model space at Z = 0
|
||||
@@ -4686,7 +4756,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
deselect_all();
|
||||
}
|
||||
//BBS Select plate in this 3D canvas.
|
||||
else if (evt.LeftUp() && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
|
||||
// The left up may come from an ImGui window (e.g. a drag started on the gizmo floating window and released over the bed),
|
||||
// in which case it must not be treated as a click on the plate, otherwise the gizmo would be closed (see deselect_all below).
|
||||
else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
|
||||
{
|
||||
int hover_idx = m_hover_plate_idxs.front();
|
||||
wxGetApp().plater()->select_plate_by_hover_id(hover_idx);
|
||||
@@ -6033,9 +6105,18 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
{
|
||||
if (!wxGetApp().show_3d_navigator()) {
|
||||
m_canvas_toolbar_pos[0] = 0;
|
||||
m_navigator_dragging = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix stealing capture event from other drag events
|
||||
const bool other_drag_active = !m_navigator_dragging && (m_moving || m_rectangle_selection.is_dragging() || m_gizmos.is_dragging() || m_layers_editing.state == LayersEditing::Editing);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const bool saved_mouse_down0 = io.MouseDown[0];
|
||||
if (other_drag_active)
|
||||
io.MouseDown[0] = false;
|
||||
|
||||
ImGuizmo::BeginFrame();
|
||||
|
||||
auto& style = ImGuizmo::GetStyle();
|
||||
@@ -6060,7 +6141,6 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
sc *= (float) dpi / (float) DPI_DEFAULT;
|
||||
#endif // WIN32
|
||||
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
const float viewManipulateLeft = 0;
|
||||
const float viewManipulateTop = io.DisplaySize.y;
|
||||
const float camDistance = 8.f;
|
||||
@@ -6084,6 +6164,10 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
camDistance, ImVec2(viewManipulateLeft, viewManipulateTop - size), ImVec2(size, size),
|
||||
0x00101010);
|
||||
|
||||
// Restore the real mouse-down state
|
||||
if (other_drag_active)
|
||||
io.MouseDown[0] = saved_mouse_down0;
|
||||
|
||||
if (result.changed) {
|
||||
for (unsigned int c = 0; c < 4; ++c) {
|
||||
for (unsigned int r = 0; r < 4; ++r) {
|
||||
@@ -6115,6 +6199,8 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
|
||||
request_extra_frame();
|
||||
}
|
||||
|
||||
m_navigator_dragging = result.dragging;
|
||||
}
|
||||
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
||||
@@ -9226,8 +9312,12 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
|
||||
|
||||
//ORCA ImGui::IsWindowHovered() returns false when left_down events on buttons that causes scrollbar disappears for a short time
|
||||
auto win_pos = ImGui::GetWindowPos();
|
||||
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll); // use non clipped rectangle to reserve clickable area for scrollbar track
|
||||
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered;
|
||||
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll);
|
||||
|
||||
// Also show scrollbar visible and continue to capture mouse position
|
||||
const bool is_scrollbar_active_drag = GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0 && GImGui->ActiveIdWindow == ImGui::GetCurrentWindow();
|
||||
|
||||
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered || is_scrollbar_active_drag;
|
||||
|
||||
imgui.end();
|
||||
}
|
||||
|
||||
@@ -589,6 +589,7 @@ private:
|
||||
bool m_toolpath_outside{ false };
|
||||
ECursorType m_cursor_type;
|
||||
GLSelectionRectangle m_rectangle_selection;
|
||||
bool m_navigator_dragging{ false };
|
||||
|
||||
//BBS:add plate related logic
|
||||
mutable std::vector<int> m_hover_volume_idxs;
|
||||
@@ -916,6 +917,7 @@ public:
|
||||
void update_volumes_colors_by_extruder();
|
||||
|
||||
bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; }
|
||||
bool has_mouse_capture() const;
|
||||
|
||||
void render(bool only_init = false);
|
||||
bool is_rendering_enabled()
|
||||
@@ -1117,6 +1119,10 @@ public:
|
||||
|
||||
void set_mouse_as_dragging() { m_mouse.dragging = true; }
|
||||
bool is_mouse_dragging() const { return m_mouse.dragging; }
|
||||
// True when the current left up event comes from an ImGui window and was not processed by it
|
||||
// (e.g. a drag that started on a gizmo floating window and was released over the 3D scene).
|
||||
// Such a release is the end of an ImGui interaction, not a click on the scene.
|
||||
bool is_mouse_left_up_ignored() const { return m_mouse.ignore_left_up; }
|
||||
|
||||
double get_size_proportional_to_max_bed_size(double factor) const;
|
||||
|
||||
|
||||
@@ -256,18 +256,18 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
||||
}
|
||||
}
|
||||
|
||||
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font)
|
||||
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts)
|
||||
{
|
||||
wxGetApp().CallAfter([=] {
|
||||
ErrorDialog msg(parent, message, monospaced_font);
|
||||
ErrorDialog msg(parent, message, has_code_excerpts);
|
||||
msg.ShowModal();
|
||||
});
|
||||
}
|
||||
|
||||
void show_error(wxWindow* parent, const char* message, bool monospaced_font)
|
||||
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts)
|
||||
{
|
||||
assert(message);
|
||||
show_error(parent, wxString::FromUTF8(message), monospaced_font);
|
||||
show_error(parent, wxString::FromUTF8(message), has_code_excerpts);
|
||||
}
|
||||
|
||||
void show_error_id(int id, const std::string& message)
|
||||
|
||||
@@ -40,11 +40,11 @@ extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_
|
||||
// Change option value in config
|
||||
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
|
||||
|
||||
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
|
||||
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false);
|
||||
void show_error(wxWindow* parent, const char* message, bool monospaced_font = false);
|
||||
inline void show_error(wxWindow* parent, const std::string& message, bool monospaced_font = false) { show_error(parent, message.c_str(), monospaced_font); }
|
||||
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
|
||||
// monospaced so the caret aligns. Used for placeholder-parser errors.
|
||||
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts = false);
|
||||
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts = false);
|
||||
inline void show_error(wxWindow* parent, const std::string& message, bool has_code_excerpts = false) { show_error(parent, message.c_str(), has_code_excerpts); }
|
||||
void show_error_id(int id, const std::string& message); // For Perl
|
||||
void show_info(wxWindow* parent, const wxString& message, const wxString& title = wxString());
|
||||
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
|
||||
|
||||
@@ -307,6 +307,20 @@ public:
|
||||
#endif // !__APPLE__
|
||||
)
|
||||
{
|
||||
// Some desktop environments ignore splash screen typed window properties
|
||||
// when running the app through Wayland,resulting in the titlebar being shown
|
||||
// on the splash screen. The code below creates a client-side window decoration
|
||||
// when running on Wayland and then removes that decoration. This ensures every
|
||||
// environment correctly targets and removes the titlebar for this screen.
|
||||
#if defined(__WXGTK__)
|
||||
if (Slic3r::GUI::is_running_on_wayland()) {
|
||||
GtkWidget *empty = gtk_fixed_new();
|
||||
gtk_widget_set_size_request(empty, 0, 0);
|
||||
gtk_window_set_titlebar(GTK_WINDOW(GetHandle()), empty);
|
||||
gtk_window_set_decorated(GTK_WINDOW(GetHandle()), false);
|
||||
}
|
||||
#endif
|
||||
|
||||
this->SetPosition(pos);
|
||||
this->CenterOnScreen();
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
||||
{"sparse_infill_density", "", 1},
|
||||
{"fill_multiline", "", 1},
|
||||
{"sparse_infill_pattern", "", 1},
|
||||
{"sparse_infill_smooth_factor", "", 1},
|
||||
{"lateral_lattice_angle_1", "", 1},
|
||||
{"lateral_lattice_angle_2", "", 1},
|
||||
{"infill_overhang_angle", "", 1},
|
||||
|
||||
@@ -548,7 +548,7 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
const char* css =
|
||||
"button {"
|
||||
"button, button:hover, button:active, button:focus {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
@@ -589,6 +589,58 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoveInputBorder(wxWindow* win)
|
||||
{
|
||||
GtkWidget* widget = win->GetHandle();
|
||||
if (!widget) return;
|
||||
|
||||
#if GTK_CHECK_VERSION(3, 0, 0)
|
||||
// GTK3+: use CSS provider
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
// Target 'entry' and its inner subnodes (like text selection areas)
|
||||
const char* css =
|
||||
"entry, entry text, entry undershoot {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
" padding: 0px;"
|
||||
" margin: 0px;"
|
||||
" min-height: 0px;"
|
||||
" min-width: 0px;"
|
||||
" background: none;"
|
||||
"}";
|
||||
|
||||
#if GTK_CHECK_VERSION(4, 0, 0)
|
||||
// GTK4
|
||||
gtk_css_provider_load_from_data(provider, css, -1);
|
||||
#else
|
||||
// GTK3
|
||||
gtk_css_provider_load_from_data(provider, css, -1, nullptr);
|
||||
#endif
|
||||
|
||||
GtkStyleContext* ctx = gtk_widget_get_style_context(widget);
|
||||
gtk_style_context_add_provider(
|
||||
ctx,
|
||||
GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER
|
||||
);
|
||||
g_object_unref(provider);
|
||||
|
||||
#else
|
||||
// GTK2: Target the x/y thickness of the entry widget
|
||||
gtk_rc_parse_string(
|
||||
"style \"no-padding-entry\" {"
|
||||
" xthickness = 0"
|
||||
" ythickness = 0"
|
||||
" GtkEntry::inner-border = { 0, 0, 0, 0 }"
|
||||
" GtkEntry::focus-line-width = 0"
|
||||
"}"
|
||||
"class \"GtkEntry\" style \"no-padding-entry\""
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#endif // __WXGTK__
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -113,14 +113,7 @@ public:
|
||||
update_dark_ui(this);
|
||||
#endif
|
||||
|
||||
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
|
||||
// So, calculate the m_em_unit value from the font size, as before
|
||||
#if !defined(__WXGTK__)
|
||||
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
|
||||
#else
|
||||
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
||||
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
|
||||
#endif // __WXGTK__
|
||||
update_em_unit();
|
||||
|
||||
// recalc_font();
|
||||
|
||||
@@ -235,6 +228,19 @@ private:
|
||||
// m_em_unit = metrics.averageWidth;
|
||||
// }
|
||||
|
||||
// update em_unit value for new window font
|
||||
void update_em_unit()
|
||||
{
|
||||
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
|
||||
// So, calculate the m_em_unit value from the font size, as before
|
||||
#if !defined(__WXGTK__)
|
||||
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
|
||||
#else
|
||||
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
||||
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
|
||||
#endif // __WXGTK__
|
||||
}
|
||||
|
||||
// check if new scale is differ from previous
|
||||
bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; }
|
||||
|
||||
@@ -247,8 +253,7 @@ private:
|
||||
// set normal application font as a current window font
|
||||
m_normal_font = this->GetFont();
|
||||
|
||||
// update em_unit value for new window font
|
||||
m_em_unit = std::max<int>(10, 10.0f * m_scale_factor);
|
||||
update_em_unit();
|
||||
|
||||
// rescale missed controls sizes and images
|
||||
on_dpi_changed(suggested_rect);
|
||||
@@ -472,8 +477,9 @@ void dataview_remove_insets(wxDataViewCtrl* dv);
|
||||
void staticbox_remove_margin(wxStaticBox* sb);
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
void RemoveButtonBorder(wxWindow* win);
|
||||
#ifdef __WXGTK__
|
||||
void RemoveButtonBorder(wxWindow* win); // for wxButton/wxBitmapToggleButton based controls (SwitchButton, CheckBox)
|
||||
void RemoveInputBorder(wxWindow* win); // for TextCtrl based controls (TextInput, ComboBox, SpinInput..)
|
||||
#endif
|
||||
|
||||
#if defined(__WXOSX__) || defined(__linux__)
|
||||
|
||||
@@ -442,7 +442,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
|
||||
}
|
||||
} else if (m_dragging) {
|
||||
// when mouse cursor leave window than finish actual dragging operation
|
||||
bool is_leaving = mouse_event.Leaving();
|
||||
bool is_leaving = mouse_event.Leaving() && !m_parent.has_mouse_capture(); // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
if (mouse_event.Dragging()) {
|
||||
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
auto ray = m_parent.mouse_ray(mouse_coord);
|
||||
|
||||
@@ -15,6 +15,8 @@ static const ColorRGBA DEF_COLOR = {0.7f, 0.7f, 0.7f, 1.f};
|
||||
static const ColorRGBA SELECTED_COLOR = {0.0f, 0.5f, 0.5f, 1.0f};
|
||||
static const ColorRGBA ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f};
|
||||
static const ColorRGBA HOVER_COLOR = {0.7f, 0.7f, 0.7f, 0.5f};
|
||||
static constexpr float BRIM_EAR_RADIUS_MIN = 0.1f;
|
||||
static constexpr float BRIM_EAR_RADIUS_MAX = 100.f;
|
||||
|
||||
static ModelVolume *get_model_volume(const Selection &selection, Model &model)
|
||||
{
|
||||
@@ -41,14 +43,14 @@ GLGizmoBrimEars::GLGizmoBrimEars(GLCanvas3D &parent, const std::string &icon_fil
|
||||
|
||||
bool GLGizmoBrimEars::on_init()
|
||||
{
|
||||
m_new_point_head_diameter = get_brim_default_radius();
|
||||
m_new_point_head_radius = get_brim_default_radius();
|
||||
|
||||
m_shortcut_key = WXK_CONTROL_E;
|
||||
|
||||
const wxString ctrl = GUI::shortkey_ctrl_prefix();
|
||||
const wxString alt = GUI::shortkey_alt_prefix();
|
||||
|
||||
m_desc["head_diameter"] = _L("Head diameter");
|
||||
m_desc["brim_ear_radius"] = _L("Brim ear radius");
|
||||
m_desc["max_angle"] = _L("Max angle");
|
||||
m_desc["detection_radius"] = _L("Detection radius");
|
||||
m_desc["remove"] = _L("Remove");
|
||||
@@ -62,7 +64,7 @@ bool GLGizmoBrimEars::on_init()
|
||||
m_shortcuts = {
|
||||
{_L("Left mouse button"), _L("Add or Select")},
|
||||
{_L("Right mouse button"), _L("Remove")},
|
||||
{ctrl + _L("Mouse wheel"), m_desc["head_diameter"]},
|
||||
{ctrl + _L("Mouse wheel"), m_desc["brim_ear_radius"]},
|
||||
{alt + _L("Mouse wheel"), m_desc["section_view"]},
|
||||
};
|
||||
|
||||
@@ -358,7 +360,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
Transform3d inverse_trsf = volume->get_instance_transformation().get_matrix_no_offset().inverse();
|
||||
std::pair<Vec3f, Vec3f> pos_and_normal;
|
||||
if (unproject_on_mesh2(mouse_position, pos_and_normal)) {
|
||||
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_diameter / 2.f), false, (inverse_trsf * m_world_normal).cast<float>(), true);
|
||||
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_radius), false, (inverse_trsf * m_world_normal).cast<float>(), true);
|
||||
} else {
|
||||
render_hover_point.reset();
|
||||
}
|
||||
@@ -397,7 +399,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
Vec3d object_pos = trsf.inverse() * world_pos;
|
||||
// brim ear always face up
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add brim ear");
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2.f, false, (inverse_trsf * m_world_normal).cast<float>());
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, (inverse_trsf * m_world_normal).cast<float>());
|
||||
m_parent.set_as_dirty();
|
||||
m_wait_for_up_event = true;
|
||||
find_single();
|
||||
@@ -490,9 +492,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
// mouse wheel up
|
||||
if (action == SLAGizmoEventType::MouseWheelUp) {
|
||||
if (control_down) {
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
float initial_value = m_new_point_head_radius;
|
||||
begin_radius_change(initial_value);
|
||||
m_new_point_head_diameter = std::min(20., initial_value + 0.1);
|
||||
m_new_point_head_radius = std::min(BRIM_EAR_RADIUS_MAX, initial_value + 0.1f);
|
||||
update_cache_radius();
|
||||
return true;
|
||||
}
|
||||
@@ -502,9 +504,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
|
||||
if (action == SLAGizmoEventType::MouseWheelDown) {
|
||||
if (control_down) {
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
float initial_value = m_new_point_head_radius;
|
||||
begin_radius_change(initial_value);
|
||||
m_new_point_head_diameter = std::max(5., initial_value - 0.1);
|
||||
m_new_point_head_radius = std::max(BRIM_EAR_RADIUS_MIN, initial_value - 0.1f);
|
||||
update_cache_radius();
|
||||
return true;
|
||||
}
|
||||
@@ -597,18 +599,18 @@ std::vector<const ConfigOption *> GLGizmoBrimEars::get_config_options(const std:
|
||||
|
||||
void GLGizmoBrimEars::begin_radius_change(float initial_value)
|
||||
{
|
||||
if (m_old_point_head_diameter == 0.f)
|
||||
m_old_point_head_diameter = initial_value;
|
||||
if (m_old_point_head_radius == 0.f)
|
||||
m_old_point_head_radius = initial_value;
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::update_cache_radius()
|
||||
{
|
||||
if (render_hover_point)
|
||||
render_hover_point->brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
|
||||
render_hover_point->brim_point.head_front_radius = m_new_point_head_radius;
|
||||
|
||||
for (auto &cache_entry : m_editing_cache)
|
||||
if (cache_entry.selected) {
|
||||
cache_entry.brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
|
||||
cache_entry.brim_point.head_front_radius = m_new_point_head_radius;
|
||||
find_single();
|
||||
update_model_object();
|
||||
}
|
||||
@@ -617,18 +619,18 @@ void GLGizmoBrimEars::update_cache_radius()
|
||||
|
||||
void GLGizmoBrimEars::apply_radius_change()
|
||||
{
|
||||
if (m_old_point_head_diameter == 0.f) return;
|
||||
if (m_old_point_head_radius == 0.f) return;
|
||||
|
||||
// momentarily restore the old value to take snapshot
|
||||
for (auto& cache_entry : m_editing_cache)
|
||||
if (cache_entry.selected)
|
||||
cache_entry.brim_point.head_front_radius = m_old_point_head_diameter / 2.f;
|
||||
float backup = m_new_point_head_diameter;
|
||||
m_new_point_head_diameter = m_old_point_head_diameter;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change point head diameter");
|
||||
m_new_point_head_diameter = backup;
|
||||
cache_entry.brim_point.head_front_radius = m_old_point_head_radius;
|
||||
float backup = m_new_point_head_radius;
|
||||
m_new_point_head_radius = m_old_point_head_radius;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change brim ear radius");
|
||||
m_new_point_head_radius = backup;
|
||||
update_cache_radius();
|
||||
m_old_point_head_diameter = 0.f;
|
||||
m_old_point_head_radius = 0.f;
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limit)
|
||||
@@ -653,7 +655,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
||||
|
||||
float space_size = m_imgui->get_style_scaling() * 8;
|
||||
std::vector<wxString> text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
|
||||
std::vector<wxString> text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
|
||||
m_desc["create"], m_desc["remove"]};
|
||||
float widest_text = m_imgui->find_widest_text(text_list);
|
||||
float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x;
|
||||
@@ -680,11 +682,11 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
// - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
|
||||
// - take correct undo/redo snapshot after the user is done with moving the slider
|
||||
ImGui::AlignTextToFramePadding();
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
m_imgui->text(m_desc["head_diameter"]);
|
||||
float initial_value = m_new_point_head_radius;
|
||||
m_imgui->text(m_desc["brim_ear_radius"]);
|
||||
ImGui::SameLine(caption_size);
|
||||
ImGui::PushItemWidth(slider_width);
|
||||
m_imgui->bbl_slider_float_style("##head_diameter", &m_new_point_head_diameter, 5, 20, "%.1f", 1.0f, true);
|
||||
m_imgui->bbl_slider_float_style("##brim_ear_radius", &m_new_point_head_radius, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f", 1.0f, true);
|
||||
if (m_imgui->get_last_slider_status().clicked) {
|
||||
begin_radius_change(initial_value);
|
||||
}
|
||||
@@ -695,7 +697,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
}
|
||||
ImGui::SameLine(drag_left_width);
|
||||
ImGui::PushItemWidth(1.5 * slider_icon_width);
|
||||
ImGui::BBLDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f");
|
||||
ImGui::BBLDragFloat("##brim_ear_radius_input", &m_new_point_head_radius, 0.05f, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -910,9 +912,9 @@ void GLGizmoBrimEars::on_stop_dragging()
|
||||
m_point_before_drag = CacheEntry();
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
|
||||
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
|
||||
|
||||
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
|
||||
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
|
||||
|
||||
void GLGizmoBrimEars::select_point(int i)
|
||||
{
|
||||
@@ -920,11 +922,11 @@ void GLGizmoBrimEars::select_point(int i)
|
||||
for (auto &point_and_selection : m_editing_cache) point_and_selection.selected = (i == AllPoints);
|
||||
m_selection_empty = (i == NoPoints);
|
||||
|
||||
if (i == AllPoints) m_new_point_head_diameter = m_editing_cache[0].brim_point.head_front_radius * 2.f;
|
||||
if (i == AllPoints) m_new_point_head_radius = m_editing_cache[0].brim_point.head_front_radius;
|
||||
} else {
|
||||
m_editing_cache[i].selected = true;
|
||||
m_selection_empty = false;
|
||||
m_new_point_head_diameter = m_editing_cache[i].brim_point.head_front_radius * 2.f;
|
||||
m_new_point_head_radius = m_editing_cache[i].brim_point.head_front_radius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1011,8 +1013,7 @@ void GLGizmoBrimEars::auto_generate()
|
||||
auto add_point = [this, &trsf, &normal](const Point &p) {
|
||||
Vec3d world_pos = {float(p.x() * SCALING_FACTOR), float(p.y() * SCALING_FACTOR), -0.0001};
|
||||
Vec3d object_pos = trsf.inverse() * world_pos;
|
||||
// m_editing_cache.emplace_back(BrimPoint(object_pos.cast<float>(), m_new_point_head_diameter / 2), false, normal);
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2, false, normal);
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, normal);
|
||||
};
|
||||
for (const ExPolygon &ex_poly : m_first_layer) {
|
||||
Polygon out_poly = ex_poly.contour;
|
||||
@@ -1158,8 +1159,11 @@ void GLGizmoBrimEars::reset_all_pick() { std::map<GLVolume *, std::shared_ptr<Pi
|
||||
float GLGizmoBrimEars::get_brim_default_radius() const
|
||||
{
|
||||
const double nozzle_diameter = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
|
||||
const DynamicPrintConfig &pring_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
return pring_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 16.0f;
|
||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
return std::clamp(
|
||||
float(print_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 8.0),
|
||||
BRIM_EAR_RADIUS_MIN,
|
||||
BRIM_EAR_RADIUS_MAX);
|
||||
}
|
||||
|
||||
ExPolygon GLGizmoBrimEars::make_polygon(BrimPoint point, const Geometry::Transformation &trsf)
|
||||
|
||||
@@ -98,12 +98,12 @@ private:
|
||||
|
||||
void render_points(const Selection& selection);
|
||||
|
||||
float m_new_point_head_diameter; // Size of a new point.
|
||||
float m_new_point_head_radius; // Radius of a new point.
|
||||
float m_max_angle = 125.f;
|
||||
float m_detection_radius = 1.f;
|
||||
double m_detection_radius_max = .0f;
|
||||
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
|
||||
float m_old_point_head_diameter = 0.; // the same
|
||||
float m_old_point_head_radius = 0.; // the same
|
||||
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selectedchanges or undo/redo
|
||||
std::map<int, CacheEntry> m_single_brim;
|
||||
ObjectID m_old_mo_id;
|
||||
|
||||
@@ -566,8 +566,11 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
|
||||
|
||||
void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
static bool was_dragging = true;
|
||||
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging) {
|
||||
static bool was_dragging = true;
|
||||
// The left up may be the end of a drag that started on the gizmo floating window (e.g. selecting
|
||||
// text in the input field). Such a release is not a click on the scene and must not close the gizmo.
|
||||
// (The flag is only set for left up events, so right up behavior is unchanged.)
|
||||
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging && !m_parent.is_mouse_left_up_ignored()) {
|
||||
// is hovered volume closest hovered?
|
||||
int hovered_idx = m_parent.get_first_hover_volume_idx();
|
||||
if (hovered_idx < 0)
|
||||
|
||||
+106
-18
@@ -9,8 +9,13 @@
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
#include <wx/html/winpars.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
@@ -229,12 +234,82 @@ void MsgDialog::finalize()
|
||||
}
|
||||
|
||||
|
||||
// A placeholder-parser caret line, pointing at the column where parsing failed.
|
||||
static bool is_caret_line(const std::string &line)
|
||||
{
|
||||
return std::count(line.begin(), line.end(), '^') == 1 &&
|
||||
std::all_of(line.begin(), line.end(), [](char c) { return c == ' ' || c == '^'; });
|
||||
}
|
||||
|
||||
// Tag each line as a code excerpt (a caret line or the source line above one) that must stay
|
||||
// monospaced for the '^' to align.
|
||||
static std::vector<std::pair<std::string, bool>> classify_code_lines(const std::string &msg)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, msg, boost::is_any_of("\n"));
|
||||
for (std::string &line : lines)
|
||||
if (!line.empty() && line.back() == '\r')
|
||||
line.pop_back();
|
||||
|
||||
std::vector<std::pair<std::string, bool>> tagged;
|
||||
tagged.reserve(lines.size());
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
bool is_code = is_caret_line(lines[i]) || (i + 1 < lines.size() && is_caret_line(lines[i + 1]));
|
||||
tagged.emplace_back(std::move(lines[i]), is_code);
|
||||
}
|
||||
return tagged;
|
||||
}
|
||||
|
||||
// Keeps whitespace literal so the caret's leading spaces survive.
|
||||
// Used inside <code>, which supplies the fixed face. <pre> does both but adds a blank line above it.
|
||||
class CodeExcerptTagHandler : public wxHtmlWinTagHandler
|
||||
{
|
||||
public:
|
||||
wxString GetSupportedTags() override { return wxT("EXCERPT"); }
|
||||
bool HandleTag(const wxHtmlTag &tag) override
|
||||
{
|
||||
const wxHtmlWinParser::WhitespaceMode ws = m_WParser->GetWhitespaceMode();
|
||||
m_WParser->SetWhitespaceMode(wxHtmlWinParser::Whitespace_Pre);
|
||||
ParseInner(tag);
|
||||
m_WParser->SetWhitespaceMode(ws);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Render the message as HTML, monospacing only the code excerpts.
|
||||
static std::string format_parser_error_html(const std::string &msg)
|
||||
{
|
||||
std::string out;
|
||||
for (const auto &[text, is_code] : classify_code_lines(msg)) {
|
||||
if (!out.empty()) out += "<br>"; // join, not trail; a trailing <br> forces a scrollbar
|
||||
std::string escaped = xml_escape(text);
|
||||
if (is_code)
|
||||
out += "<code><excerpt>" + escaped + "</excerpt></code>";
|
||||
else
|
||||
out += escaped;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Measure each line in the font it will render in, so the dialog fits the longest line without slack.
|
||||
static wxSize measure_mixed_text(wxWindow *parent, const std::string &msg, const wxFont &prose_font, const wxFont &code_font)
|
||||
{
|
||||
wxClientDC dc(parent);
|
||||
int width = 0, height = 0;
|
||||
for (const auto &[text, is_code] : classify_code_lines(msg)) {
|
||||
dc.SetFont(is_code ? code_font : prose_font);
|
||||
width = std::max(width, dc.GetTextExtent(wxString::FromUTF8(text.c_str())).GetWidth());
|
||||
height += dc.GetCharHeight();
|
||||
}
|
||||
return wxSize(width, height);
|
||||
}
|
||||
|
||||
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
|
||||
static void add_msg_content(wxWindow *parent,
|
||||
wxBoxSizer *content_sizer,
|
||||
wxString msg,
|
||||
bool monospaced_font = false,
|
||||
bool is_marked_msg = false,
|
||||
bool has_code_excerpts = false,
|
||||
bool is_marked_msg = false,
|
||||
const wxString &link_text = "",
|
||||
std::function<void(const wxString &)> link_callback = nullptr)
|
||||
{
|
||||
@@ -243,7 +318,7 @@ static void add_msg_content(wxWindow *parent,
|
||||
|
||||
// count lines in the message
|
||||
int msg_lines = 0;
|
||||
if (!monospaced_font) {
|
||||
if (!has_code_excerpts) {
|
||||
int line_len = 55;// count of symbols in one line
|
||||
int start_line = 0;
|
||||
for (auto i = msg.begin(); i != msg.end(); ++i) {
|
||||
@@ -300,13 +375,23 @@ static void add_msg_content(wxWindow *parent,
|
||||
page_size = wxSize(info_width, page_height);
|
||||
}
|
||||
else {
|
||||
wxClientDC dc(parent);
|
||||
dc.SetFont(font); // ORCA without this it calculates bigger size
|
||||
wxSize msg_sz = dc.GetMultiLineTextExtent(msg) + parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
|
||||
wxSize msg_sz;
|
||||
if (has_code_excerpts) {
|
||||
msg_sz = measure_mixed_text(parent, msg.ToUTF8().data(), font, monospace);
|
||||
} else {
|
||||
wxClientDC dc(parent);
|
||||
dc.SetFont(font); // ORCA without this it calculates bigger size
|
||||
msg_sz = dc.GetMultiLineTextExtent(msg);
|
||||
}
|
||||
msg_sz += parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
|
||||
|
||||
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(msg_sz.GetY(), info_width));
|
||||
int page_height = msg_sz.GetY();
|
||||
// Reserve the horizontal scrollbar's height, or it clips the last line.
|
||||
if (msg_sz.GetX() > info_width)
|
||||
page_height += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, parent);
|
||||
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(page_height, info_width));
|
||||
// Extra line breaks in message dialog
|
||||
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false) {//for common text
|
||||
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false && !has_code_excerpts) {//for common text
|
||||
html->Destroy();
|
||||
if (msg_sz.GetX() < info_width) {//No need for line breaks
|
||||
info_width = msg_sz.GetX();
|
||||
@@ -337,12 +422,15 @@ static void add_msg_content(wxWindow *parent,
|
||||
}
|
||||
html->SetMinSize(page_size);
|
||||
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
if (monospaced_font)
|
||||
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
|
||||
std::string msg_escaped;
|
||||
if (has_code_excerpts) {
|
||||
html->GetParser()->AddTagHandler(new CodeExcerptTagHandler());
|
||||
msg_escaped = format_parser_error_html(msg.ToUTF8().data());
|
||||
} else {
|
||||
msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
}
|
||||
|
||||
if (!link_text.IsEmpty() && link_callback) {
|
||||
msg_escaped += "<span><a href=\"#\" style=\"color:rgb(0, 150, 136); text-decoration:underline;\">" + std::string(link_text.ToUTF8().data()) + "</a></span>";
|
||||
@@ -360,15 +448,15 @@ static void add_msg_content(wxWindow *parent,
|
||||
|
||||
// ErrorDialog
|
||||
|
||||
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool monospaced_font)
|
||||
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts)
|
||||
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_FULL_NAME),
|
||||
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_FULL_NAME), wxOK)
|
||||
, msg(temp_msg)
|
||||
{
|
||||
add_msg_content(this, content_sizer, msg, monospaced_font);
|
||||
add_msg_content(this, content_sizer, msg, has_code_excerpts);
|
||||
|
||||
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
|
||||
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/64));
|
||||
// Use a small bitmap for code excerpts, which cannot wrap and so need the width.
|
||||
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, has_code_excerpts ? 48 : /*1*/64));
|
||||
|
||||
SetMaxSize(MSG_DLG_MAX_SIZE);
|
||||
|
||||
|
||||
@@ -106,9 +106,9 @@ protected:
|
||||
class ErrorDialog : public MsgDialog
|
||||
{
|
||||
public:
|
||||
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
|
||||
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool courier_font);
|
||||
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
|
||||
// monospaced so the caret aligns. Used for placeholder-parser errors.
|
||||
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts);
|
||||
ErrorDialog(ErrorDialog &&) = delete;
|
||||
ErrorDialog(const ErrorDialog &) = delete;
|
||||
ErrorDialog &operator=(ErrorDialog &&) = delete;
|
||||
|
||||
@@ -386,6 +386,7 @@ void OptionsGroup::activate_line(Line& line)
|
||||
}
|
||||
if (label != nullptr && line.label_tooltip != "")
|
||||
label->SetToolTip(line.label_tooltip);
|
||||
line.label_widget = label;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,6 +575,7 @@ void OptionsGroup::clear(bool destroy_custom_ctrl)
|
||||
for (Line& line : m_lines) {
|
||||
if (line.near_label_widget_win)
|
||||
line.near_label_widget_win = nullptr;
|
||||
line.label_widget = nullptr;
|
||||
|
||||
if (line.widget_sizer) {
|
||||
line.widget_sizer->Clear(true);
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
widget_t widget {nullptr};
|
||||
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
|
||||
wxWindow* near_label_widget_win {nullptr};
|
||||
wxStaticText* label_widget {nullptr};
|
||||
wxSizer* widget_sizer {nullptr};
|
||||
wxSizer* extra_widget_sizer {nullptr};
|
||||
//BBS: export the extra colume widget
|
||||
@@ -81,6 +82,14 @@ public:
|
||||
label(_(label)), label_tooltip(_(tooltip)) {}
|
||||
Line() : m_is_separator(true) {}
|
||||
|
||||
void set_label(const wxString& new_label) {
|
||||
label = new_label;
|
||||
if (label_widget != nullptr) {
|
||||
label_widget->SetLabel(label + (label.IsEmpty() ? "" : ": "));
|
||||
label_widget->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_separator() const { return m_is_separator; }
|
||||
bool has_only_option(const std::string& opt_key) const { return m_options.size() == 1 && m_options[0].opt_id == opt_key; }
|
||||
|
||||
|
||||
@@ -865,6 +865,9 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
|
||||
clr_picker = new wxBitmapButton(parent, wxID_ANY, {}, wxDefaultPosition, wxSize(FromDIP(20), FromDIP(20)), wxBU_EXACTFIT | wxBU_AUTODRAW | wxBORDER_NONE);
|
||||
clr_picker->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
clr_picker->SetToolTip(_L("Click to select filament color"));
|
||||
#ifdef __WXGTK__
|
||||
RemoveButtonBorder(clr_picker);
|
||||
#endif
|
||||
clr_picker->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||
// Check if it's an official filament
|
||||
auto fila_type = Preset::remove_suffix_modified(GetValue().ToUTF8().data());
|
||||
|
||||
+15
-1
@@ -1738,6 +1738,13 @@ void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index)
|
||||
if (line) line->toggle_visible = toggle;
|
||||
};
|
||||
|
||||
void Tab::set_option_label(const std::string &opt_key, const wxString &label, int opt_index)
|
||||
{
|
||||
if (!m_active_page) return;
|
||||
Line *line = m_active_page->get_line(opt_key, opt_index);
|
||||
if (line) line->set_label(label);
|
||||
}
|
||||
|
||||
// To be called by custom widgets, load a value into a config,
|
||||
// update the preset selection boxes (the dirty flags)
|
||||
// If value is saved before calling this function, put saved_value = true,
|
||||
@@ -2816,6 +2823,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
||||
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
||||
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_patterns#sparse-infill-smooth-factor");
|
||||
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
||||
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
|
||||
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
|
||||
@@ -3069,6 +3077,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("combine_brims", "others_settings_brim#combine-brims");
|
||||
optgroup->append_single_option_line("brim_ears_max_angle", "others_settings_brim#ear-max-angle");
|
||||
optgroup->append_single_option_line("brim_ears_detection_length", "others_settings_brim#ear-detection-radius");
|
||||
optgroup->append_single_option_line("brim_ears_outer_only", "others_settings_brim#brim-ears-outer-only");
|
||||
|
||||
optgroup = page->new_optgroup(L("Special mode"), L"param_special");
|
||||
optgroup->append_single_option_line("slicing_mode", "others_settings_special_mode#slicing-mode");
|
||||
@@ -5016,6 +5025,7 @@ void TabPrinter::build_fff()
|
||||
|
||||
optgroup->append_single_option_line("printer_structure", "printer_basic_information_advanced#printer-structure");
|
||||
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
|
||||
optgroup->append_single_option_line("gcode_skip_config_block", "printer_basic_information_advanced#skip-g-code-config-block");
|
||||
optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer");
|
||||
optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host");
|
||||
optgroup->append_single_option_line("use_3mf");
|
||||
@@ -8945,11 +8955,15 @@ ConfigManipulation Tab::get_config_manipulation()
|
||||
return toggle_line(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index);
|
||||
};
|
||||
|
||||
auto cb_set_option_label = [this](const t_config_option_key &opt_key, const wxString &label, int opt_index) {
|
||||
return set_option_label(opt_key, label, opt_index >= 0 ? opt_index + 256 : opt_index);
|
||||
};
|
||||
|
||||
auto cb_value_change = [this](const std::string& opt_key, const boost::any& value) {
|
||||
return on_value_change(opt_key, value);
|
||||
};
|
||||
|
||||
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this);
|
||||
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this, cb_set_option_label);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -402,6 +402,7 @@ public:
|
||||
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
|
||||
void toggle_option(const std::string &opt_key, bool toggle, int opt_index = -1);
|
||||
void toggle_line(const std::string &opt_key, bool toggle, int opt_index = -1); // BBS: hide some line
|
||||
void set_option_label(const std::string &opt_key, const wxString &label, int opt_index = -1);
|
||||
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString);
|
||||
bool current_preset_is_dirty() const;
|
||||
bool saved_preset_is_dirty() const;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "../wxExtensions.hpp"
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
@@ -29,7 +29,7 @@ CheckBox::CheckBox(wxWindow *parent, int id)
|
||||
Bind(wxEVT_LEAVE_WINDOW, &CheckBox::updateBitmap, this);
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include "../wxExtensions.hpp"
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
RadioBox::RadioBox(wxWindow *parent)
|
||||
@@ -15,6 +19,7 @@ RadioBox::RadioBox(wxWindow *parent)
|
||||
// Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); });
|
||||
update();
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
wxSize bestSize = GetBestSize();
|
||||
bestSize.IncTo(m_on.GetBmpSize());
|
||||
SetSize(bestSize);
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
#include <wx/dcgraph.h>
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
BEGIN_EVENT_TABLE(SpinInput, StaticBox)
|
||||
|
||||
EVT_KEY_DOWN(SpinInput::keyPressed)
|
||||
@@ -58,6 +62,11 @@ void SpinInput::Create(wxWindow *parent,
|
||||
state_handler.attach({&label_color, &text_color});
|
||||
state_handler.update_binds();
|
||||
text_ctrl = new TextCtrl(this, wxID_ANY, text, {20, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER, wxTextValidator(wxFILTER_DIGITS));
|
||||
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveInputBorder(text_ctrl);
|
||||
#endif
|
||||
|
||||
text_ctrl->SetFont(Label::Body_14);
|
||||
text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states()));
|
||||
text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states()));
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "libslic3r/MacUtils.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
@@ -37,7 +37,7 @@ SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id)
|
||||
Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); });
|
||||
SetFont(Label::Body_12);
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dcgraph.h>
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
BEGIN_EVENT_TABLE(TextInput, StaticBox)
|
||||
|
||||
EVT_PAINT(TextInput::paintEvent)
|
||||
@@ -60,6 +64,11 @@ void TextInput::Create(wxWindow * parent,
|
||||
state_handler.attach({&label_color, & text_color});
|
||||
state_handler.update_binds();
|
||||
text_ctrl = new TextCtrl(this, wxID_ANY, text, {4, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER);
|
||||
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveInputBorder(text_ctrl);
|
||||
#endif
|
||||
|
||||
text_ctrl->SetFont(Label::Body_14);
|
||||
text_ctrl->SetInitialSize(text_ctrl->GetBestSize());
|
||||
text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states()));
|
||||
|
||||
@@ -1022,6 +1022,10 @@ ScalableButton::ScalableButton( wxWindow * parent,
|
||||
m_width = size.x * 10 / em;
|
||||
m_height= size.y * 10 / em;
|
||||
}
|
||||
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -338,6 +338,22 @@ TEST_CASE("G-code lists the resolved extrusion-width settings", "[Print]")
|
||||
CHECK(with_first_layer.find("; first layer extrusion width") != std::string::npos);
|
||||
}
|
||||
|
||||
// gcode_skip_config_block suppresses the resolved-settings block while leaving the
|
||||
// header and executable blocks intact.
|
||||
TEST_CASE("gcode_skip_config_block omits the resolved-settings comment block", "[Print]")
|
||||
{
|
||||
const std::string gcode = slice({ cube(20) }, {
|
||||
{ "gcode_skip_config_block", true },
|
||||
{ "gcode_comments", true },
|
||||
});
|
||||
CHECK(gcode.find("; CONFIG_BLOCK_START") == std::string::npos);
|
||||
CHECK(gcode.find("; CONFIG_BLOCK_END") == std::string::npos);
|
||||
CHECK(gcode.find("; layer_height =") == std::string::npos);
|
||||
CHECK(gcode.find("; fill_density =") == std::string::npos);
|
||||
CHECK(gcode.find("; HEADER_BLOCK_START") != std::string::npos);
|
||||
CHECK(gcode.find("; EXECUTABLE_BLOCK_START") != std::string::npos);
|
||||
}
|
||||
|
||||
// Custom G-code templates substitute placeholders during export.
|
||||
TEST_CASE("Custom G-code placeholders are substituted", "[Print]")
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
@@ -32,6 +33,30 @@ static size_t brim_loop_count(Print &print)
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool brim_enters_first_layer_hole(Print &print)
|
||||
{
|
||||
const PrintObject *object = print.get_object(0);
|
||||
Polygons holes;
|
||||
for (const ExPolygon &slice : object->layers().front()->lslices)
|
||||
holes.insert(holes.end(), slice.holes.begin(), slice.holes.end());
|
||||
|
||||
const Vec3d plate_origin = print.get_plate_origin();
|
||||
Point shift = object->instances().front().shift_without_plate_offset();
|
||||
shift += Point(scaled(plate_origin.x()), scaled(plate_origin.y()));
|
||||
for (Polygon &hole : holes)
|
||||
hole.translate(shift);
|
||||
|
||||
for (const auto &kv : print.get_brimMap()) {
|
||||
Polylines brim_paths;
|
||||
kv.second.collect_polylines(brim_paths);
|
||||
for (const Polyline &path : brim_paths)
|
||||
for (const Point &point : path.points)
|
||||
if (contains(holes, point, false))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The span is skirt_height layers, or every layer when a draft shield is on (forced even at
|
||||
// height 0); per-object skirts are rejected in By object printing (no room between objects).
|
||||
TEST_CASE("Skirt is emitted once per layer it spans", "[SkirtBrim]")
|
||||
@@ -225,6 +250,131 @@ TEST_CASE("Brim ears appear only at corners within the max angle", "[SkirtBrim]"
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Outer-only brim ears stay out of model holes", "[SkirtBrim]")
|
||||
{
|
||||
const bool outer_only = GENERATE(false, true);
|
||||
DYNAMIC_SECTION("brim_ears_outer_only=" << outer_only) {
|
||||
Print print;
|
||||
init_and_process_print({ TestMesh::cube_with_concave_hole }, print, {
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "brim_ears" },
|
||||
{ "brim_width", 2 },
|
||||
{ "brim_ears_max_angle", 125 },
|
||||
{ "brim_ears_detection_length", 0 },
|
||||
{ "brim_ears_outer_only", outer_only },
|
||||
{ "initial_layer_line_width", 0.5 },
|
||||
});
|
||||
|
||||
REQUIRE(brim_loop_count(print) > 0);
|
||||
CHECK(brim_enters_first_layer_hole(print) != outer_only);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Painted brim ear radius controls sliced size", "[SkirtBrim]")
|
||||
{
|
||||
constexpr double ear_radius = 10.0;
|
||||
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "painted" },
|
||||
{ "brim_width", 15 },
|
||||
{ "brim_object_gap", 0.1 },
|
||||
{ "brim_ears_outer_only", true },
|
||||
{ "initial_layer_line_width", 0.5 },
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({ cube(20) }, print, model, config);
|
||||
print.process();
|
||||
|
||||
const PrintObject *object = print.get_object(0);
|
||||
REQUIRE(!object->layers().front()->lslices.empty());
|
||||
const Point ear_center = object->layers().front()->lslices.front().contour.points.front();
|
||||
|
||||
Transform3d model_transform = model.objects.front()->instances.front()->get_transformation().get_matrix_no_offset();
|
||||
const Point ¢er_offset = object->center_offset();
|
||||
model_transform = model_transform.pretranslate(
|
||||
Vec3d(-unscale<double>(center_offset.x()), -unscale<double>(center_offset.y()), 0));
|
||||
Vec3d model_pos = model_transform.inverse() *
|
||||
Vec3d(unscale<double>(ear_center.x()), unscale<double>(ear_center.y()), 0);
|
||||
model_pos.z() = model.objects.front()->raw_mesh_bounding_box().min.z() - 0.0001;
|
||||
model.objects.front()->brim_points = {
|
||||
BrimPoint(model_pos.cast<float>(), float(ear_radius)),
|
||||
};
|
||||
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
|
||||
const Vec3d plate_origin = print.get_plate_origin();
|
||||
Point path_center = ear_center + object->instances().front().shift_without_plate_offset();
|
||||
path_center += Point(scaled(plate_origin.x()), scaled(plate_origin.y()));
|
||||
|
||||
double max_path_radius = 0.0;
|
||||
for (const auto &kv : print.get_brimMap()) {
|
||||
Polylines brim_paths;
|
||||
kv.second.collect_polylines(brim_paths);
|
||||
for (const Polyline &path : brim_paths)
|
||||
for (const Point &point : path.points)
|
||||
max_path_radius = std::max(max_path_radius, unscale<double>((point - path_center).cast<double>().norm()));
|
||||
}
|
||||
|
||||
REQUIRE(max_path_radius > 0.0);
|
||||
INFO("Outermost painted-ear path radius: " << max_path_radius << " mm");
|
||||
CHECK(max_path_radius > ear_radius - 0.5);
|
||||
CHECK(max_path_radius < ear_radius);
|
||||
}
|
||||
|
||||
TEST_CASE("Outer-only painted brim ears stay out of model holes", "[SkirtBrim]")
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "painted" },
|
||||
{ "brim_ears_outer_only", true },
|
||||
{ "initial_layer_line_width", 0.5 },
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({ TestMesh::cube_with_concave_hole }, print, model, config);
|
||||
|
||||
// Slice once to obtain exact outer and inner contour points in print
|
||||
// coordinates, then express them in the model coordinates painted ears store.
|
||||
print.process();
|
||||
const PrintObject *object = print.get_object(0);
|
||||
REQUIRE(!object->layers().front()->lslices.empty());
|
||||
REQUIRE(!object->layers().front()->lslices.front().holes.empty());
|
||||
|
||||
Transform3d model_transform = model.objects.front()->instances.front()->get_transformation().get_matrix_no_offset();
|
||||
const Point ¢er_offset = object->center_offset();
|
||||
model_transform = model_transform.pretranslate(
|
||||
Vec3d(-unscale<double>(center_offset.x()), -unscale<double>(center_offset.y()), 0));
|
||||
const double bottom_z = model.objects.front()->raw_mesh_bounding_box().min.z() - 0.0001;
|
||||
auto painted_point = [&model_transform, bottom_z](const Point &point) {
|
||||
Vec3d model_pos = model_transform.inverse() *
|
||||
Vec3d(unscale<double>(point.x()), unscale<double>(point.y()), 0);
|
||||
model_pos.z() = bottom_z;
|
||||
return BrimPoint(model_pos.cast<float>(), 3.f);
|
||||
};
|
||||
|
||||
const ExPolygon &first_slice = object->layers().front()->lslices.front();
|
||||
Polygon inner_contour = first_slice.holes.front();
|
||||
inner_contour.reverse();
|
||||
const Points inner_ear_points = inner_contour.concave_points(55. * PI / 180.);
|
||||
REQUIRE(!inner_ear_points.empty());
|
||||
model.objects.front()->brim_points = {
|
||||
painted_point(first_slice.contour.points.front()),
|
||||
painted_point(inner_ear_points.front()),
|
||||
};
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
|
||||
REQUIRE(brim_loop_count(print) > 0);
|
||||
CHECK_FALSE(brim_enters_first_layer_hole(print));
|
||||
}
|
||||
|
||||
SCENARIO("Skirt has the configured number of loops", "[SkirtBrim]") {
|
||||
GIVEN("20mm cube and default config") {
|
||||
WHEN("skirt_loops is set to 2") {
|
||||
|
||||
@@ -18,6 +18,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_preset_setting_id.cpp
|
||||
test_preset_diff.cpp
|
||||
test_elephant_foot_compensation.cpp
|
||||
test_fill_plane_path.cpp
|
||||
test_geometry.cpp
|
||||
test_multimaterial_segmentation.cpp
|
||||
test_placeholder_parser.cpp
|
||||
@@ -28,6 +29,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_stl.cpp
|
||||
test_meshboolean.cpp
|
||||
test_marchingsquares.cpp
|
||||
test_model.cpp
|
||||
test_utils.cpp
|
||||
test_timeutils.cpp
|
||||
test_voronoi.cpp
|
||||
|
||||
@@ -509,64 +509,3 @@ SCENARIO("Nozzle-group metadata .3mf round-trip", "[3mf][MultiNozzle]") {
|
||||
boost::filesystem::remove_all(backup_dir);
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("2D convex hull of sinking object", "[3mf][.]") {
|
||||
GIVEN("model") {
|
||||
// load a model
|
||||
Model model;
|
||||
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl";
|
||||
REQUIRE(load_stl(src_file.c_str(), &model));
|
||||
model.add_default_instances();
|
||||
|
||||
WHEN("model is rotated, scaled and set as sinking") {
|
||||
ModelObject* object = model.objects[0];
|
||||
object->center_around_origin(false);
|
||||
|
||||
// This outputs the same exact data as the Prusaslicer test
|
||||
write_debug_stl("3mf/orca.ascii", object->volumes[0]->mesh());
|
||||
|
||||
// set instance's attitude so that it is rotated, scaled (and sinking? how is it sinking? the rotation? does it matter if it's sinking?)
|
||||
ModelInstance* instance = object->instances[0];
|
||||
instance->set_rotation(X, -M_PI / 4.0);
|
||||
instance->set_offset(Vec3d::Zero());
|
||||
instance->set_scaling_factor({ 2.0, 2.0, 2.0 });
|
||||
|
||||
// calculate 2D convex hull
|
||||
auto trafo = instance->get_transformation().get_matrix();
|
||||
|
||||
// This matrix is the same exact matrix as the Prusaslicer test
|
||||
CAPTURE(trafo);
|
||||
Polygon hull_2d = object->convex_hull_2d(trafo);
|
||||
|
||||
// But we get different hull_2d.points here (and somehow decimal numbers despite being int64_t values, but that's probabaly printing configuration somewhere -- Prusaslicer's prints out with newlines between the X&Y and not one between coordinates, which is about the worse possible output).
|
||||
// I think it's something to do with PrusaSlicer ignoring everything under the Z plane, which makes sense from the results.
|
||||
// See the comments added to ModelObject::convex_hull_2d for more information.
|
||||
|
||||
// verify result
|
||||
Points result = {
|
||||
{ -91501496, -15914144 },
|
||||
{ 91501496, -15914144 },
|
||||
{ 91501496, 4243 },
|
||||
{ 78229680, 4246883 },
|
||||
{ 56898100, 4246883 },
|
||||
{ -85501496, 4242641 },
|
||||
{ -91501496, 4243 }
|
||||
};
|
||||
|
||||
THEN("2D convex hull should match with reference") {
|
||||
// Allow 1um error due to floating point rounding.
|
||||
bool res = hull_2d.points.size() == result.size();
|
||||
if (res) {
|
||||
for (size_t i = 0; i < result.size(); ++ i) {
|
||||
const Point &p1 = result[i];
|
||||
const Point &p2 = hull_2d.points[i];
|
||||
CHECK((std::abs(p1.x() - p2.x()) > 1 || std::abs(p1.y() - p2.y()) > 1));
|
||||
}
|
||||
}
|
||||
|
||||
CAPTURE(hull_2d.points);
|
||||
REQUIRE(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "libslic3r/Fill/FillPlanePath.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double output_scale = 1'000'000.;
|
||||
|
||||
class TestableHilbertCurve : public FillHilbertCurve
|
||||
{
|
||||
public:
|
||||
Points generate_points(double resolution, double smooth_factor = 0., coord_t max_coordinate = 7)
|
||||
{
|
||||
InfillPolylineOutput output(output_scale);
|
||||
FillParams params;
|
||||
params.smooth_factor = smooth_factor;
|
||||
FillHilbertCurve::generate(0, 0, max_coordinate, max_coordinate, resolution, params, output);
|
||||
return std::move(output.result());
|
||||
}
|
||||
};
|
||||
|
||||
double path_length(const Points &points)
|
||||
{
|
||||
double length = 0.;
|
||||
for (size_t i = 1; i < points.size(); ++i)
|
||||
length += (points[i] - points[i - 1]).cast<double>().norm();
|
||||
return length;
|
||||
}
|
||||
|
||||
double discrete_curvature_at(const Points &points, const Point &point)
|
||||
{
|
||||
const auto point_it = std::find(points.begin(), points.end(), point);
|
||||
REQUIRE(point_it != points.end());
|
||||
const size_t point_idx = size_t(std::distance(points.begin(), point_it));
|
||||
REQUIRE(point_idx > 0);
|
||||
REQUIRE(point_idx + 1 < points.size());
|
||||
|
||||
const Vec2d incoming = (points[point_idx] - points[point_idx - 1]).cast<double>() / output_scale;
|
||||
const Vec2d outgoing = (points[point_idx + 1] - points[point_idx]).cast<double>() / output_scale;
|
||||
const Vec2d chord = incoming + outgoing;
|
||||
const double cross = std::abs(incoming.x() * outgoing.y() - incoming.y() * outgoing.x());
|
||||
return 2. * cross / (incoming.norm() * outgoing.norm() * chord.norm());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Hilbert curve exposes a smoothing factor", "[FillPlanePath]")
|
||||
{
|
||||
const ConfigOptionDef *factor_def = print_config_def.get("sparse_infill_smooth_factor");
|
||||
REQUIRE(factor_def != nullptr);
|
||||
REQUIRE(factor_def->type == coPercent);
|
||||
REQUIRE_THAT(factor_def->min, Catch::Matchers::WithinAbs(0., 1e-12));
|
||||
REQUIRE_THAT(factor_def->max, Catch::Matchers::WithinAbs(100., 1e-12));
|
||||
REQUIRE_THAT(factor_def->get_default_value<ConfigOptionPercent>()->value,
|
||||
Catch::Matchers::WithinAbs(0., 1e-12));
|
||||
}
|
||||
|
||||
TEST_CASE("Hilbert curve smoothing rounds right angle turns", "[FillPlanePath]")
|
||||
{
|
||||
const Points sharp = TestableHilbertCurve().generate_points(0.005);
|
||||
const Points smooth = TestableHilbertCurve().generate_points(0.005, 1.);
|
||||
|
||||
REQUIRE(smooth.front() == sharp.front());
|
||||
REQUIRE(smooth.back() == sharp.back());
|
||||
REQUIRE(smooth.size() > sharp.size());
|
||||
|
||||
bool has_turn = false;
|
||||
for (size_t i = 1; i < smooth.size(); ++i) {
|
||||
const Vec2d segment = (smooth[i] - smooth[i - 1]).cast<double>();
|
||||
REQUIRE(segment.squaredNorm() > 0.);
|
||||
}
|
||||
for (size_t i = 1; i + 1 < smooth.size(); ++i) {
|
||||
const Vec2d incoming = (smooth[i] - smooth[i - 1]).cast<double>();
|
||||
const Vec2d outgoing = (smooth[i + 1] - smooth[i]).cast<double>();
|
||||
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
|
||||
const double cosine = incoming.dot(outgoing) / (incoming.norm() * outgoing.norm());
|
||||
has_turn |= std::abs(cross) > 0.;
|
||||
REQUIRE(cosine > 0.);
|
||||
}
|
||||
REQUIRE(has_turn);
|
||||
|
||||
const coord_t upper_bound = coord_t(7 * output_scale);
|
||||
for (const Point &point : smooth) {
|
||||
REQUIRE(point.x() >= 0);
|
||||
REQUIRE(point.y() >= 0);
|
||||
REQUIRE(point.x() <= upper_bound);
|
||||
REQUIRE(point.y() <= upper_bound);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Smoothed Hilbert curve honors path resolution", "[FillPlanePath]")
|
||||
{
|
||||
const Points coarse = TestableHilbertCurve().generate_points(0.1, 1.);
|
||||
const Points fine = TestableHilbertCurve().generate_points(0.001, 1.);
|
||||
|
||||
REQUIRE(fine.size() > coarse.size());
|
||||
REQUIRE(fine.front() == coarse.front());
|
||||
REQUIRE(fine.back() == coarse.back());
|
||||
}
|
||||
|
||||
TEST_CASE("Smoothed Hilbert corners use a uniform subdivision depth", "[FillPlanePath]")
|
||||
{
|
||||
const Points smooth = TestableHilbertCurve().generate_points(0.0035, 1., 1);
|
||||
const Point curve_entry(0, coord_t(0.5 * output_scale));
|
||||
const Point curve_exit(coord_t(0.5 * output_scale), coord_t(output_scale));
|
||||
|
||||
const auto entry_it = std::find(smooth.begin(), smooth.end(), curve_entry);
|
||||
REQUIRE(entry_it != smooth.end());
|
||||
const auto exit_it = std::find(entry_it, smooth.end(), curve_exit);
|
||||
REQUIRE(exit_it != smooth.end());
|
||||
|
||||
const size_t segment_count = size_t(std::distance(entry_it, exit_it));
|
||||
REQUIRE(segment_count > 1);
|
||||
REQUIRE((segment_count & (segment_count - 1)) == 0);
|
||||
|
||||
double previous_length = (entry_it[1] - entry_it[0]).cast<double>().norm();
|
||||
REQUIRE(previous_length > 0.);
|
||||
double max_length_ratio = 1.;
|
||||
for (size_t segment = 1; segment < segment_count; ++segment) {
|
||||
const double current_length = (entry_it[segment + 1] - entry_it[segment]).cast<double>().norm();
|
||||
REQUIRE(current_length > 0.);
|
||||
max_length_ratio = std::max(max_length_ratio,
|
||||
std::max(current_length / previous_length, previous_length / current_length));
|
||||
previous_length = current_length;
|
||||
}
|
||||
REQUIRE(max_length_ratio < 1.5);
|
||||
}
|
||||
|
||||
TEST_CASE("Hilbert smoothing joins straight segments with continuous curvature", "[FillPlanePath]")
|
||||
{
|
||||
const Points coarse = TestableHilbertCurve().generate_points(0.005, 0.5, 1);
|
||||
const Points fine = TestableHilbertCurve().generate_points(0.0001, 0.5, 1);
|
||||
const Point first_curve_entry(0, coord_t(0.75 * output_scale));
|
||||
|
||||
const double coarse_entry_curvature = discrete_curvature_at(coarse, first_curve_entry);
|
||||
const double fine_entry_curvature = discrete_curvature_at(fine, first_curve_entry);
|
||||
REQUIRE(coarse_entry_curvature > 0.);
|
||||
REQUIRE(fine_entry_curvature < 0.25 * coarse_entry_curvature);
|
||||
}
|
||||
|
||||
TEST_CASE("Hilbert curve smooth factor controls corner curvature", "[FillPlanePath]")
|
||||
{
|
||||
const Points sharp = TestableHilbertCurve().generate_points(0.005);
|
||||
const Points half_smooth = TestableHilbertCurve().generate_points(0.005, 0.5);
|
||||
const Points full_smooth = TestableHilbertCurve().generate_points(0.005, 1.);
|
||||
const Points invalid_factor = TestableHilbertCurve().generate_points(
|
||||
0.005, std::numeric_limits<double>::quiet_NaN());
|
||||
|
||||
REQUIRE(full_smooth.front() == half_smooth.front());
|
||||
REQUIRE(full_smooth.back() == half_smooth.back());
|
||||
REQUIRE(path_length(full_smooth) < path_length(half_smooth));
|
||||
REQUIRE(invalid_factor == sharp);
|
||||
|
||||
for (size_t i = 1; i < full_smooth.size(); ++i)
|
||||
REQUIRE((full_smooth[i] - full_smooth[i - 1]).squaredNorm() > 0);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
// convex_hull_2d does not clip geometry below the bed, so these cases avoid
|
||||
// sinking transforms.
|
||||
TEST_CASE("A part's 2D convex hull is its footprint projected onto the bed", "[Model]")
|
||||
{
|
||||
Model model;
|
||||
ModelObject* object = model.add_object();
|
||||
// Keep the cube's raw coordinates ([0,20] on every axis): the default
|
||||
// add_volume re-centers the geometry, which would move the footprint.
|
||||
object->add_volume(make_cube(20, 20, 20), ModelVolumeType::MODEL_PART, false);
|
||||
|
||||
SECTION("identity transform yields the 20 mm square") {
|
||||
const Polygon hull = object->convex_hull_2d(Geometry::Transformation{}.get_matrix());
|
||||
const BoundingBox bb = hull.bounding_box();
|
||||
CHECK(hull.size() == 4);
|
||||
CHECK(bb.min.x() == scaled(0.));
|
||||
CHECK(bb.min.y() == scaled(0.));
|
||||
CHECK(bb.max.x() == scaled(20.));
|
||||
CHECK(bb.max.y() == scaled(20.));
|
||||
}
|
||||
|
||||
SECTION("scaling and offset move and grow the footprint") {
|
||||
Geometry::Transformation t;
|
||||
t.set_scaling_factor({2, 2, 2}); // cube now spans [0,40]
|
||||
t.set_offset({10, 5, 0}); // then shift +10 in X, +5 in Y
|
||||
|
||||
const Polygon hull = object->convex_hull_2d(t.get_matrix());
|
||||
const BoundingBox bb = hull.bounding_box();
|
||||
CHECK(hull.size() == 4);
|
||||
CHECK(bb.min.x() == scaled(10.));
|
||||
CHECK(bb.min.y() == scaled(5.));
|
||||
CHECK(bb.max.x() == scaled(50.));
|
||||
CHECK(bb.max.y() == scaled(45.));
|
||||
}
|
||||
}
|
||||
@@ -488,3 +488,69 @@ TEST_CASE("Plugin capability override keys are scoped per preset type", "[Preset
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// A standalone filament collection that exposes the protected library masking builder, so the Orca
|
||||
// Filament Library scenario can be set up without the full system-profile load pipeline.
|
||||
struct LibraryFilamentTestCollection : public PresetCollection
|
||||
{
|
||||
LibraryFilamentTestCollection()
|
||||
: PresetCollection(Preset::TYPE_FILAMENT, Preset::filament_options(),
|
||||
static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()))
|
||||
{}
|
||||
using PresetCollection::update_library_profile_excluded_from;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Orca: a filament in the Orca Filament Library that names its compatible printers has to hide the generic
|
||||
// library filament sharing its alias, the same way a vendor owned filament does. Otherwise both are compatible
|
||||
// with that printer and the plater combo box lists the shared alias twice.
|
||||
TEST_CASE("A printer specific filament supersedes the generic library filament with the same alias", "[Preset][Bundle]")
|
||||
{
|
||||
LibraryFilamentTestCollection filaments;
|
||||
PresetCollection printers(Preset::TYPE_PRINTER, Preset::printer_options(),
|
||||
static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()));
|
||||
// The masking keys off the vendor name, which VendorProfile's constructor does not derive from the id.
|
||||
VendorProfile library(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
VendorProfile vendor("Vendor");
|
||||
library.name = PresetBundle::ORCA_FILAMENT_LIBRARY;
|
||||
vendor.name = "Vendor";
|
||||
|
||||
auto add_filament = [&filaments](const VendorProfile &owner, const std::string &name, std::vector<std::string> compatible_printers) {
|
||||
Preset &preset = add_inmemory_preset(filaments, name);
|
||||
preset.alias = "Generic ABS";
|
||||
preset.vendor = &owner;
|
||||
preset.config.option<ConfigOptionStrings>("compatible_printers", true)->values = std::move(compatible_printers);
|
||||
};
|
||||
|
||||
add_filament(library, "Generic ABS @System", {});
|
||||
add_filament(library, "Generic ABS @Printer A", { "Printer A" });
|
||||
add_filament(vendor, "Generic ABS @Printer B", { "Printer B" });
|
||||
|
||||
filaments.update_library_profile_excluded_from();
|
||||
|
||||
const Preset *generic = filaments.find_preset("Generic ABS @System");
|
||||
REQUIRE(generic != nullptr);
|
||||
CHECK(generic->m_excluded_from.count("Printer A") == 1);
|
||||
CHECK(generic->m_excluded_from.count("Printer B") == 1);
|
||||
CHECK(generic->m_excluded_from.size() == 2);
|
||||
|
||||
// A printer specific profile names printers, so it is never the one being hidden - not even by itself.
|
||||
const Preset *specific = filaments.find_preset("Generic ABS @Printer A");
|
||||
REQUIRE(specific != nullptr);
|
||||
CHECK(specific->m_excluded_from.empty());
|
||||
|
||||
// ...and the generic profile really drops out of the compatible set on the printer it is hidden from.
|
||||
add_inmemory_preset(printers, "Printer A");
|
||||
add_inmemory_preset(printers, "Printer C");
|
||||
const Preset *printer_a = printers.find_preset("Printer A");
|
||||
const Preset *printer_c = printers.find_preset("Printer C");
|
||||
REQUIRE(printer_a != nullptr);
|
||||
REQUIRE(printer_c != nullptr);
|
||||
|
||||
const PresetWithVendorProfile generic_lib(*generic, &library);
|
||||
CHECK_FALSE(is_compatible_with_printer(generic_lib, PresetWithVendorProfile(*printer_a, nullptr)));
|
||||
CHECK(is_compatible_with_printer(generic_lib, PresetWithVendorProfile(*printer_c, nullptr)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user