Merge branch 'main' into feature/wipetower2_travel_path

This commit is contained in:
SoftFever
2026-08-05 00:11:07 +08:00
61 changed files with 1808 additions and 615 deletions
+4
View File
@@ -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)
+8
View File
@@ -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
+95
View File
@@ -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
+71 -26
View File
@@ -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);
+63 -16
View File
@@ -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);
+7 -3
View File
@@ -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)
+2 -1
View File
@@ -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>();
}
};
+1 -1
View File
@@ -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
View File
@@ -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);
+12
View File
@@ -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.
+5 -5
View File
@@ -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) {
+3
View File
@@ -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 };
+161 -2
View File
@@ -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 &params, 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)
{
+7
View File
@@ -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 &params, InfillPolylineOutput &output) override;
};
class FillOctagramSpiral : public FillPlanePath
+20 -17
View File
@@ -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");
+1 -1
View File
@@ -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 {
+6
View File
@@ -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)
+28 -24
View File
@@ -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");
+17 -12
View File
@@ -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
}
+2
View File
@@ -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";
+7 -4
View File
@@ -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);
+29 -1
View File
@@ -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");
+3 -1
View File
@@ -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))
+2
View File
@@ -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"
-5
View File
@@ -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
View File
@@ -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) {
+17 -5
View File
@@ -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);
+6 -1
View File
@@ -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);
+96 -6
View File
@@ -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();
}
+6
View File
@@ -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;
+4 -4
View File
@@ -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)
+5 -5
View File
@@ -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);
+14
View File
@@ -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();
+1
View File
@@ -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},
+53 -1
View File
@@ -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__
+18 -12
View File
@@ -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__)
+1 -1
View File
@@ -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);
+37 -33
View File
@@ -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)
+2 -2
View File
@@ -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;
+5 -2
View File
@@ -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
View File
@@ -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);
+3 -3
View File
@@ -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;
+2
View File
@@ -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);
+9
View File
@@ -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; }
+3
View File
@@ -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
View File
@@ -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);
}
+1
View File
@@ -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 -2
View File
@@ -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
+5
View File
@@ -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);
+9
View File
@@ -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()));
+2 -2
View File
@@ -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
+9
View File
@@ -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()));
+4
View File
@@ -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
}
+16
View File
@@ -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]")
{
+150
View File
@@ -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 &center_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 &center_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") {
+2
View File
@@ -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
-61
View File
@@ -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);
}
}
}
}
+164
View File
@@ -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);
}
+40
View File
@@ -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)));
}