mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-07 15:51:08 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c8e1f1c24 | ||
|
|
1f7a8ec91b | ||
|
|
fb74c8710c |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M5.5,14.5c-1.105,0-2-3.686-2-7s.895-7,2-7" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="8.67" y1="4.67" x2="14.33" y2="10.33" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round"/><line x1="14.33" y1="4.67" x2="8.67" y2="10.33" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round"/><path d="M3.5,13.7c-.294.511-.636.8-1,.8-1.1,0-2-3.686-2-7s.9-7,2-7c.365,0,.707.293,1,.805" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M6.727,4.5c.334-2.208,1-4,1.773-4,.354,0,.686.378.974,1" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M9.474,13.5c-.288.622-.62,1-.974,1-.77,0-1.439-1.792-1.773-4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 931 B |
@@ -29,19 +29,13 @@ uniform vec3 palette_lab[64];
|
||||
uniform vec3 palette_rgb[64];
|
||||
uniform int palette_count;
|
||||
uniform bool pure_only; // match against single filaments only (flat-colour image)
|
||||
// How each entry prints. A pure entry is one filament (a == b); a mix interleaves filaments a and b,
|
||||
// num parts of a in every den, and the print shows that interleave rather than the entry's average
|
||||
// colour. The fragment resolves it exactly as GLGizmoTextureDisplacement::make_mix_resolver() does
|
||||
// per triangle on the CPU, so the preview shows the pattern the bake will print.
|
||||
// How each entry prints. Every entry names a single filament: a mix is given its own mixed filament
|
||||
// slot, whose components the slicer alternates per print layer, so the fragment just looks that slot's
|
||||
// colour up.
|
||||
uniform int palette_a[64];
|
||||
uniform int palette_b[64];
|
||||
uniform int palette_num[64];
|
||||
uniform int palette_den[64];
|
||||
uniform vec3 filament_rgb[16];
|
||||
uniform int filament_count;
|
||||
uniform int mix_mode; // ColorMixMode: 0 Z bands, 1 XY dither, 2 auto
|
||||
uniform float layer_height; // mm; one Z band per print layer
|
||||
uniform float dither_cell; // mm; one XY dither cell
|
||||
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
|
||||
uniform bool has_color_tex;
|
||||
uniform bool volume_mirrored;
|
||||
@@ -229,63 +223,16 @@ int nearest_palette_entry(vec3 rgb)
|
||||
}
|
||||
|
||||
// One 2x2 Bayer cell, {0, 2; 3, 1}, for x and y in {0, 1}.
|
||||
float bayer2(float x, float y) { return 2.0 * x + 3.0 * y - 4.0 * x * y; }
|
||||
|
||||
// The colour the printer lays down at world point `pos` for palette entry `index`: its filament, or
|
||||
// for a mix whichever of its two filaments this point falls on. Mirrors make_mix_resolver() on the
|
||||
// CPU, floors on the band/cell size included. All the modular arithmetic is done in floats with
|
||||
// mod(), which wraps negative coordinates the way the CPU's ((v % n) + n) % n does and needs no
|
||||
// integer % (not available on every GLSL 1.10 target).
|
||||
vec3 printed_color(int index, vec3 pos, vec3 normal, vec3 footprint)
|
||||
// The colour the printer lays down at world point `pos` for palette entry `index`. Every entry names a
|
||||
// single filament: a mix is given its own mixed filament slot, whose components the slicer alternates
|
||||
// per print layer, so there is nothing left to interleave here.
|
||||
vec3 printed_color(int index)
|
||||
{
|
||||
int a = palette_a[index];
|
||||
int b = palette_b[index];
|
||||
if (a < 0 || a >= filament_count || b < 0 || b >= filament_count)
|
||||
if (a < 0 || a >= filament_count)
|
||||
return palette_rgb[index]; // no filament to resolve to: the entry's own colour
|
||||
if (a == b)
|
||||
return filament_rgb[a];
|
||||
float num = float(palette_num[index]);
|
||||
float den = float(palette_den[index]);
|
||||
// Auto: bands where the surface is steeper than ~45 degrees, the dominant filament elsewhere.
|
||||
if (mix_mode == 2 && abs(normal.z) >= 0.7)
|
||||
return filament_rgb[(num * 2.0 >= den) ? a : b];
|
||||
|
||||
// Pre-filter. The interleave is an ordered dither the eye is meant to blend away, and no dither
|
||||
// blends when it is drawn at less than a few pixels per period - it aliases, which is what turned
|
||||
// every upright wall into horizontal streaks: the Z band cycle is den * layer_height (around a
|
||||
// millimetre), and every pixel of a row on a vertical wall shares one z, so each row came out as a
|
||||
// 1-bit threshold of the image at that row's phase. `footprint` is mm of world position per pixel,
|
||||
// so this is zoom- and resolution-correct rather than a tuned constant: where the print's own
|
||||
// pattern is finer than this view can resolve, show what the print looks like from here, which is
|
||||
// the entry's perceptual average. The Normal view remains where the per-facet truth lives.
|
||||
float period = (mix_mode == 1) ? 2.0 * max(dither_cell, 0.01) : den * max(layer_height, 0.01);
|
||||
float px = (mix_mode == 1) ? max(footprint.x, footprint.y) : footprint.z;
|
||||
float sharp = clamp(period / max(4.0 * px, 1e-6) - 0.5, 0.0, 1.0);
|
||||
if (sharp <= 0.0)
|
||||
return palette_rgb[index];
|
||||
|
||||
vec3 picked;
|
||||
if (mix_mode == 1) {
|
||||
// Ordered 4x4 Bayer over floor(x / cell), floor(y / cell). The CPU's table
|
||||
// 0 8 2 10
|
||||
// 12 4 14 6
|
||||
// 3 11 1 9
|
||||
// 15 7 13 5
|
||||
// is 4 * bayer2(x % 2, y % 2) + bayer2(x / 2, y / 2), which needs no array (GLSL 1.10 has
|
||||
// no constant arrays).
|
||||
float cell = max(dither_cell, 0.01);
|
||||
float gx = mod(floor(pos.x / cell), 4.0);
|
||||
float gy = mod(floor(pos.y / cell), 4.0);
|
||||
float bayer = 4.0 * bayer2(mod(gx, 2.0), mod(gy, 2.0)) + bayer2(floor(gx / 2.0), floor(gy / 2.0));
|
||||
picked = filament_rgb[(num / den > (bayer + 0.5) / 16.0) ? a : b];
|
||||
} else {
|
||||
// Z bands: one per band height, the band's phase in the a/b cycle picks the filament. Both
|
||||
// operands are integer-valued, so the half keeps "phase < num" exact under float rounding.
|
||||
float slot = floor(pos.z / max(layer_height, 0.01));
|
||||
float phase = mod(slot, den);
|
||||
picked = filament_rgb[(phase < num - 0.5) ? a : b];
|
||||
}
|
||||
return mix(palette_rgb[index], picked, sharp);
|
||||
return filament_rgb[a];
|
||||
}
|
||||
|
||||
void main()
|
||||
@@ -296,9 +243,6 @@ void main()
|
||||
// World millimetres throughout, like the bake - see the 140 variant.
|
||||
vec3 triangle_normal = normalize(cross(dFdx(world_pos.xyz), dFdy(world_pos.xyz)));
|
||||
vec3 tex_pos = world_pos.xyz - tex_anchor; // the frame the texture is projected in, as the bake does
|
||||
// World mm per pixel, for pre-filtering the interleave in printed_color(). Taken here because the
|
||||
// albedo branch at the end of main() is non-uniform control flow, where derivatives are undefined.
|
||||
vec3 pos_fwidth = fwidth(world_pos.xyz);
|
||||
if (volume_mirrored)
|
||||
triangle_normal = -triangle_normal;
|
||||
|
||||
@@ -420,6 +364,6 @@ void main()
|
||||
// orientation and scale about the volume's origin, see texture_displacement_bake_frame()), so
|
||||
// measuring z from the bed instead shifted the band phase by the volume origin's height - a
|
||||
// different filament in the same place than the bake produces.
|
||||
albedo = printed_color(nearest_palette_entry(texture2D(color_tex, color_uv).rgb), tex_pos, triangle_normal, pos_fwidth);
|
||||
albedo = printed_color(nearest_palette_entry(texture2D(color_tex, color_uv).rgb));
|
||||
gl_FragColor = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
|
||||
}
|
||||
|
||||
@@ -88,19 +88,13 @@ uniform vec3 palette_lab[64];
|
||||
uniform vec3 palette_rgb[64];
|
||||
uniform int palette_count;
|
||||
uniform bool pure_only; // match against single filaments only (flat-colour image)
|
||||
// How each entry prints. A pure entry is one filament (a == b); a mix interleaves filaments a and b,
|
||||
// num parts of a in every den, and the print shows that interleave rather than the entry's average
|
||||
// colour. The fragment resolves it exactly as GLGizmoTextureDisplacement::make_mix_resolver() does
|
||||
// per triangle on the CPU, so the preview shows the pattern the bake will print.
|
||||
// How each entry prints. Every entry names a single filament: a mix is given its own mixed filament
|
||||
// slot, whose components the slicer alternates per print layer, so the fragment just looks that slot's
|
||||
// colour up.
|
||||
uniform int palette_a[64];
|
||||
uniform int palette_b[64];
|
||||
uniform int palette_num[64];
|
||||
uniform int palette_den[64];
|
||||
uniform vec3 filament_rgb[16];
|
||||
uniform int filament_count;
|
||||
uniform int mix_mode; // ColorMixMode: 0 Z bands, 1 XY dither, 2 auto
|
||||
uniform float layer_height; // mm; one Z band per print layer
|
||||
uniform float dither_cell; // mm; one XY dither cell
|
||||
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
|
||||
uniform bool has_color_tex;
|
||||
uniform bool volume_mirrored;
|
||||
@@ -295,63 +289,16 @@ int nearest_palette_entry(vec3 rgb)
|
||||
}
|
||||
|
||||
// One 2x2 Bayer cell, {0, 2; 3, 1}, for x and y in {0, 1}.
|
||||
float bayer2(float x, float y) { return 2.0 * x + 3.0 * y - 4.0 * x * y; }
|
||||
|
||||
// The colour the printer lays down at world point `pos` for palette entry `index`: its filament, or
|
||||
// for a mix whichever of its two filaments this point falls on. Mirrors make_mix_resolver() on the
|
||||
// CPU, floors on the band/cell size included. All the modular arithmetic is done in floats with
|
||||
// mod(), which wraps negative coordinates the way the CPU's ((v % n) + n) % n does and needs no
|
||||
// integer % (not available on every GLSL 1.10 target).
|
||||
vec3 printed_color(int index, vec3 pos, vec3 normal, vec3 footprint)
|
||||
// The colour the printer lays down at world point `pos` for palette entry `index`. Every entry names a
|
||||
// single filament: a mix is given its own mixed filament slot, whose components the slicer alternates
|
||||
// per print layer, so there is nothing left to interleave here.
|
||||
vec3 printed_color(int index)
|
||||
{
|
||||
int a = palette_a[index];
|
||||
int b = palette_b[index];
|
||||
if (a < 0 || a >= filament_count || b < 0 || b >= filament_count)
|
||||
if (a < 0 || a >= filament_count)
|
||||
return palette_rgb[index]; // no filament to resolve to: the entry's own colour
|
||||
if (a == b)
|
||||
return filament_rgb[a];
|
||||
float num = float(palette_num[index]);
|
||||
float den = float(palette_den[index]);
|
||||
// Auto: bands where the surface is steeper than ~45 degrees, the dominant filament elsewhere.
|
||||
if (mix_mode == 2 && abs(normal.z) >= 0.7)
|
||||
return filament_rgb[(num * 2.0 >= den) ? a : b];
|
||||
|
||||
// Pre-filter. The interleave is an ordered dither the eye is meant to blend away, and no dither
|
||||
// blends when it is drawn at less than a few pixels per period - it aliases, which is what turned
|
||||
// every upright wall into horizontal streaks: the Z band cycle is den * layer_height (around a
|
||||
// millimetre), and every pixel of a row on a vertical wall shares one z, so each row came out as a
|
||||
// 1-bit threshold of the image at that row's phase. `footprint` is mm of world position per pixel,
|
||||
// so this is zoom- and resolution-correct rather than a tuned constant: where the print's own
|
||||
// pattern is finer than this view can resolve, show what the print looks like from here, which is
|
||||
// the entry's perceptual average. The Normal view remains where the per-facet truth lives.
|
||||
float period = (mix_mode == 1) ? 2.0 * max(dither_cell, 0.01) : den * max(layer_height, 0.01);
|
||||
float px = (mix_mode == 1) ? max(footprint.x, footprint.y) : footprint.z;
|
||||
float sharp = clamp(period / max(4.0 * px, 1e-6) - 0.5, 0.0, 1.0);
|
||||
if (sharp <= 0.0)
|
||||
return palette_rgb[index];
|
||||
|
||||
vec3 picked;
|
||||
if (mix_mode == 1) {
|
||||
// Ordered 4x4 Bayer over floor(x / cell), floor(y / cell). The CPU's table
|
||||
// 0 8 2 10
|
||||
// 12 4 14 6
|
||||
// 3 11 1 9
|
||||
// 15 7 13 5
|
||||
// is 4 * bayer2(x % 2, y % 2) + bayer2(x / 2, y / 2), which needs no array (GLSL 1.10 has
|
||||
// no constant arrays).
|
||||
float cell = max(dither_cell, 0.01);
|
||||
float gx = mod(floor(pos.x / cell), 4.0);
|
||||
float gy = mod(floor(pos.y / cell), 4.0);
|
||||
float bayer = 4.0 * bayer2(mod(gx, 2.0), mod(gy, 2.0)) + bayer2(floor(gx / 2.0), floor(gy / 2.0));
|
||||
picked = filament_rgb[(num / den > (bayer + 0.5) / 16.0) ? a : b];
|
||||
} else {
|
||||
// Z bands: one per band height, the band's phase in the a/b cycle picks the filament. Both
|
||||
// operands are integer-valued, so the half keeps "phase < num" exact under float rounding.
|
||||
float slot = floor(pos.z / max(layer_height, 0.01));
|
||||
float phase = mod(slot, den);
|
||||
picked = filament_rgb[(phase < num - 0.5) ? a : b];
|
||||
}
|
||||
return mix(palette_rgb[index], picked, sharp);
|
||||
return filament_rgb[a];
|
||||
}
|
||||
|
||||
void main()
|
||||
@@ -364,9 +311,6 @@ void main()
|
||||
// world position and perturb the world normal.
|
||||
vec3 triangle_normal = normalize(cross(dFdx(world_pos.xyz), dFdy(world_pos.xyz)));
|
||||
vec3 tex_pos = world_pos.xyz - tex_anchor; // the frame the texture is projected in, as the bake does
|
||||
// World mm per pixel, for pre-filtering the interleave in printed_color(). Taken here because the
|
||||
// albedo branch at the end of main() is non-uniform control flow, where derivatives are undefined.
|
||||
vec3 pos_fwidth = fwidth(world_pos.xyz);
|
||||
if (volume_mirrored)
|
||||
triangle_normal = -triangle_normal;
|
||||
|
||||
@@ -508,6 +452,6 @@ void main()
|
||||
// orientation and scale about the volume's origin, see texture_displacement_bake_frame()), so
|
||||
// measuring z from the bed instead shifted the band phase by the volume origin's height - a
|
||||
// different filament in the same place than the bake produces.
|
||||
albedo = printed_color(nearest_palette_entry(texture(color_tex, color_uv).rgb), tex_pos, triangle_normal, pos_fwidth);
|
||||
albedo = printed_color(nearest_palette_entry(texture(color_tex, color_uv).rgb));
|
||||
out_color = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,5 @@
|
||||
<dict>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<!-- The Bambu network plug-in's code protector rewrites one of its own signed code pages
|
||||
after loading. Under the hardened runtime macOS kills the process when that page is
|
||||
paged back in; this lets it run, as Bambu Studio's signature does. -->
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -3618,9 +3618,6 @@ int CLI::run(int argc, char **argv)
|
||||
{
|
||||
if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "model_id" || opt_key == "dev_model_name" || opt_key == "filament_settings_id")
|
||||
continue;
|
||||
// rebuilt from every filament after this loop
|
||||
if (filament_dev_options.find(opt_key) != filament_dev_options.end())
|
||||
continue;
|
||||
ConfigOption *opt = m_print_config.option(opt_key, true);
|
||||
if (opt == nullptr) {
|
||||
// opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def().
|
||||
@@ -3684,14 +3681,6 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
// The stored values cannot be told apart per filament, so they are kept as they are unless every slot has a config.
|
||||
std::vector<const DynamicPrintConfig *> filament_configs(filament_count, nullptr);
|
||||
for (size_t index = 0; index < load_filaments_config.size(); index++)
|
||||
if (load_filaments_index[index] >= 1 && load_filaments_index[index] <= filament_count)
|
||||
filament_configs[load_filaments_index[index] - 1] = &load_filaments_config[index];
|
||||
if (std::find(filament_configs.begin(), filament_configs.end(), nullptr) == filament_configs.end())
|
||||
set_filament_dev_options(m_print_config, filament_configs);
|
||||
|
||||
if (m_print_config.option<ConfigOptionStrings>("filament_extruder_variant")) {
|
||||
std::vector<int>& filament_self_indice = m_print_config.option<ConfigOptionInts>("filament_self_index", true)->values;
|
||||
int index_size = m_print_config.option<ConfigOptionStrings>("filament_extruder_variant")->size();
|
||||
|
||||
@@ -5,10 +5,5 @@
|
||||
<!-- for dynamic loading of libraries without signature validation. Used for 3dconnection drivers.-->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<!-- The Bambu network plug-in's code protector rewrites one of its own signed code pages
|
||||
after loading. Under the hardened runtime macOS kills the process when that page is
|
||||
paged back in; this lets it run, as Bambu Studio's signature does. -->
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -148,6 +148,8 @@ struct ConflictComputeResult
|
||||
|
||||
using ConflictComputeOpt = std::optional<ConflictComputeResult>;
|
||||
|
||||
using ConflictObjName = std::optional<std::pair<std::string, std::string>>;
|
||||
|
||||
struct ConflictChecker
|
||||
{
|
||||
static ConflictResultOpt find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional<const FakeWipeTower *> wtdptr);
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
namespace Slic3r {
|
||||
namespace PreciseSeam {
|
||||
|
||||
// Import EnforcedBlockedSeamPoint from SeamPlacerImpl namespace for convenience
|
||||
using SeamPlacerImpl::EnforcedBlockedSeamPoint;
|
||||
|
||||
// Geometry and its exterior bounds are prepared together, then treated as read-only.
|
||||
struct ModifierRegion {
|
||||
ExPolygon polygon;
|
||||
@@ -134,7 +137,7 @@ SegmentExtraction extract_perimeter_segments(const PreparedPerimeter &prepared,
|
||||
|
||||
// Result of weak modifier segment processing
|
||||
struct WeakModifierSegment {
|
||||
SeamPlacerImpl::EnforcedBlockedSeamPoint type; // Enforced/Blocked/Neutral
|
||||
EnforcedBlockedSeamPoint type; // Enforced/Blocked/Neutral
|
||||
Point left_point; // Coordinates of left (first) point of segment
|
||||
PerimeterPosition left_position; // Position on the source perimeter before insertion/refinement.
|
||||
Point right_point; // Coordinates of right (last) point of segment
|
||||
|
||||
@@ -40,7 +40,8 @@ std::string get_error_string(const ThumbnailErrors& errors);
|
||||
|
||||
|
||||
typedef std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>> GCodeThumbnailDefinitionsList;
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext = "PNG");
|
||||
using namespace std::literals;
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext = "PNG"sv);
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase &config);
|
||||
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ struct Circle {
|
||||
|
||||
using Circlef = Circle<Vec2f>;
|
||||
using Circled = Circle<Vec2d>;
|
||||
using CircleSqf = CircleSq<Vec2f>;
|
||||
using CircleSqd = CircleSq<Vec2d>;
|
||||
|
||||
/// Find the center of the circle corresponding to the vector of Points as an arc.
|
||||
Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20);
|
||||
|
||||
@@ -154,12 +154,13 @@ template<class ExecutionPolicy, class Enable = void> struct _Loop
|
||||
};
|
||||
|
||||
// Add Specialization for using ExecutionTBB for parallel loops.
|
||||
template<> struct _Loop<Slic3r::ExecutionTBB>
|
||||
using namespace Slic3r;
|
||||
template<> struct _Loop<ExecutionTBB>
|
||||
{
|
||||
template<class It, class Fn> static void for_each_idx(It from, It to, Fn&& fn)
|
||||
{
|
||||
Slic3r::execution::for_each(
|
||||
Slic3r::ex_tbb, size_t(0), size_t(to - from), [&from, &fn](size_t i) { fn(from[i], i); }, Slic3r::execution::max_concurrency(Slic3r::ex_tbb));
|
||||
execution::for_each(
|
||||
ex_tbb, size_t(0), size_t(to - from), [&from, &fn](size_t i) { fn(from[i], i); }, execution::max_concurrency(ex_tbb));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10822,22 +10822,6 @@ void normalize_filament_values_to_variants(DynamicPrintConfig &config)
|
||||
}
|
||||
}
|
||||
|
||||
void set_filament_dev_options(DynamicPrintConfig &config, const std::vector<const DynamicPrintConfig *> &filament_configs)
|
||||
{
|
||||
for (const std::string &key : filament_dev_options) {
|
||||
if (std::none_of(filament_configs.begin(), filament_configs.end(), [&key](const DynamicPrintConfig *filament) { return filament->has(key); }))
|
||||
continue;
|
||||
const ConfigOption *default_value = print_config_def.get(key)->default_value.get();
|
||||
auto *dst = static_cast<ConfigOptionVectorBase *>(config.option(key, true));
|
||||
dst->clear();
|
||||
for (const DynamicPrintConfig *filament : filament_configs) {
|
||||
const auto *src = static_cast<const ConfigOptionVectorBase *>(filament->has(key) ? filament->option(key) : default_value);
|
||||
if (!src->empty())
|
||||
dst->append(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//used for object/region config
|
||||
//use the smallest of multiple to single
|
||||
@@ -11536,18 +11520,12 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
|
||||
int cur_variant_count = cur_extruder_variants.size();
|
||||
int target_variant_count = target_extruder_variants.size();
|
||||
|
||||
// A base variant this config does not list (the base gained it after the config was saved, or the
|
||||
// config lists none) takes this config's first variant of the same extruder, as a user preset's
|
||||
// values do in update_diff_values_to_child_config. Left unmatched, the base's value would silently
|
||||
// replace the user's.
|
||||
variant_index.resize(target_variant_count, -1);
|
||||
if (cur_variant_count == 0) {
|
||||
// Defensive: target_variant_count may be 0 if the preset doesn't carry extruder_variant_name.
|
||||
// In that case keep variant_index empty and let the downstream size checks produce a useful error.
|
||||
if (!variant_index.empty())
|
||||
// This config's one value belongs to the extruder of the base's first variant.
|
||||
variant_index = map_variant_indices(target_extruder_variants, target_extruder_ids, {},
|
||||
target_extruder_ids.empty() ? std::vector<int>() : std::vector<int>{target_extruder_ids[0]});
|
||||
variant_index[0] = 0;
|
||||
}
|
||||
else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){
|
||||
//should not happen
|
||||
@@ -11560,7 +11538,18 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
|
||||
%extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size();
|
||||
}
|
||||
else {
|
||||
variant_index = map_variant_indices(target_extruder_variants, target_extruder_ids, cur_extruder_variants, cur_extruder_ids);
|
||||
for (int i = 0; i < target_variant_count; i++)
|
||||
{
|
||||
for (int j = 0; j < cur_variant_count; j++)
|
||||
{
|
||||
if ((target_extruder_variants[i] == cur_extruder_variants[j])
|
||||
&&(target_extruder_ids.empty() || (target_extruder_ids[i] == cur_extruder_ids[j])))
|
||||
{
|
||||
variant_index[i] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& opt : keys) {
|
||||
@@ -11585,13 +11574,6 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
|
||||
if (cur_variant_count > target_variant_count)
|
||||
continue;
|
||||
|
||||
// The variant lists are the base's layout itself, which every other value is
|
||||
// carried onto: a variant this config lacks keeps its own name and id.
|
||||
if (opt == extruder_id_name || opt == extruder_variant_name) {
|
||||
opt_src->set(opt_target);
|
||||
continue;
|
||||
}
|
||||
|
||||
int stride = 1;
|
||||
if (key_set2.find(opt) != key_set2.end())
|
||||
stride = 2;
|
||||
|
||||
@@ -928,11 +928,6 @@ void normalize_filament_values_to_variants(DynamicPrintConfig &config);
|
||||
|
||||
extern std::set<std::string> filament_dev_options;
|
||||
|
||||
// Orca: a filament_dev_options option holds several values per filament, and how many is up to the
|
||||
// filament preset, so one filament's values cannot be replaced in place. This rebuilds each option from
|
||||
// filament_configs, one config per filament in slot order, as the filaments' values one after another.
|
||||
void set_filament_dev_options(DynamicPrintConfig &config, const std::vector<const DynamicPrintConfig *> &filament_configs);
|
||||
|
||||
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
|
||||
extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config,
|
||||
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& f_map_indices);
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
using TColor = typename PixelRenderer::color_type;
|
||||
using TValue = typename TColor::value_type;
|
||||
using TPixel = typename PixelRenderer::pixel_type;
|
||||
using TRawBuffer = agg::rendering_buffer;
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
@@ -551,9 +551,13 @@ DecimateResult decimate(const TriSoup &geometry, size_t target_triangles, bool h
|
||||
|
||||
// Rebuild from the surviving faces, with per-face normals.
|
||||
TriSoup &out = result.geometry;
|
||||
if (!face_color.empty())
|
||||
result.face_color.reserve(active_faces);
|
||||
for (size_t f = 0; f < face_count; ++f) {
|
||||
if (faces[f * 3] < 0)
|
||||
continue;
|
||||
if (!face_color.empty())
|
||||
result.face_color.push_back(f < face_color.size() ? face_color[f] : -1);
|
||||
const Vec3f a = pos[size_t(faces[f * 3])].cast<float>();
|
||||
const Vec3f b = pos[size_t(faces[f * 3 + 1])].cast<float>();
|
||||
const Vec3f c = pos[size_t(faces[f * 3 + 2])].cast<float>();
|
||||
|
||||
@@ -46,6 +46,13 @@ using DecimateProgressFn = std::function<bool(double fraction)>;
|
||||
struct DecimateResult
|
||||
{
|
||||
TriSoup geometry;
|
||||
// One entry per output face, carried from the `face_color` handed in: a colour difference is a
|
||||
// crease, so no collapse ever merges two faces of different colour and every survivor keeps exactly
|
||||
// the colour it came with. Empty when no `face_color` was given.
|
||||
//
|
||||
// This is what lets the caller colour the simplified mesh by *provenance* rather than by sampling it
|
||||
// again: the input colours were masked by the paint on the fine mesh, where that mask is exact.
|
||||
std::vector<int> face_color;
|
||||
// The locked faces alone met the target, so it was unreachable without touching preserved
|
||||
// geometry.
|
||||
bool locked_over_budget = false;
|
||||
|
||||
@@ -42,7 +42,7 @@ TriSoup to_soup(const indexed_triangle_set &its, const std::vector<uint8_t> &fac
|
||||
return out;
|
||||
}
|
||||
|
||||
indexed_triangle_set to_indexed_triangle_set(const TriSoup &soup)
|
||||
indexed_triangle_set to_indexed_triangle_set(const TriSoup &soup, std::vector<int> *face_color)
|
||||
{
|
||||
indexed_triangle_set out;
|
||||
const size_t n = soup.pos.size();
|
||||
@@ -54,12 +54,22 @@ indexed_triangle_set to_indexed_triangle_set(const TriSoup &soup)
|
||||
if (map.inserted())
|
||||
out.vertices.push_back(soup.pos[i]);
|
||||
}
|
||||
const bool track_color = face_color != nullptr && !face_color->empty();
|
||||
std::vector<int> kept_color;
|
||||
if (track_color)
|
||||
kept_color.reserve(face_color->size());
|
||||
for (size_t t = 0; t + 2 < n; t += 3) {
|
||||
// Welded-together corners carry no area.
|
||||
if (id[t] == id[t + 1] || id[t + 1] == id[t + 2] || id[t] == id[t + 2])
|
||||
continue;
|
||||
out.indices.emplace_back(id[t], id[t + 1], id[t + 2]);
|
||||
if (track_color) {
|
||||
const size_t src = t / 3;
|
||||
kept_color.push_back(src < face_color->size() ? (*face_color)[src] : -1);
|
||||
}
|
||||
}
|
||||
if (track_color)
|
||||
*face_color = std::move(kept_color);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ namespace TextureBake {
|
||||
TriSoup to_soup(const indexed_triangle_set &its, const std::vector<uint8_t> &face_excluded = {});
|
||||
|
||||
// Welds at the geometry grid.
|
||||
indexed_triangle_set to_indexed_triangle_set(const TriSoup &soup);
|
||||
// `face_color`, when given, is read as one entry per soup triangle and rewritten to match the output.
|
||||
// Welding can leave a triangle with no area, and those are dropped here, so the two would otherwise
|
||||
// fall out of step.
|
||||
indexed_triangle_set to_indexed_triangle_set(const TriSoup &soup, std::vector<int> *face_color = nullptr);
|
||||
|
||||
} // namespace TextureBake
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -290,6 +290,34 @@ PipelineResult run_pipeline(const TriSoup &input, const HeightSampleFn &sample,
|
||||
return result;
|
||||
}
|
||||
|
||||
// Colour per face, taken here and carried from here on. This is the only point where the paint mask
|
||||
// is exact: `exclude_weight` says which faces the paint left out, and the mesh is still the refined
|
||||
// one the displacement produced. Everything downstream (the collapse, the T-junction repair) carries
|
||||
// these along rather than sampling again, and the caller uses them as they are.
|
||||
//
|
||||
// It also gives the collapse its crease criterion: an edge between two colours is never collapsed
|
||||
// across, which is what keeps a survivor's colour well defined.
|
||||
if (color_sample) {
|
||||
const size_t nf = displaced.triangle_count();
|
||||
result.face_color.assign(nf, -1);
|
||||
const bool have_w = !displaced.exclude_weight.empty();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, nf), [&](const tbb::blocked_range<size_t> &r) {
|
||||
for (size_t t = r.begin(); t < r.end(); ++t) {
|
||||
// Unpainted faces take no colour at all, which is what stops the texture appearing on
|
||||
// surfaces the paint never covered.
|
||||
if (have_w && (displaced.exclude_weight[t * 3] + displaced.exclude_weight[t * 3 + 1] +
|
||||
displaced.exclude_weight[t * 3 + 2]) / 3.f > 0.99f)
|
||||
continue; // stays FACE_UNPAINTED
|
||||
const Vec3f &a = displaced.pos[t * 3], &b = displaced.pos[t * 3 + 1], &c = displaced.pos[t * 3 + 2];
|
||||
const int sampled = color_sample((a + b + c) / 3.f, displaced.nrm[t * 3]);
|
||||
// Painted either way. The sampler expects a point on the base surface and these are on
|
||||
// the displaced one, so off the patch by more than its tolerance it simply says "no
|
||||
// colour" - which must not be confused with "not painted".
|
||||
result.face_color[t] = (sampled >= 0) ? sampled : FACE_NO_COLOUR;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Decimate - export only. A bake needs the face-parent map, which a collapse destroys.
|
||||
std::vector<int> parent = std::move(sub.face_parent_id);
|
||||
const size_t displaced_before_decimate = displaced.triangle_count();
|
||||
@@ -323,24 +351,8 @@ PipelineResult run_pipeline(const TriSoup &input, const HeightSampleFn &sample,
|
||||
// unless the budget was lowered until decimation had to run. Only collapses costing less than
|
||||
// harvest_tol are taken, so this does not reach the relief.
|
||||
const bool harvest_only = !over_budget && settings.harvest_flat && displaced.triangle_count() > 0;
|
||||
std::vector<int> &face_color = result.face_color;
|
||||
if (over_budget || harvest_only) {
|
||||
// Colour per face on the fine mesh, so colour boundaries become creases the collapse
|
||||
// respects. Excluded (unpainted) faces take no colour.
|
||||
std::vector<int> face_color;
|
||||
if (color_sample) {
|
||||
const size_t nf = displaced.triangle_count();
|
||||
face_color.assign(nf, -1);
|
||||
const bool have_w = !displaced.exclude_weight.empty();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, nf), [&](const tbb::blocked_range<size_t> &r) {
|
||||
for (size_t t = r.begin(); t < r.end(); ++t) {
|
||||
if (have_w && (displaced.exclude_weight[t * 3] + displaced.exclude_weight[t * 3 + 1] +
|
||||
displaced.exclude_weight[t * 3 + 2]) / 3.f > 0.99f)
|
||||
continue;
|
||||
const Vec3f &a = displaced.pos[t * 3], &b = displaced.pos[t * 3 + 1], &c = displaced.pos[t * 3 + 2];
|
||||
face_color[t] = color_sample((a + b + c) / 3.f, displaced.nrm[t * 3]);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Harvesting alone is asked for by handing it the count it already has: nothing is then
|
||||
// over the target, so the loop only ever pops collapses under the tolerance.
|
||||
const size_t before = displaced.triangle_count();
|
||||
@@ -350,6 +362,7 @@ PipelineResult run_pipeline(const TriSoup &input, const HeightSampleFn &sample,
|
||||
result.locked_over_budget = dec.locked_over_budget;
|
||||
result.budget_limited = result.simplified = dec.target_cost_detail;
|
||||
displaced = std::move(dec.geometry);
|
||||
face_color = std::move(dec.face_color);
|
||||
lap("decimate", displaced, over_budget ? "over budget, simplified" : "flat faces harvested");
|
||||
BOOST_LOG_TRIVIAL(info) << "TextureBake decimate: " << before << " -> " << displaced.triangle_count()
|
||||
<< (over_budget ? " (budget " : " (flat harvest, budget ") << target << ")";
|
||||
@@ -377,7 +390,7 @@ PipelineResult run_pipeline(const TriSoup &input, const HeightSampleFn &sample,
|
||||
|
||||
// 6. Close the T-junctions decimation left behind. Only meaningful when it ran.
|
||||
if (mode == PipelineMode::Export && parent.empty()) {
|
||||
displaced = resolve_t_junctions(displaced);
|
||||
displaced = resolve_t_junctions(displaced, {}, &result.face_color);
|
||||
lap("repair", displaced);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +108,27 @@ using PipelineProgressFn = std::function<bool(const char *stage, double fraction
|
||||
// colour-boundary creases. Only consulted when the mesh is over budget.
|
||||
using ColorSampleFn = std::function<int(const Vec3f ¢roid, const Vec3f &normal)>;
|
||||
|
||||
// Sentinels for PipelineResult::face_color.
|
||||
static constexpr int FACE_UNPAINTED = -1; // the paint did not cover this face's origin
|
||||
static constexpr int FACE_NO_COLOUR = -2; // painted, but the sampler returned nothing at this point
|
||||
|
||||
struct PipelineResult
|
||||
{
|
||||
TriSoup geometry;
|
||||
// One entry per output face, carried through decimation, the T-junction repair and the weld.
|
||||
// FACE_UNPAINTED means the paint never covered the geometry this face came from; anything else means
|
||||
// it did, and is the palette index `color_sample` returned there (FACE_NO_COLOUR when it returned
|
||||
// none). The distinction matters: the sampler answers for points on the *base* surface, and these
|
||||
// are sampled on the displaced one, so a painted face can easily come back without a colour. Only
|
||||
// the painted/unpainted split is reliable here, and that is what a caller should use it for.
|
||||
//
|
||||
// Empty unless the caller gave a `color_sample`.
|
||||
//
|
||||
// A caller that needs per-face colour must use this rather than sampling the result again. The
|
||||
// result is displaced geometry: a point on it is no longer where its base surface was, so matching
|
||||
// it back by proximity colours whatever base surface happens to be nearest - which on a part thinner
|
||||
// than the relief depth is the *opposite* face, picking up the texture meant for the painted one.
|
||||
std::vector<int> face_color;
|
||||
// Output face -> input face. Empty in Export mode, where decimation invalidates it.
|
||||
std::vector<int> face_parent_id;
|
||||
bool safety_cap_hit = false;
|
||||
|
||||
@@ -72,7 +72,8 @@ size_t count_area_slivers(const TriSoup &geometry)
|
||||
return n;
|
||||
}
|
||||
|
||||
TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts)
|
||||
TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts,
|
||||
std::vector<int> *face_color)
|
||||
{
|
||||
const size_t n_tri = geometry.triangle_count();
|
||||
const double on_tol2 = opts.on_seg_tol * opts.on_seg_tol;
|
||||
@@ -98,7 +99,12 @@ TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts)
|
||||
// grid. A needle reads as watertight yet is deleted downstream, and dropping it leaves exactly
|
||||
// the on-edge-vertex topology the pass below closes.
|
||||
std::vector<std::array<int, 3>> faces;
|
||||
// Parallel to `faces` throughout, so a split or a dropped degenerate keeps the two in step.
|
||||
const bool track_color = face_color != nullptr && !face_color->empty();
|
||||
std::vector<int> colors;
|
||||
faces.reserve(n_tri);
|
||||
if (track_color)
|
||||
colors.reserve(n_tri);
|
||||
for (size_t t = 0; t < n_tri; ++t) {
|
||||
const int a = vid[t * 3], b = vid[t * 3 + 1], c = vid[t * 3 + 2];
|
||||
if (a == b || b == c || a == c)
|
||||
@@ -108,6 +114,8 @@ TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts)
|
||||
if (u.cross(w).squaredNorm() < DEGENERATE_AREA_SQ)
|
||||
continue;
|
||||
faces.push_back({ a, b, c });
|
||||
if (track_color)
|
||||
colors.push_back(t < face_color->size() ? (*face_color)[t] : -1);
|
||||
}
|
||||
|
||||
for (int iter = 0; iter < opts.max_iters; ++iter) {
|
||||
@@ -166,11 +174,16 @@ TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts)
|
||||
break;
|
||||
|
||||
std::vector<std::array<int, 3>> next;
|
||||
std::vector<int> next_colors;
|
||||
next.reserve(faces.size() + splits.size() * 2);
|
||||
if (track_color)
|
||||
next_colors.reserve(next.capacity());
|
||||
for (size_t fi = 0; fi < faces.size(); ++fi) {
|
||||
const auto it = splits.find(fi);
|
||||
if (it == splits.end()) {
|
||||
next.push_back(faces[fi]);
|
||||
if (track_color)
|
||||
next_colors.push_back(colors[fi]);
|
||||
continue;
|
||||
}
|
||||
const auto &f = faces[fi];
|
||||
@@ -195,11 +208,18 @@ TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts)
|
||||
seq.insert(seq.end(), sp.mids.rbegin(), sp.mids.rend());
|
||||
seq.push_back(sp.a);
|
||||
}
|
||||
for (size_t s = 0; s + 1 < seq.size(); ++s)
|
||||
for (size_t s = 0; s + 1 < seq.size(); ++s) {
|
||||
next.push_back({ seq[s], seq[s + 1], apex });
|
||||
if (track_color)
|
||||
next_colors.push_back(colors[fi]); // every piece of a split face keeps its colour
|
||||
}
|
||||
}
|
||||
faces.swap(next);
|
||||
if (track_color)
|
||||
colors.swap(next_colors);
|
||||
}
|
||||
if (track_color)
|
||||
*face_color = std::move(colors);
|
||||
|
||||
TriSoup out;
|
||||
out.pos.reserve(faces.size() * 3);
|
||||
|
||||
@@ -43,7 +43,12 @@ struct RepairOptions
|
||||
int max_iters = 16;
|
||||
};
|
||||
|
||||
TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts = {});
|
||||
// `face_color`, when given, is read as one entry per input face and rewritten to match the output: a
|
||||
// face split to close a T-junction hands its colour to every piece, and a degenerate face dropped on
|
||||
// the way takes its entry with it. Without this the caller would have no way to keep a per-face colour
|
||||
// across this pass, which changes the triangle count.
|
||||
TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts = {},
|
||||
std::vector<int> *face_color = nullptr);
|
||||
|
||||
} // namespace TextureBake
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -2284,12 +2284,11 @@ indexed_triangle_set build_texture_displacement_v2(const indexed_triangle_set
|
||||
//
|
||||
// The *palette* index, not the printed filament. The decimation treats any edge whose two faces
|
||||
// differ as a crease (TextureBakeDecimate.cpp), so it must only ever see where the **perceived**
|
||||
// colour changes - which is exactly what ColorResolveFn's own contract says the interleaving may
|
||||
// never be fed into. Handing it the resolved filament made every Z band boundary a crease: on an
|
||||
// upright wall that is one crease per band, so the collapse ran along those lines and left a stack
|
||||
// of horizontal slivers, each printing in a single filament. Those were the horizontal colour
|
||||
// lines in the baked result, and they also spent the triangle budget drawing a pattern the eye is
|
||||
// meant to blend away. Faces the paint excludes are skipped by the pipeline itself.
|
||||
// colour changes. A mix is one perceived colour however its components are laid down, which is why
|
||||
// it has to be the palette index here: back when this was handed a per-triangle interleave instead,
|
||||
// every band boundary read as a crease, the collapse ran along those lines and left a stack of
|
||||
// horizontal slivers, and the triangle budget went on drawing a pattern the eye is meant to blend
|
||||
// away. Faces the paint excludes are skipped by the pipeline itself.
|
||||
const TextureBake::ColorSampleFn color_sample =
|
||||
color_sampler ? TextureBake::ColorSampleFn([&color_sampler](const Vec3f &p, const Vec3f &n) {
|
||||
return color_sampler(p, n);
|
||||
@@ -2315,7 +2314,7 @@ indexed_triangle_set build_texture_displacement_v2(const indexed_triangle_set
|
||||
stats->triangles_budget = result.triangles_budget;
|
||||
stats->budget_limited = result.budget_limited;
|
||||
}
|
||||
indexed_triangle_set out = TextureBake::to_indexed_triangle_set(result.geometry);
|
||||
indexed_triangle_set out = TextureBake::to_indexed_triangle_set(result.geometry, &result.face_color);
|
||||
if (out.indices.empty())
|
||||
return mesh;
|
||||
if (flip_normals)
|
||||
@@ -2336,6 +2335,8 @@ indexed_triangle_set build_texture_displacement_v2(const indexed_triangle_set
|
||||
max_depth = std::max(max_depth, std::abs(layer.depth_mm));
|
||||
const float relief_tol = max_depth + paint_tol;
|
||||
std::vector<int> palette(out.indices.size(), -1);
|
||||
const std::vector<int> &face_mask = result.face_color;
|
||||
const bool have_face_mask = face_mask.size() == out.indices.size();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, out.indices.size()), [&](const tbb::blocked_range<size_t> &r) {
|
||||
for (size_t i = r.begin(); i < r.end(); ++i) {
|
||||
const stl_triangle_vertex_indices &t = out.indices[i];
|
||||
@@ -2351,8 +2352,19 @@ indexed_triangle_set build_texture_displacement_v2(const indexed_triangle_set
|
||||
// reason; this path was the inconsistent one.
|
||||
Vec3f foot = centroid, base_n = Vec3f::UnitZ();
|
||||
const float d2 = painted_closest(centroid, &foot, &base_n);
|
||||
if (!all_painted && d2 >= relief_tol * relief_tol)
|
||||
// Which faces may be coloured comes from the pipeline, which recorded it on the
|
||||
// refined mesh where the paint mask is exact, and carried it through the collapse,
|
||||
// the T-junction repair and the weld. Proximity cannot answer this: a displaced face
|
||||
// is no longer where its base was, so on a part thinner than the relief depth the
|
||||
// nearest painted surface to the *opposite* face is the painted one, and the texture
|
||||
// appeared there too. Only the position to sample at still comes from the base
|
||||
// surface, for the projection reason above.
|
||||
if (have_face_mask) {
|
||||
if (face_mask[i] == TextureBake::FACE_UNPAINTED)
|
||||
continue;
|
||||
} else if (!all_painted && d2 >= relief_tol * relief_tol) {
|
||||
continue;
|
||||
}
|
||||
palette[i] = sampler(foot, base_n);
|
||||
}
|
||||
});
|
||||
@@ -2374,7 +2386,7 @@ indexed_triangle_set build_texture_displacement_v2(const indexed_triangle_set
|
||||
Vec3f normal = (b - a).cross(c - a);
|
||||
const float nl = normal.norm();
|
||||
normal = (nl > 0.f) ? Vec3f(normal / nl) : Vec3f::UnitZ();
|
||||
const int filament = color->resolve ? color->resolve(palette[i], centroid, normal) : palette[i];
|
||||
const int filament = palette[i];
|
||||
if (filament >= 0)
|
||||
out_color[i] = uint8_t(std::min(filament + 1, 255));
|
||||
}
|
||||
@@ -2831,8 +2843,7 @@ static indexed_triangle_set build_texture_displacement_in_place(
|
||||
Vec3f normal = (b - a).cross(c - a);
|
||||
const float nl = normal.norm();
|
||||
normal = (nl > 0.f) ? Vec3f(normal / nl) : Vec3f::UnitZ();
|
||||
const int filament = color->resolve ? color->resolve(triangle_palette[i], centroid, normal)
|
||||
: triangle_palette[i];
|
||||
const int filament = triangle_palette[i];
|
||||
if (filament >= 0)
|
||||
out_color[i] = uint8_t(std::min(filament + 1, 255));
|
||||
}
|
||||
|
||||
@@ -321,25 +321,6 @@ struct TextureDisplacementLayer
|
||||
}
|
||||
};
|
||||
|
||||
// How a *mixed* palette entry - one that names two filaments rather than one - is turned into real
|
||||
// per-facet paint. An MMU extrudes one filament at a time, so an intermediate colour exists only by
|
||||
// interleaving two of them finely enough that the eye does the blending.
|
||||
enum class ColorMixMode : int
|
||||
{
|
||||
// Horizontal bands: which of the two filaments a point takes depends on its height, so
|
||||
// consecutive print layers alternate. This is how filament-blend prints actually work, and on a
|
||||
// vertical-ish surface it reads as a genuinely smooth colour. On a near-horizontal surface a whole
|
||||
// layer is one band, so the blend disappears - that is what XYDither is for.
|
||||
ZBands = 0,
|
||||
// An ordered (Bayer) checkerboard across the surface, at any orientation. Independent of layer
|
||||
// height, but its cell is around the size of one facet, so a fine mix can read as texture rather
|
||||
// than as a clean blend.
|
||||
XYDither = 1,
|
||||
// Per triangle, by its orientation: bands where the surface is upright enough for consecutive
|
||||
// layers to alternate, the checkerboard where it faces up or down and a layer would be one band.
|
||||
// The default - a flat-topped part with a mix on top gets no blend at all from bands alone.
|
||||
Auto = 2,
|
||||
};
|
||||
|
||||
// Settings that apply to the whole layer stack rather than to one layer, held per ModelVolume next
|
||||
// to texture_displacement_layers and consumed by build_texture_displacement().
|
||||
@@ -406,7 +387,6 @@ struct TextureDisplacementOptions
|
||||
// image (TextureDetail::flat_colors): a texture of flat colours prints in single filaments, a
|
||||
// photograph or gradient in mixes. Off forces single filaments everywhere.
|
||||
bool color_mix_enabled = true;
|
||||
ColorMixMode color_mix_mode = ColorMixMode::Auto;
|
||||
// Majority-filter passes over the assigned colours. See TextureColorRequest::despeckle_passes -
|
||||
// this is the control for it, and 2 is enough to clear the salt-and-pepper an image with detail
|
||||
// finer than the mesh leaves behind, without eating features that are genuinely a facet wide.
|
||||
@@ -414,11 +394,9 @@ struct TextureDisplacementOptions
|
||||
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
int mix_mode = int(color_mix_mode);
|
||||
ar(displace_border, smooth_enabled, smooth_strength, smooth_iterations, smooth_skip_border,
|
||||
pipeline_v2, v2_refine_mm, v2_regularize, v2_max_triangles_k,
|
||||
v2_relocate, color_mix_enabled, mix_mode, color_despeckle);
|
||||
color_mix_mode = ColorMixMode(mix_mode);
|
||||
v2_relocate, color_mix_enabled, color_despeckle);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -511,17 +489,9 @@ DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer
|
||||
// is. See GLGizmoTextureDisplacement::make_palette_quantizer().
|
||||
using ColorQuantizeFn = std::function<int(const Vec3f &)>;
|
||||
|
||||
// Resolves a palette index plus a surface position to the filament index that position should print
|
||||
// in. A pure entry ignores the position; a mixed one interleaves its two filaments per ColorMixMode.
|
||||
//
|
||||
// Deliberately separate from ColorQuantizeFn, and deliberately *not* used by the subdivision's colour
|
||||
// criterion: that criterion asks where the **perceived** colour changes, and must not see the
|
||||
// interleaving. Refining on every band or dither-cell boundary would spend the whole triangle budget
|
||||
// drawing a pattern the eye is supposed to blend away.
|
||||
using ColorResolveFn = std::function<int(int palette_index, const Vec3f &pos, const Vec3f &normal)>;
|
||||
|
||||
// One printable colour: either a loaded filament on its own, or a blend of two of them realised by
|
||||
// interleaving (see ColorMixMode). Plain data, so it can be captured into a background job.
|
||||
// interleaving, which the slicer does per print layer. Plain data, so it can be captured into a job.
|
||||
struct PrintableColor
|
||||
{
|
||||
Vec3f rgb = Vec3f::Zero(); // what it looks like; for a mix, the perceptual average of the two
|
||||
@@ -538,9 +508,6 @@ struct TextureColorSettings
|
||||
{
|
||||
std::vector<PrintableColor> palette;
|
||||
std::vector<PrintableColor> palette_pure; // the filaments alone, for flat-colour images
|
||||
ColorMixMode mix_mode = ColorMixMode::ZBands;
|
||||
float layer_height = 0.2f; // sizes the Z bands
|
||||
float dither_cell_mm = 0.4f; // sizes the XY dither cells
|
||||
int despeckle_passes = 2;
|
||||
|
||||
bool empty() const { return palette.empty(); }
|
||||
@@ -780,9 +747,6 @@ struct TextureColorRequest
|
||||
// made of flat colours (TextureDetail::flat_colors) is matched with this one, so a tile or a logo
|
||||
// prints in single filaments while a photograph on another layer may still use mixes.
|
||||
ColorQuantizeFn quantize_pure;
|
||||
// Palette index + position -> filament. Optional: without it a palette index is taken to be a
|
||||
// filament index directly, which is the no-mixing case.
|
||||
ColorResolveFn resolve;
|
||||
// Majority-filter passes over the *perceived* colour, before any interleaving is resolved.
|
||||
//
|
||||
// Sampling a detailed image once per triangle leaves salt-and-pepper wherever the image's own
|
||||
|
||||
@@ -17,6 +17,9 @@ typedef std::vector<Color> ColorList;
|
||||
typedef std::array<double, 3> ColorDouble;
|
||||
typedef std::array<std::size_t, 3> RGB;
|
||||
|
||||
// Function pointer type that points to a specific color-difference function based on the chosen method.
|
||||
using DistanceFunction = double (*)(const Color&, const Color&);
|
||||
|
||||
// Color space used for computing color differences.
|
||||
enum struct ColorDifferenceMethod : std::size_t {
|
||||
RGB = 0, // Simplest and fastest
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace Slic3r { namespace tex2color {
|
||||
|
||||
namespace PMP = CGAL::Polygon_mesh_processing;
|
||||
|
||||
// Default upper bound on the number of half-edges in any single boundary cycle
|
||||
// that CloseBoundariesAndRepairManifoldness will attempt to triangulate. The
|
||||
// cost of triangulate_hole grows non-linearly with cycle length, so this caps
|
||||
@@ -58,7 +60,6 @@ struct BoundaryEdgeStats
|
||||
// any pre-processing (e.g. stitch_borders) needed for the count to be meaningful.
|
||||
inline BoundaryEdgeStats ComputeBoundaryEdgeStats(const cgalutils::CGALMesh& cgal_mesh)
|
||||
{
|
||||
namespace PMP = CGAL::Polygon_mesh_processing;
|
||||
using CGALMesh = cgalutils::CGALMesh;
|
||||
using HalfedgeDescriptor = boost::graph_traits<CGALMesh>::halfedge_descriptor;
|
||||
|
||||
@@ -85,7 +86,6 @@ inline BoundaryEdgeStats ComputeBoundaryEdgeStats(const cgalutils::CGALMesh& cga
|
||||
// boundary statistics; entering this function always triggers triangulation.
|
||||
inline void CloseBoundariesAndRepairManifoldness(cgalutils::CGALMesh& cgal_mesh)
|
||||
{
|
||||
namespace PMP = CGAL::Polygon_mesh_processing;
|
||||
using CGALMesh = cgalutils::CGALMesh;
|
||||
using HalfedgeDescriptor = boost::graph_traits<CGALMesh>::halfedge_descriptor;
|
||||
using FaceDescriptor = boost::graph_traits<CGALMesh>::face_descriptor;
|
||||
@@ -111,7 +111,6 @@ inline bool RepairMesh(const TriMesh& mesh,
|
||||
AlgoCancelCallback cancel_callback = nullptr,
|
||||
const RepairSetting& setting = RepairSetting{})
|
||||
{
|
||||
namespace PMP = CGAL::Polygon_mesh_processing;
|
||||
using Clock = std::chrono::steady_clock;
|
||||
auto elapsed_ms = [](Clock::time_point t0) {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
|
||||
|
||||
@@ -432,6 +432,8 @@ inline IntegerOnly<I, I> fast_round_up(double a)
|
||||
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
|
||||
}
|
||||
|
||||
template<class T> using SamePair = std::pair<T, T>;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
||||
@@ -198,3 +198,6 @@ template<> struct std::hash<NozzleDef>
|
||||
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
|
||||
};
|
||||
};
|
||||
|
||||
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
|
||||
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;
|
||||
@@ -183,10 +183,13 @@ wxDECLARE_EVENT(EVT_GLCANVAS_PLATE_NAME_CHANGE, SimpleEvent);
|
||||
//BBS: declare EVT_GLCANVAS_PLATE_SELECT
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_PLATE_SELECT, SimpleEvent);
|
||||
|
||||
using Vec2dEvent = Event<Vec2d>;
|
||||
// _bool_ value is used as a indicator of selection in the 3DScene
|
||||
using RBtnEvent = Event<std::pair<Vec2d, bool>>;
|
||||
using RBtnPlateEvent = Event<std::pair<Vec2d, int>>;
|
||||
template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>;
|
||||
|
||||
using Vec3dEvent = Event<Vec3d>;
|
||||
template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>;
|
||||
|
||||
using HeightProfileSmoothEvent = Event<HeightProfileSmoothingParams>;
|
||||
|
||||
@@ -1462,11 +1462,8 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
|
||||
// How the entry prints: its filament, or for a mix the two it interleaves and in what ratio.
|
||||
shader->set_uniform(("palette_a" + idx).c_str(), e.a);
|
||||
shader->set_uniform(("palette_b" + idx).c_str(), e.b);
|
||||
shader->set_uniform(("palette_num" + idx).c_str(), e.num);
|
||||
shader->set_uniform(("palette_den" + idx).c_str(), e.den);
|
||||
}
|
||||
// The filaments those indices refer to, and the interleave the shader resolves a mix with - the
|
||||
// same inputs make_mix_resolver() gets, so the preview shows the pattern that prints rather than
|
||||
// the mix's smooth average colour. m_palette_filaments is what m_shaded_preview_palette was built from.
|
||||
const int filament_count =
|
||||
(palette_count > 0) ? int(std::min(m_palette_filaments.size(), size_t(PALETTE_MAX_FILAMENTS))) : 0;
|
||||
@@ -1475,9 +1472,6 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
|
||||
const ColorRGBA &c = m_palette_filaments[size_t(i)];
|
||||
shader->set_uniform(("filament_rgb[" + std::to_string(i) + "]").c_str(), Vec3f(c.r(), c.g(), c.b()));
|
||||
}
|
||||
shader->set_uniform("mix_mode", int(mv->texture_displacement_options.color_mix_mode));
|
||||
shader->set_uniform("layer_height", color_band_mm(*mv)); // as color_settings_for()
|
||||
shader->set_uniform("dither_cell", std::max(m_subdivide_color_mm, 0.05f) * 2.f); // as color_settings_for()
|
||||
if (color_tex != nullptr) {
|
||||
shader->set_uniform("color_tex", 1);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE1));
|
||||
@@ -2022,7 +2016,13 @@ void GLGizmoTextureDisplacement::queue_preview_job()
|
||||
// The filament list the result's indices refer to, captured with the job rather than read back
|
||||
// when it lands - loading a filament meanwhile must not recolour a preview computed against a
|
||||
// different list.
|
||||
const std::vector<ColorRGBA> filaments = m_palette_filaments;
|
||||
// Every extruder, not the palette's physical-only list: the bake writes the filament it resolved
|
||||
// to, and a mix resolves to a *mixed filament slot*, which is an extruder past the physical ones.
|
||||
// Grouping against the shorter list dropped every triangle carrying such a slot out of the mesh
|
||||
// entirely - the relief vanished and left only the few triangles that happened to print in a plain
|
||||
// filament. The palette still has to be built from physical filaments alone (see filament_palette()),
|
||||
// which is why these two are not the same list.
|
||||
const std::vector<ColorRGBA> filaments = wxGetApp().plater()->get_extruders_colors();
|
||||
|
||||
m_preview_job_running = true;
|
||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||
@@ -4219,19 +4219,44 @@ bool GLGizmoTextureDisplacement::any_layer_colors(const ModelVolume &mv)
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoTextureDisplacement::bind_mixes_to_filament_slots(std::vector<PaletteEntry> &palette)
|
||||
{
|
||||
Sidebar *sidebar = &wxGetApp().plater()->sidebar();
|
||||
if (sidebar == nullptr)
|
||||
return;
|
||||
for (PaletteEntry &e : palette) {
|
||||
if (!e.is_mix())
|
||||
continue;
|
||||
// Components are 1-based in the config; the ratios are percentages summing to 100, which is the
|
||||
// form create_mixed_filament_from_result() normalises from.
|
||||
const int a_pct = int(std::lround(100.0 * double(e.num) / double(e.den)));
|
||||
const int slot = sidebar->ensure_mixed_filament({ unsigned(e.a + 1), unsigned(e.b + 1) },
|
||||
{ a_pct, 100 - a_pct });
|
||||
if (slot >= 0) {
|
||||
e.a = e.b = slot;
|
||||
e.num = e.den = 1;
|
||||
} else {
|
||||
// No room for another slot. Collapse to the component that dominates the blend, which is what
|
||||
// the old per-triangle path did on a surface it could not band anyway.
|
||||
const int dominant = (e.num * 2 >= e.den) ? e.a : e.b;
|
||||
e.a = e.b = dominant;
|
||||
e.num = e.den = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextureColorSettings GLGizmoTextureDisplacement::color_settings_for(const ModelVolume &mv)
|
||||
{
|
||||
TextureColorSettings out;
|
||||
if (!any_layer_colors(mv))
|
||||
return out; // nothing is colouring: every colour path stays switched off
|
||||
out.palette = cached_palette();
|
||||
out.palette_pure = make_palette(m_palette_filaments, /* mixing */ false);
|
||||
out.mix_mode = mv.texture_displacement_options.color_mix_mode;
|
||||
out.palette_pure = make_palette(m_palette_filaments, /* mixing */ false, PALETTE_MAX_ENTRIES);
|
||||
// Done here rather than in cached_palette(): this runs when a preview or a bake is queued, off a
|
||||
// user action, while that one is also touched from the render path - and creating filament slots
|
||||
// there would mutate the project mid-frame.
|
||||
bind_mixes_to_filament_slots(out.palette);
|
||||
out.despeckle_passes = mv.texture_displacement_options.color_despeckle;
|
||||
out.layer_height = color_band_mm(mv);
|
||||
// The dither cell is tied to the colour-detail target: a cell much smaller than a facet cannot be
|
||||
// drawn at all, and one much larger stops reading as a blend and starts reading as a check.
|
||||
out.dither_cell_mm = std::max(m_subdivide_color_mm, 0.05f) * 2.f;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -4244,10 +4269,15 @@ const std::vector<GLGizmoTextureDisplacement::PaletteEntry> &GLGizmoTextureDispl
|
||||
const ModelVolume *mv = texture_volume();
|
||||
const bool mixing = mv != nullptr && mv->texture_displacement_options.color_mix_enabled;
|
||||
std::vector<ColorRGBA> filaments = filament_palette();
|
||||
if (m_palette_cache.empty() || filaments != m_palette_filaments || mixing != m_palette_mixing) {
|
||||
// Every mix costs a filament slot once they are bound to one, and the mask can name only so many
|
||||
// states, so the palette has to leave room beside the physical filaments it already counts.
|
||||
const int cap = int(EnforcerBlockerType::ExtruderMax);
|
||||
if (m_palette_cache.empty() || filaments != m_palette_filaments || mixing != m_palette_mixing ||
|
||||
cap != m_palette_cap) {
|
||||
m_palette_filaments = std::move(filaments);
|
||||
m_palette_mixing = mixing;
|
||||
m_palette_cache = make_palette(m_palette_filaments, mixing);
|
||||
m_palette_cap = cap;
|
||||
m_palette_cache = make_palette(m_palette_filaments, mixing, cap);
|
||||
m_palette_quantizer = make_palette_quantizer(m_palette_cache);
|
||||
}
|
||||
return m_palette_cache;
|
||||
@@ -4255,40 +4285,29 @@ const std::vector<GLGizmoTextureDisplacement::PaletteEntry> &GLGizmoTextureDispl
|
||||
|
||||
std::vector<ColorRGBA> GLGizmoTextureDisplacement::filament_palette()
|
||||
{
|
||||
std::vector<ColorRGBA> palette = wxGetApp().plater()->get_extruders_colors();
|
||||
// mmu_segmentation_facets encodes the filament in a 6-bit prefix code and stops at Extruder16.
|
||||
std::vector<ColorRGBA> all = wxGetApp().plater()->get_extruders_colors();
|
||||
|
||||
// Physical filaments only. The mixes this palette produces each become a mixed filament slot of
|
||||
// their own (see bind_mixes_to_filament_slots()), and those slots are extruders too - so taking the
|
||||
// list as it comes meant the next rebuild mixed *them* again, and handed components naming a
|
||||
// virtual slot to a blend that can only name physical ones. That is what left entries reading
|
||||
// "filament 1 plus nothing" and raised "Mixed filament has invalid or mismatched components".
|
||||
const auto *is_mixed = wxGetApp().preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
std::vector<ColorRGBA> palette;
|
||||
palette.reserve(all.size());
|
||||
for (size_t i = 0; i < all.size(); ++i)
|
||||
if (is_mixed == nullptr || i >= is_mixed->values.size() || !is_mixed->values[i])
|
||||
palette.push_back(all[i]);
|
||||
|
||||
// A paint mask can only name so many states, and every mix spends one beside these.
|
||||
if (palette.size() > size_t(EnforcerBlockerType::ExtruderMax))
|
||||
palette.resize(size_t(EnforcerBlockerType::ExtruderMax));
|
||||
return palette;
|
||||
}
|
||||
|
||||
float GLGizmoTextureDisplacement::color_band_mm(const ModelVolume &mv)
|
||||
{
|
||||
const float lh = print_layer_height();
|
||||
const float edge = (mv.texture_displacement_options.v2_refine_mm > 0.f) ? mv.texture_displacement_options.v2_refine_mm
|
||||
: v2_recommendation(mv).edge_mm;
|
||||
if (edge <= 0.f || lh <= 0.f)
|
||||
return lh;
|
||||
// A refined triangle of edge e stacks in rows about 0.87 * e apart (an equilateral triangle's
|
||||
// height), and a dither needs at least two rows per period to be a dither at all.
|
||||
constexpr float ROW_PER_EDGE = 0.87f;
|
||||
return lh * std::max(1.f, std::ceil(2.f * ROW_PER_EDGE * edge / lh));
|
||||
}
|
||||
|
||||
float GLGizmoTextureDisplacement::print_layer_height()
|
||||
{
|
||||
try {
|
||||
const DynamicPrintConfig &cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
if (const ConfigOptionFloat *opt = cfg.option<ConfigOptionFloat>("layer_height"); opt != nullptr)
|
||||
if (opt->value > 1e-3)
|
||||
return float(opt->value);
|
||||
} catch (...) {
|
||||
}
|
||||
return 0.2f;
|
||||
}
|
||||
|
||||
std::vector<GLGizmoTextureDisplacement::PaletteEntry> GLGizmoTextureDisplacement::make_palette(
|
||||
const std::vector<ColorRGBA> &filaments, bool mixing)
|
||||
const std::vector<ColorRGBA> &filaments, bool mixing, int max_entries)
|
||||
{
|
||||
std::vector<PaletteEntry> out;
|
||||
const int n = int(filaments.size());
|
||||
@@ -4305,7 +4324,7 @@ std::vector<GLGizmoTextureDisplacement::PaletteEntry> GLGizmoTextureDisplacement
|
||||
const int pairs = n * (n - 1) / 2;
|
||||
int steps = 0;
|
||||
for (int s = 5; s >= 1; --s)
|
||||
if (n + pairs * s <= PALETTE_MAX_ENTRIES) {
|
||||
if (n + pairs * s <= max_entries) {
|
||||
steps = s;
|
||||
break;
|
||||
}
|
||||
@@ -4327,51 +4346,6 @@ std::vector<GLGizmoTextureDisplacement::PaletteEntry> GLGizmoTextureDisplacement
|
||||
return out;
|
||||
}
|
||||
|
||||
ColorResolveFn GLGizmoTextureDisplacement::make_mix_resolver(const std::vector<PaletteEntry> &palette,
|
||||
ColorMixMode mode, float layer_height,
|
||||
float cell_mm)
|
||||
{
|
||||
if (palette.empty())
|
||||
return nullptr;
|
||||
auto entries = std::make_shared<std::vector<PaletteEntry>>(palette);
|
||||
const float band = std::max(layer_height, 0.01f);
|
||||
const float cell = std::max(cell_mm, 0.01f);
|
||||
|
||||
return [entries, mode, band, cell](int index, const Vec3f &pos, const Vec3f &normal) -> int {
|
||||
if (index < 0 || size_t(index) >= entries->size())
|
||||
return -1;
|
||||
const PaletteEntry &e = (*entries)[size_t(index)];
|
||||
if (!e.is_mix())
|
||||
return e.a;
|
||||
|
||||
// Which of the two filaments this point falls on. Both patterns are *ordered*, never random:
|
||||
// the eye blends a regular pattern into a flat colour, and turns a random one into noise.
|
||||
// Auto: bands wherever the surface is steeper than ~45 degrees - consecutive layers alternate
|
||||
// there, which is how a blend prints and reads. On a flat-facing surface a layer is one band
|
||||
// and the only way to interleave is a checkerboard across the surface, which at print scale
|
||||
// reads as a pattern rather than a colour; there the mix falls back to its dominant filament.
|
||||
const bool upright = std::abs(normal.z()) < 0.7f;
|
||||
if (mode == ColorMixMode::Auto && !upright)
|
||||
return e.num * 2 >= e.den ? e.a : e.b;
|
||||
const bool bands = mode == ColorMixMode::ZBands || mode == ColorMixMode::Auto;
|
||||
if (bands) {
|
||||
// One band per print layer. floorf, not a cast, so this stays correct below z = 0.
|
||||
const int slot = int(std::floor(pos.z() / band));
|
||||
const int phase = ((slot % e.den) + e.den) % e.den;
|
||||
return phase < e.num ? e.a : e.b;
|
||||
}
|
||||
// Ordered 4x4 Bayer over the surface, indexed by position so the pattern is stable in space
|
||||
// rather than in triangle order (which would move under any remesh, and read as noise).
|
||||
static const int BAYER[16] = { 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 };
|
||||
const int gx = ((int(std::floor(pos.x() / cell)) % 4) + 4) % 4;
|
||||
const int gy = ((int(std::floor(pos.y() / cell)) % 4) + 4) % 4;
|
||||
// A third axis would be ideal, but the two dominant ones are enough for a surface pattern and
|
||||
// keep the cell square on the faces that matter.
|
||||
const float threshold = (float(BAYER[gy * 4 + gx]) + 0.5f) / 16.f;
|
||||
return (float(e.num) / float(e.den)) > threshold ? e.a : e.b;
|
||||
};
|
||||
}
|
||||
|
||||
ColorQuantizeFn GLGizmoTextureDisplacement::make_palette_quantizer(const std::vector<PaletteEntry> &palette)
|
||||
{
|
||||
if (palette.empty())
|
||||
@@ -4408,10 +4382,15 @@ ColorQuantizeFn GLGizmoTextureDisplacement::make_palette_quantizer(const std::ve
|
||||
best_pure = int(i);
|
||||
}
|
||||
}
|
||||
// A mix is an interleave that only reads as its colour from a distance; up close
|
||||
// it is stripes. Spend it only where it buys a clearly better match than the nearest
|
||||
// single filament: ten Delta E is a visible step, less is not worth the stripes.
|
||||
constexpr float PREFER_PURE_DE = 10.f;
|
||||
// A mix is an interleave that only reads as its colour from a distance; up close it is
|
||||
// stripes. So it is spent only where it buys a better match than the nearest single
|
||||
// filament - but "better" was set at ten Delta E, which is not a visible step, it is a
|
||||
// different colour. Measured over the whole cube that threshold turned 94% of the
|
||||
// lookups that wanted a mix back into a pure filament, leaving 38%; along a greyscale
|
||||
// ramp, the shape a height texture actually traces, it cut 80% to 66%. Two Delta E is
|
||||
// about where a side-by-side difference stops being arguable, which is the right place
|
||||
// to start paying for stripes.
|
||||
constexpr float PREFER_PURE_DE = 2.f;
|
||||
if (best_pure >= 0 && palette[size_t(best)].is_mix() && best_pure_d - best_d < PREFER_PURE_DE)
|
||||
best = best_pure;
|
||||
(*lut)[(size_t(r) * E + size_t(g)) * E + size_t(b)] = uint8_t(best);
|
||||
@@ -4511,7 +4490,7 @@ TextureDisplacementPrepareResult GLGizmoTextureDisplacement::prepare_mesh(
|
||||
if (params.subdiv_color_edge_mm > 0.f && !palette.empty())
|
||||
color = make_combined_color_sampler(mesh.its, layers, current, make_palette_quantizer(palette));
|
||||
// Note the sampler is built on the *quantizer* alone - the refinement follows perceived
|
||||
// colour, never the interleaving that realises a mix (see ColorResolveFn).
|
||||
// colour, never the interleaving that realises a mix - the slicer does that per layer.
|
||||
// "Min edge" is a feature-mode control (it is the floor the curvature test refines down
|
||||
// to); in plain adaptive mode the target edge length is the only criterion, so the floor
|
||||
// must not be allowed to silently override a target the user set below it.
|
||||
@@ -6069,24 +6048,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
||||
"of filaments can cover a photo or a gradient. An image of flat colors "
|
||||
"prints the same either way. Off uses one filament per area."));
|
||||
if (opts.color_mix_enabled) {
|
||||
slider_label(_L("Mix by"));
|
||||
const std::string mix_z = _u8L("Layers");
|
||||
const std::string mix_xy = _u8L_CONTEXT("Surface", "Texture Displacement");
|
||||
const std::string mix_auto = _u8L("Automatic");
|
||||
const char *mix_items[] = { mix_z.c_str(), mix_xy.c_str(), mix_auto.c_str() };
|
||||
int mix_mode = int(opts.color_mix_mode);
|
||||
ImGui::SetNextItemWidth(-card_pad);
|
||||
if (scoped_combo("##color_mix_mode", &mix_mode, mix_items, IM_ARRAYSIZE(mix_items))) {
|
||||
opts.color_mix_mode = ColorMixMode(mix_mode);
|
||||
m_preview_params_dirty = true;
|
||||
}
|
||||
hover_tip(_u8L("Layers: the two filaments alternate between print layers, which "
|
||||
"blends smoothly on upright surfaces but disappears on flat-facing "
|
||||
"ones, where a whole layer is a single band.\n"
|
||||
"Surface: a fine checkerboard across the surface, which works at "
|
||||
"any angle but can read as texture rather than as a blend.\n"
|
||||
"Automatic: layers on upright faces; flat-facing faces take the nearer "
|
||||
"single filament, since a checkerboard there shows as a pattern."));
|
||||
ImGui::TextDisabled("%s", Slic3r::format(_u8L("%1% printable colors from %2% filaments"),
|
||||
int(cached_palette().size()), int(m_palette_filaments.size())).c_str());
|
||||
}
|
||||
|
||||
@@ -81,7 +81,11 @@ public:
|
||||
// How many ratios depends on how many filaments there are, so the palette stays bounded: the
|
||||
// quantizer's lookup cube costs one DeltaE00 per cell per entry to fill, and with sixteen
|
||||
// filaments there are already plenty of colours without mixing any of them.
|
||||
static std::vector<PaletteEntry> make_palette(const std::vector<ColorRGBA> &filaments, bool mixing);
|
||||
// `max_entries` bounds the whole palette. It is normally the quantizer's own limit, but when the
|
||||
// mixes become filament slots it has to be the paint mask's instead: a mask can name only
|
||||
// EnforcerBlockerType::ExtruderMax states, and every mix now occupies one of them.
|
||||
static std::vector<PaletteEntry> make_palette(const std::vector<ColorRGBA> &filaments, bool mixing,
|
||||
int max_entries);
|
||||
|
||||
// Maps an image colour to the closest entry of `palette`, perceptually (CIEDE2000 over CIELAB - a
|
||||
// plain RGB distance picks visibly wrong filaments, most obviously between a saturated colour and
|
||||
@@ -95,9 +99,6 @@ public:
|
||||
|
||||
// Turns a palette index plus a position into the filament to print there, interleaving the two
|
||||
// filaments of a mixed entry per `mode`. `layer_height` sizes the Z bands; `cell_mm` the dither
|
||||
// cells. See ColorResolveFn for why this is separate from the quantizer.
|
||||
static ColorResolveFn make_mix_resolver(const std::vector<PaletteEntry> &palette, ColorMixMode mode,
|
||||
float layer_height, float cell_mm);
|
||||
|
||||
// Everything the jobs need to colour with, for the current volume: palette, mix mode, layer
|
||||
// height, despeckle. Empty when no layer is actually colouring.
|
||||
@@ -106,16 +107,20 @@ public:
|
||||
// The printable palette for the current filaments and mixing setting, rebuilt only when either
|
||||
// actually changes - see the definition for why that caching is not optional.
|
||||
const std::vector<PaletteEntry> &cached_palette();
|
||||
// Turns every mix in `palette` into a mixed filament slot and rewrites the entry to name that slot
|
||||
// as a plain filament, so nothing downstream has to know a mix is involved: is_mix() goes false and
|
||||
// the resolver simply returns it. The per-layer interleaving then happens in the slicer, where it is
|
||||
// not limited by how fine the mesh is. Entries whose slot could not be created (the paint-state cap)
|
||||
// fall back to the nearer of the two components.
|
||||
void bind_mixes_to_filament_slots(std::vector<PaletteEntry> &palette);
|
||||
std::vector<PaletteEntry> m_palette_cache;
|
||||
std::vector<ColorRGBA> m_palette_filaments;
|
||||
int m_palette_cap = 0; // the max_entries m_palette_cache was built with
|
||||
bool m_palette_mixing = false;
|
||||
ColorQuantizeFn m_palette_quantizer;
|
||||
|
||||
// The loaded filaments, clamped to the sixteen mmu_segmentation_facets can address.
|
||||
static std::vector<ColorRGBA> filament_palette();
|
||||
// The print's layer height, which sizes ColorMixMode::ZBands. Falls back to 0.2 mm if it cannot be
|
||||
// read - a wrong band size is a cosmetic error, not a reason to refuse to colour anything.
|
||||
static float print_layer_height();
|
||||
// The Z band height, in mm. One print layer is the ideal, but the interleave is realised per
|
||||
// *facet*: a band thinner than the mesh can resolve does not dither, it beats against the triangle
|
||||
// grid and comes out as broad horizontal stripes - and since MMU segmentation reads facet colour,
|
||||
@@ -123,7 +128,6 @@ public:
|
||||
// diagonal and knows nothing about the layer height, so the band is rounded up to a whole number of
|
||||
// layers at least two facet rows tall: still exact on the printer, and representable by the mesh
|
||||
// that has to carry it. Used by both the bake settings and the preview shader, so the two agree.
|
||||
float color_band_mm(const ModelVolume &mv);
|
||||
|
||||
// The Normal preview's triangles, grouped by the filament they will print in. Colour is per facet
|
||||
// and there are at most sixteen filaments, so the mesh is uploaded once with its index buffer
|
||||
|
||||
@@ -53,9 +53,6 @@ void TextureDisplacementBakeJob::process(Ctl &ctl)
|
||||
color_request.quantize = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette);
|
||||
if (!m_input.color.palette_pure.empty())
|
||||
color_request.quantize_pure = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette_pure);
|
||||
color_request.resolve = GLGizmoTextureDisplacement::make_mix_resolver(
|
||||
m_input.color.palette, m_input.color.mix_mode, m_input.color.layer_height,
|
||||
m_input.color.dither_cell_mm);
|
||||
color_request.despeckle_passes = m_input.color.despeckle_passes;
|
||||
color_request.out_triangle = &m_triangle_color;
|
||||
if (color_request.quantize)
|
||||
|
||||
@@ -32,9 +32,6 @@ void TextureDisplacementPreviewJob::process(Ctl &ctl)
|
||||
color_request.quantize = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette);
|
||||
if (!m_input.color.palette_pure.empty())
|
||||
color_request.quantize_pure = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette_pure);
|
||||
color_request.resolve = GLGizmoTextureDisplacement::make_mix_resolver(
|
||||
m_input.color.palette, m_input.color.mix_mode, m_input.color.layer_height,
|
||||
m_input.color.dither_cell_mm);
|
||||
color_request.despeckle_passes = m_input.color.despeckle_passes;
|
||||
color_request.out_triangle = &m_result.triangle_color;
|
||||
if (color_request.quantize)
|
||||
|
||||
@@ -54,6 +54,7 @@ struct Option {
|
||||
Option(const ConfigOptionDef& _opt, t_config_option_key id) :
|
||||
opt(_opt), opt_id(id) {}
|
||||
};
|
||||
using t_option = std::unique_ptr<Option>; //!
|
||||
|
||||
/// Represents option lines
|
||||
class Line : public UndoValueUIManager
|
||||
|
||||
@@ -935,6 +935,7 @@ struct Sidebar::priv
|
||||
StaticLine* m_text_mixed_title{nullptr};
|
||||
ScalableButton* m_btn_mixed_add{nullptr};
|
||||
ScalableButton* m_btn_mixed_del{nullptr};
|
||||
ScalableButton* m_btn_mixed_del_all{nullptr};
|
||||
wxScrolledWindow* m_mixed_scroll_area{nullptr}; // independent scrollbar for mixed rows
|
||||
wxPanel* m_panel_mixed_content{nullptr};
|
||||
wxBoxSizer* m_sizer_mixed_filaments{nullptr}; // two-column, mirrors sizer_filaments
|
||||
@@ -3449,6 +3450,11 @@ Sidebar::Sidebar(Plater *parent)
|
||||
});
|
||||
title_sizer->Add(p->m_btn_mixed_del, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(SidebarProps::IconSpacing()));
|
||||
|
||||
p->m_btn_mixed_del_all = new ScalableButton(p->m_panel_mixed_title, wxID_ANY, "delete_all_filaments");
|
||||
p->m_btn_mixed_del_all->SetToolTip(_L("Remove all mixed filaments"));
|
||||
p->m_btn_mixed_del_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { remove_all_mixed_filaments(); });
|
||||
title_sizer->Add(p->m_btn_mixed_del_all, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(SidebarProps::IconSpacing()));
|
||||
|
||||
p->m_btn_mixed_add = new ScalableButton(p->m_panel_mixed_title, wxID_ANY, "add_filament");
|
||||
p->m_btn_mixed_add->SetToolTip(_L("Add mixed filament"));
|
||||
p->m_btn_mixed_add->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { add_mixed_filament(); });
|
||||
@@ -5122,6 +5128,58 @@ static bool create_mixed_filament_from_result(
|
||||
return true;
|
||||
}
|
||||
|
||||
int Sidebar::ensure_mixed_filament(const std::vector<unsigned int> &components, const std::vector<int> &ratios)
|
||||
{
|
||||
if (components.size() < 2 || components.size() != ratios.size())
|
||||
return -1;
|
||||
if (p->combos_filament.size() < 2)
|
||||
return -1;
|
||||
|
||||
// Normalise the way create_mixed_filament_from_result() stores them, so the comparison below sees
|
||||
// the same text the config holds rather than two spellings of one blend.
|
||||
int ratio_sum = 0;
|
||||
for (const int r : ratios)
|
||||
ratio_sum += r;
|
||||
if (ratio_sum <= 0)
|
||||
return -1;
|
||||
|
||||
std::string comp_str, ratio_str;
|
||||
{
|
||||
CNumericLocalesSetter c_locale_setter;
|
||||
for (size_t i = 0; i < components.size(); ++i) {
|
||||
if (i > 0) { comp_str += ","; ratio_str += ","; }
|
||||
comp_str += std::to_string(components[i]);
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%.4f", float(ratios[i]) / float(ratio_sum));
|
||||
ratio_str += buf;
|
||||
}
|
||||
}
|
||||
|
||||
const auto &project_config = wxGetApp().preset_bundle->project_config;
|
||||
const auto *is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
const auto *comp_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||
const auto *ratios_opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
||||
if (is_mixed_opt != nullptr && comp_opt != nullptr && ratios_opt != nullptr)
|
||||
for (size_t i = 0; i < is_mixed_opt->values.size(); ++i)
|
||||
if (is_mixed_opt->values[i] && i < comp_opt->values.size() && i < ratios_opt->values.size() &&
|
||||
comp_opt->values[i] == comp_str && ratios_opt->values[i] == ratio_str)
|
||||
return int(i);
|
||||
|
||||
if (wxGetApp().preset_bundle->filament_presets.size() >= size_t(EnforcerBlockerType::ExtruderMax))
|
||||
return -1;
|
||||
|
||||
std::vector<std::string> color_strs, names, types;
|
||||
collect_physical_filament_info(color_strs, names, types);
|
||||
|
||||
MixedFilamentResult result;
|
||||
result.components = components;
|
||||
result.ratios = ratios;
|
||||
const size_t created_at = wxGetApp().preset_bundle->filament_presets.size();
|
||||
if (!create_mixed_filament_from_result(this, result, color_strs))
|
||||
return -1;
|
||||
return int(created_at);
|
||||
}
|
||||
|
||||
void Sidebar::add_mixed_filament()
|
||||
{
|
||||
auto* plater = dynamic_cast<Plater*>(GetParent());
|
||||
@@ -5299,6 +5357,28 @@ void Sidebar::edit_mixed_filament(size_t panel_idx)
|
||||
}
|
||||
}
|
||||
|
||||
void Sidebar::remove_all_mixed_filaments()
|
||||
{
|
||||
auto *plater = dynamic_cast<Plater *>(GetParent());
|
||||
if (plater == nullptr)
|
||||
return;
|
||||
const size_t count = plater->mixed_filament_config_indices().size();
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// Worth a confirmation: this drops filament slots the model may be painted with, and anything
|
||||
// painted in one falls back to a plain filament.
|
||||
MessageDialog dlg(this, format_wxstr(_L("Remove all %1% mixed filaments?"), count), _L("Mixed Filament"),
|
||||
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return;
|
||||
|
||||
// Back to front: delete_mixed_filament_at() indexes the list as it stands, so removing from the end
|
||||
// leaves the indices of everything still to go untouched.
|
||||
for (size_t i = count; i-- > 0;)
|
||||
delete_mixed_filament_at(i);
|
||||
}
|
||||
|
||||
void Sidebar::delete_mixed_filament_at(size_t panel_idx)
|
||||
{
|
||||
auto* plater = dynamic_cast<Plater*>(GetParent());
|
||||
|
||||
@@ -108,6 +108,7 @@ class PlaterPresetComboBox;
|
||||
class PartPlateList;
|
||||
class SyncNozzleAndAmsDialog;
|
||||
class FinishSyncAmsDialog;
|
||||
using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
|
||||
|
||||
class Plater;
|
||||
enum class ActionButtonType : int;
|
||||
@@ -291,8 +292,19 @@ public:
|
||||
|
||||
// Mixed-color filament sidebar section
|
||||
void add_mixed_filament();
|
||||
// The filament slot that blends `components` (1-based physical filament indices) in `ratios`
|
||||
// (percentages), creating it when no existing mixed slot already describes that blend. Returns the
|
||||
// 0-based filament index, or -1 when the paint-state cap leaves no room for another one.
|
||||
//
|
||||
// Exists so a feature that needs a blend can ask for one without going through the modal dialog:
|
||||
// the texture displacement gizmo turns each mix in its palette into a slot, which is what moves the
|
||||
// interleaving from its own paint mask to the slicer, where it happens per layer.
|
||||
int ensure_mixed_filament(const std::vector<unsigned int> &components, const std::vector<int> &ratios);
|
||||
void edit_mixed_filament(size_t idx);
|
||||
void delete_mixed_filament_at(size_t idx);
|
||||
// Drops every mixed filament at once, after confirming. The texture displacement gizmo can create
|
||||
// one slot per colour in its palette, so clearing them one at a time is tedious.
|
||||
void remove_all_mixed_filaments();
|
||||
void decompose_filament_color(int filament_idx);
|
||||
void recalc_filament_scroll_sizes();
|
||||
void update_mixed_filament_list();
|
||||
@@ -324,6 +336,7 @@ private:
|
||||
class Plater: public wxPanel
|
||||
{
|
||||
public:
|
||||
using fs_path = boost::filesystem::path;
|
||||
|
||||
Plater(wxWindow *parent, MainFrame *main_frame);
|
||||
Plater(Plater &&) = delete;
|
||||
|
||||
@@ -596,8 +596,7 @@ PrintParams_Legacy BBLNetworkPlugin::as_legacy(PrintParams& param)
|
||||
}
|
||||
|
||||
// Every PrintParams field except the four the 02.08.01 series added
|
||||
// (task_timelapse_use_internal, extruder_cali_manual_mode, svc_context, slicer_uid) and the
|
||||
// queue_plate_id 02.08.02 appended.
|
||||
// (task_timelapse_use_internal, extruder_cali_manual_mode, svc_context, slicer_uid).
|
||||
PrintParams_0203 BBLNetworkPlugin::as_0203(PrintParams& param)
|
||||
{
|
||||
PrintParams_0203 p;
|
||||
|
||||
@@ -89,6 +89,7 @@ using fn_ft_job_get_result = ft_err(FT_CALL *)(FT_JobHandle *, uint32_t timeo
|
||||
using fn_ft_tunnel_start_job = ft_err(FT_CALL *)(FT_TunnelHandle *, FT_JobHandle *);
|
||||
using fn_ft_job_cancel = ft_err(FT_CALL *)(FT_JobHandle *);
|
||||
|
||||
using fn_ft_job_msg_destroy = void(FT_CALL *)(ft_job_msg *);
|
||||
using fn_ft_job_set_msg_cb = ft_err(FT_CALL *)(FT_JobHandle *, void(FT_CALL *)(void *user, ft_job_msg msg), void *user);
|
||||
using fn_ft_job_try_get_msg = ft_err(FT_CALL *)(FT_JobHandle *, ft_job_msg *out_msg);
|
||||
using fn_ft_job_get_msg = ft_err(FT_CALL *)(FT_JobHandle *, uint32_t timeout_ms, ft_job_msg *out_msg);
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
// Generic command / response TCP telnet like console class.
|
||||
// Used by the MKS host to send G-code commands to test connection ("M105") and to start printing ("M23 filename", "M24").
|
||||
class TCPConsole
|
||||
{
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
public:
|
||||
TCPConsole() : m_resolver(m_io_context), m_socket(m_io_context) { set_defaults(); }
|
||||
TCPConsole(const std::string& host_name, const std::string& port_name) : m_resolver(m_io_context), m_socket(m_io_context)
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
|
||||
namespace net = boost::asio; // from <boost/asio.hpp>
|
||||
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
|
||||
class WebSocketClient {
|
||||
public:
|
||||
@@ -31,7 +36,7 @@ public:
|
||||
}
|
||||
try {
|
||||
// Close the WebSocket connection
|
||||
ws_.close(boost::beast::websocket::close_code::normal);
|
||||
ws_.close(websocket::close_code::normal);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
@@ -44,7 +49,7 @@ public:
|
||||
auto const results = resolver_.resolve(host, port);
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
auto ep = boost::asio::connect(ws_.next_layer(), results);
|
||||
auto ep = net::connect(ws_.next_layer(), results);
|
||||
std::string _host = host;
|
||||
//if _host last char is '/', remove it
|
||||
if(_host.size()>0&&_host[host.size()-1] == '/'){
|
||||
@@ -53,10 +58,10 @@ public:
|
||||
|
||||
// _host += ':' + std::to_string(ep.port());
|
||||
// Set a decorator to change the User-Agent of the handshake
|
||||
ws_.set_option(boost::beast::websocket::stream_base::decorator(
|
||||
[](boost::beast::websocket::request_type& req)
|
||||
ws_.set_option(websocket::stream_base::decorator(
|
||||
[](websocket::request_type& req)
|
||||
{
|
||||
req.set(boost::beast::http::field::user_agent,"ElegooSlicer");
|
||||
req.set(http::field::user_agent,"ElegooSlicer");
|
||||
}));
|
||||
// Perform the WebSocket handshake
|
||||
ws_.handshake(_host, path);
|
||||
@@ -65,25 +70,25 @@ public:
|
||||
|
||||
void send(const std::string& message){
|
||||
// Send a message
|
||||
ws_.write(boost::asio::buffer(message));
|
||||
ws_.write(net::buffer(message));
|
||||
}
|
||||
|
||||
std::string receive(int timeout = 0){
|
||||
// This buffer will hold the incoming message
|
||||
boost::beast::flat_buffer buffer;
|
||||
beast::flat_buffer buffer;
|
||||
|
||||
// Read a message into our buffer
|
||||
ws_.read(buffer);
|
||||
|
||||
// Return the message as a string
|
||||
return boost::beast::buffers_to_string(buffer.data());
|
||||
return beast::buffers_to_string(buffer.data());
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
boost::asio::io_context ioc_;
|
||||
boost::asio::ip::tcp::resolver resolver_;
|
||||
boost::beast::websocket::stream<boost::asio::ip::tcp::socket> ws_;
|
||||
net::io_context ioc_;
|
||||
tcp::resolver resolver_;
|
||||
websocket::stream<tcp::socket> ws_;
|
||||
bool is_connect;
|
||||
};
|
||||
|
||||
|
||||
@@ -336,7 +336,6 @@ struct PrintParams {
|
||||
bool try_emmc_print;
|
||||
std::string svc_context;
|
||||
std::string slicer_uid;
|
||||
std::string queue_plate_id;
|
||||
};
|
||||
|
||||
struct TaskQueryParams
|
||||
@@ -413,7 +412,7 @@ enum class NetworkAbi {
|
||||
Unsupported, // no generation in this build can call it - never dispatch through it
|
||||
Legacy, // 01.10.01: PrintParams_Legacy; send_message/send_message_to_printer take no flag
|
||||
V0203, // 02.03.00: PrintParams_0203; bind takes no dev_model
|
||||
Current, // 02.08.04: the layouts and signatures this build declares directly
|
||||
Current, // 02.08.01: the layouts and signatures this build declares directly
|
||||
};
|
||||
|
||||
struct NetworkLibraryVersion {
|
||||
@@ -426,12 +425,10 @@ struct NetworkLibraryVersion {
|
||||
};
|
||||
|
||||
// Every row names the generation that can call it, so a series can never be offered without a
|
||||
// host-side ABI for it. Series with no generation - 02.08.01 (whose PrintParams lacks the
|
||||
// queue_plate_id that 02.08.02 appended, and whose malformed bind table macOS 27 refuses to
|
||||
// load), 02.01.01, 02.00.02 and older - must stay out; is_supported_network_version() is the
|
||||
// gate that keeps them from loading.
|
||||
// host-side ABI for it. Series with no generation - 02.01.01, 02.00.02 and older - must stay out;
|
||||
// is_supported_network_version() is the gate that keeps them from loading.
|
||||
static const NetworkLibraryVersion AVAILABLE_NETWORK_VERSIONS[] = {
|
||||
{"02.08.04", "02.08.04", nullptr, true, nullptr, NetworkAbi::Current},
|
||||
{"02.08.01", "02.08.01", nullptr, true, nullptr, NetworkAbi::Current},
|
||||
{"02.03.00", "02.03.00", nullptr, false,
|
||||
"An older plug-in series. Features that need newer plug-in support, such as print-failure "
|
||||
"snapshots in the device error dialog, are unavailable.", NetworkAbi::V0203},
|
||||
|
||||
@@ -17,18 +17,20 @@
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Put serializers in correct ADL namespaces for each type
|
||||
|
||||
namespace Slic3r {
|
||||
namespace FilamentGroupUtils {
|
||||
|
||||
inline void to_json(nlohmann::json& j, const Color& c) {
|
||||
inline void to_json(json& j, const Color& c) {
|
||||
char buf[10];
|
||||
snprintf(buf, sizeof(buf), "#%02X%02X%02X%02X", c.r, c.g, c.b, c.a);
|
||||
j = std::string(buf);
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, Color& c) {
|
||||
inline void from_json(const json& j, Color& c) {
|
||||
std::string s = j.get<std::string>();
|
||||
if (s.size() >= 7 && s[0] == '#') {
|
||||
c.r = (unsigned char)std::stoi(s.substr(1, 2), nullptr, 16);
|
||||
@@ -38,8 +40,8 @@ inline void from_json(const nlohmann::json& j, Color& c) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json& j, const FilamentInfo& fi) {
|
||||
j = nlohmann::json{
|
||||
inline void to_json(json& j, const FilamentInfo& fi) {
|
||||
j = json{
|
||||
{"color", fi.color},
|
||||
{"type", fi.type},
|
||||
{"is_support", fi.is_support},
|
||||
@@ -47,15 +49,15 @@ inline void to_json(nlohmann::json& j, const FilamentInfo& fi) {
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentInfo& fi) {
|
||||
inline void from_json(const json& j, FilamentInfo& fi) {
|
||||
fi.color = j.at("color").get<Color>();
|
||||
j.at("type").get_to(fi.type);
|
||||
j.at("is_support").get_to(fi.is_support);
|
||||
fi.usage_type = (FilamentUsageType)j.at("usage_type").get<int>();
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json& j, const MachineFilamentInfo& mfi) {
|
||||
j = nlohmann::json{
|
||||
inline void to_json(json& j, const MachineFilamentInfo& mfi) {
|
||||
j = json{
|
||||
{"color", mfi.color},
|
||||
{"type", mfi.type},
|
||||
{"is_support", mfi.is_support},
|
||||
@@ -65,7 +67,7 @@ inline void to_json(nlohmann::json& j, const MachineFilamentInfo& mfi) {
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, MachineFilamentInfo& mfi) {
|
||||
inline void from_json(const json& j, MachineFilamentInfo& mfi) {
|
||||
mfi.color = j.at("color").get<Color>();
|
||||
j.at("type").get_to(mfi.type);
|
||||
j.at("is_support").get_to(mfi.is_support);
|
||||
@@ -78,8 +80,8 @@ inline void from_json(const nlohmann::json& j, MachineFilamentInfo& mfi) {
|
||||
|
||||
namespace MultiNozzleUtils {
|
||||
|
||||
inline void to_json(nlohmann::json& j, const NozzleInfo& ni) {
|
||||
j = nlohmann::json{
|
||||
inline void to_json(json& j, const NozzleInfo& ni) {
|
||||
j = json{
|
||||
{"diameter", ni.diameter},
|
||||
{"volume_type", (int)ni.volume_type},
|
||||
{"extruder_id", ni.extruder_id},
|
||||
@@ -87,15 +89,15 @@ inline void to_json(nlohmann::json& j, const NozzleInfo& ni) {
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, NozzleInfo& ni) {
|
||||
inline void from_json(const json& j, NozzleInfo& ni) {
|
||||
j.at("diameter").get_to(ni.diameter);
|
||||
ni.volume_type = (NozzleVolumeType)j.at("volume_type").get<int>();
|
||||
j.at("extruder_id").get_to(ni.extruder_id);
|
||||
j.at("group_id").get_to(ni.group_id);
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json& j, const FilamentChangeTimeParams& p) {
|
||||
j = nlohmann::json{
|
||||
inline void to_json(json& j, const FilamentChangeTimeParams& p) {
|
||||
j = json{
|
||||
{"selector_load_time", p.selector_load_time},
|
||||
{"selector_unload_time", p.selector_unload_time},
|
||||
{"standard_load_time", p.standard_load_time},
|
||||
@@ -103,7 +105,7 @@ inline void to_json(nlohmann::json& j, const FilamentChangeTimeParams& p) {
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentChangeTimeParams& p) {
|
||||
inline void from_json(const json& j, FilamentChangeTimeParams& p) {
|
||||
j.at("selector_load_time").get_to(p.selector_load_time);
|
||||
j.at("selector_unload_time").get_to(p.selector_unload_time);
|
||||
j.at("standard_load_time").get_to(p.standard_load_time);
|
||||
@@ -114,22 +116,22 @@ inline void from_json(const nlohmann::json& j, FilamentChangeTimeParams& p) {
|
||||
|
||||
// ============ Helper: set<int> as JSON array ============
|
||||
namespace FGTestDetail {
|
||||
inline nlohmann::json set_to_json(const std::set<int>& s) {
|
||||
return nlohmann::json(std::vector<int>(s.begin(), s.end()));
|
||||
inline json set_to_json(const std::set<int>& s) {
|
||||
return json(std::vector<int>(s.begin(), s.end()));
|
||||
}
|
||||
|
||||
inline std::set<int> json_to_set(const nlohmann::json& j) {
|
||||
inline std::set<int> json_to_set(const json& j) {
|
||||
auto v = j.get<std::vector<int>>();
|
||||
return std::set<int>(v.begin(), v.end());
|
||||
}
|
||||
|
||||
inline nlohmann::json nvt_set_to_json(const std::set<NozzleVolumeType>& s) {
|
||||
inline json nvt_set_to_json(const std::set<NozzleVolumeType>& s) {
|
||||
std::vector<int> v;
|
||||
for (auto t : s) v.push_back((int)t);
|
||||
return nlohmann::json(v);
|
||||
return json(v);
|
||||
}
|
||||
|
||||
inline std::set<NozzleVolumeType> json_to_nvt_set(const nlohmann::json& j) {
|
||||
inline std::set<NozzleVolumeType> json_to_nvt_set(const json& j) {
|
||||
std::set<NozzleVolumeType> s;
|
||||
for (auto& item : j) s.insert((NozzleVolumeType)item.get<int>());
|
||||
return s;
|
||||
@@ -137,28 +139,28 @@ inline std::set<NozzleVolumeType> json_to_nvt_set(const nlohmann::json& j) {
|
||||
} // namespace FGTestDetail
|
||||
|
||||
// ============ FilamentGroupContext::ModelInfo ============
|
||||
inline void to_json(nlohmann::json& j, const FilamentGroupContext::ModelInfo& mi) {
|
||||
inline void to_json(json& j, const FilamentGroupContext::ModelInfo& mi) {
|
||||
using namespace FGTestDetail;
|
||||
j["flush_matrix"] = mi.flush_matrix;
|
||||
j["layer_filaments"] = mi.layer_filaments;
|
||||
|
||||
j["filament_info"] = nlohmann::json::array();
|
||||
j["filament_info"] = json::array();
|
||||
for (auto& fi : mi.filament_info)
|
||||
j["filament_info"].push_back(fi);
|
||||
|
||||
j["filament_ids"] = mi.filament_ids;
|
||||
|
||||
j["unprintable_filaments"] = nlohmann::json::array();
|
||||
j["unprintable_filaments"] = json::array();
|
||||
for (auto& s : mi.unprintable_filaments)
|
||||
j["unprintable_filaments"].push_back(set_to_json(s));
|
||||
|
||||
nlohmann::json uv = nlohmann::json::object();
|
||||
json uv = json::object();
|
||||
for (auto& [fil, types] : mi.unprintable_volumes)
|
||||
uv[std::to_string(fil)] = nvt_set_to_json(types);
|
||||
j["unprintable_volumes"] = uv;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentGroupContext::ModelInfo& mi) {
|
||||
inline void from_json(const json& j, FilamentGroupContext::ModelInfo& mi) {
|
||||
using namespace FGTestDetail;
|
||||
j.at("flush_matrix").get_to(mi.flush_matrix);
|
||||
j.at("layer_filaments").get_to(mi.layer_filaments);
|
||||
@@ -181,8 +183,8 @@ inline void from_json(const nlohmann::json& j, FilamentGroupContext::ModelInfo&
|
||||
}
|
||||
|
||||
// ============ FilamentGroupContext::GroupInfo ============
|
||||
inline void to_json(nlohmann::json& j, const FilamentGroupContext::GroupInfo& gi) {
|
||||
j = nlohmann::json{
|
||||
inline void to_json(json& j, const FilamentGroupContext::GroupInfo& gi) {
|
||||
j = json{
|
||||
{"total_filament_num", gi.total_filament_num},
|
||||
{"max_gap_threshold", gi.max_gap_threshold},
|
||||
{"mode", (int)gi.mode},
|
||||
@@ -193,7 +195,7 @@ inline void to_json(nlohmann::json& j, const FilamentGroupContext::GroupInfo& gi
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentGroupContext::GroupInfo& gi) {
|
||||
inline void from_json(const json& j, FilamentGroupContext::GroupInfo& gi) {
|
||||
j.at("total_filament_num").get_to(gi.total_filament_num);
|
||||
j.at("max_gap_threshold").get_to(gi.max_gap_threshold);
|
||||
gi.mode = (FGMode)j.at("mode").get<int>();
|
||||
@@ -204,12 +206,12 @@ inline void from_json(const nlohmann::json& j, FilamentGroupContext::GroupInfo&
|
||||
}
|
||||
|
||||
// ============ FilamentGroupContext::MachineInfo ============
|
||||
inline void to_json(nlohmann::json& j, const FilamentGroupContext::MachineInfo& mi) {
|
||||
inline void to_json(json& j, const FilamentGroupContext::MachineInfo& mi) {
|
||||
j["max_group_size"] = mi.max_group_size;
|
||||
|
||||
j["machine_filament_info"] = nlohmann::json::array();
|
||||
j["machine_filament_info"] = json::array();
|
||||
for (auto& vec : mi.machine_filament_info) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
json arr = json::array();
|
||||
for (auto& mfi : vec) arr.push_back(mfi);
|
||||
j["machine_filament_info"].push_back(arr);
|
||||
}
|
||||
@@ -218,7 +220,7 @@ inline void to_json(nlohmann::json& j, const FilamentGroupContext::MachineInfo&
|
||||
j["master_extruder_id"] = mi.master_extruder_id;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentGroupContext::MachineInfo& mi) {
|
||||
inline void from_json(const json& j, FilamentGroupContext::MachineInfo& mi) {
|
||||
j.at("max_group_size").get_to(mi.max_group_size);
|
||||
|
||||
mi.machine_filament_info.clear();
|
||||
@@ -234,10 +236,10 @@ inline void from_json(const nlohmann::json& j, FilamentGroupContext::MachineInfo
|
||||
}
|
||||
|
||||
// ============ FilamentGroupContext::SpeedInfo ============
|
||||
inline void to_json(nlohmann::json& j, const FilamentGroupContext::SpeedInfo& si) {
|
||||
nlohmann::json fpt = nlohmann::json::object();
|
||||
inline void to_json(json& j, const FilamentGroupContext::SpeedInfo& si) {
|
||||
json fpt = json::object();
|
||||
for (auto& [fil, inner] : si.filament_print_time) {
|
||||
nlohmann::json inner_j = nlohmann::json::object();
|
||||
json inner_j = json::object();
|
||||
for (auto& [layer, time] : inner)
|
||||
inner_j[std::to_string(layer)] = time;
|
||||
fpt[std::to_string(fil)] = inner_j;
|
||||
@@ -250,7 +252,7 @@ inline void to_json(nlohmann::json& j, const FilamentGroupContext::SpeedInfo& si
|
||||
j["ams_preload_enabled"] = si.ams_preload_enabled;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentGroupContext::SpeedInfo& si) {
|
||||
inline void from_json(const json& j, FilamentGroupContext::SpeedInfo& si) {
|
||||
si.filament_print_time.clear();
|
||||
if (j.contains("filament_print_time")) {
|
||||
for (auto& [k, v] : j.at("filament_print_time").items()) {
|
||||
@@ -267,23 +269,23 @@ inline void from_json(const nlohmann::json& j, FilamentGroupContext::SpeedInfo&
|
||||
}
|
||||
|
||||
// ============ FilamentGroupContext::NozzleInfo ============
|
||||
inline void to_json(nlohmann::json& j, const FilamentGroupContext::NozzleInfo& ni) {
|
||||
nlohmann::json enl = nlohmann::json::object();
|
||||
inline void to_json(json& j, const FilamentGroupContext::NozzleInfo& ni) {
|
||||
json enl = json::object();
|
||||
for (auto& [ext, nozzles] : ni.extruder_nozzle_list)
|
||||
enl[std::to_string(ext)] = nozzles;
|
||||
j["extruder_nozzle_list"] = enl;
|
||||
|
||||
j["nozzle_list"] = nlohmann::json::array();
|
||||
j["nozzle_list"] = json::array();
|
||||
for (auto& n : ni.nozzle_list)
|
||||
j["nozzle_list"].push_back(n);
|
||||
|
||||
nlohmann::json ns = nlohmann::json::object();
|
||||
json ns = json::object();
|
||||
for (auto& [noz, fil] : ni.nozzle_status)
|
||||
ns[std::to_string(noz)] = fil;
|
||||
j["nozzle_status"] = ns;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentGroupContext::NozzleInfo& ni) {
|
||||
inline void from_json(const json& j, FilamentGroupContext::NozzleInfo& ni) {
|
||||
ni.extruder_nozzle_list.clear();
|
||||
for (auto& [k, v] : j.at("extruder_nozzle_list").items())
|
||||
ni.extruder_nozzle_list[std::stoi(k)] = v.get<std::vector<int>>();
|
||||
@@ -300,8 +302,8 @@ inline void from_json(const nlohmann::json& j, FilamentGroupContext::NozzleInfo&
|
||||
}
|
||||
|
||||
// ============ Full FilamentGroupContext ============
|
||||
inline void to_json(nlohmann::json& j, const FilamentGroupContext& ctx) {
|
||||
nlohmann::json mi, gi, mai, si, ni;
|
||||
inline void to_json(json& j, const FilamentGroupContext& ctx) {
|
||||
json mi, gi, mai, si, ni;
|
||||
to_json(mi, ctx.model_info);
|
||||
to_json(gi, ctx.group_info);
|
||||
to_json(mai, ctx.machine_info);
|
||||
@@ -314,7 +316,7 @@ inline void to_json(nlohmann::json& j, const FilamentGroupContext& ctx) {
|
||||
j["nozzle_info"] = ni;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FilamentGroupContext& ctx) {
|
||||
inline void from_json(const json& j, FilamentGroupContext& ctx) {
|
||||
from_json(j.at("model_info"), ctx.model_info);
|
||||
from_json(j.at("group_info"), ctx.group_info);
|
||||
from_json(j.at("machine_info"), ctx.machine_info);
|
||||
@@ -334,11 +336,11 @@ struct TestMetadata {
|
||||
int seed = 0;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const TestMetadata& m) {
|
||||
j = nlohmann::json{{"id", m.id}, {"config_type", m.config_type}, {"seed", m.seed}};
|
||||
inline void to_json(json& j, const TestMetadata& m) {
|
||||
j = json{{"id", m.id}, {"config_type", m.config_type}, {"seed", m.seed}};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, TestMetadata& m) {
|
||||
inline void from_json(const json& j, TestMetadata& m) {
|
||||
j.at("id").get_to(m.id);
|
||||
j.at("config_type").get_to(m.config_type);
|
||||
j.at("seed").get_to(m.seed);
|
||||
@@ -352,8 +354,8 @@ struct TestResult {
|
||||
std::vector<std::string> violations;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const TestResult& r) {
|
||||
j = nlohmann::json{
|
||||
inline void to_json(json& j, const TestResult& r) {
|
||||
j = json{
|
||||
{"filament_map", r.filament_map},
|
||||
{"flush_cost", r.flush_cost},
|
||||
{"elapsed_ms", r.elapsed_ms},
|
||||
@@ -362,7 +364,7 @@ inline void to_json(nlohmann::json& j, const TestResult& r) {
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, TestResult& r) {
|
||||
inline void from_json(const json& j, TestResult& r) {
|
||||
j.at("filament_map").get_to(r.filament_map);
|
||||
j.at("flush_cost").get_to(r.flush_cost);
|
||||
j.at("elapsed_ms").get_to(r.elapsed_ms);
|
||||
@@ -378,15 +380,15 @@ struct BaseResult {
|
||||
bool constraints_ok = true;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const BaseResult& g) {
|
||||
j = nlohmann::json{
|
||||
inline void to_json(json& j, const BaseResult& g) {
|
||||
j = json{
|
||||
{"full_score", g.full_score},
|
||||
{"flush_cost", g.flush_cost},
|
||||
{"constraints_ok", g.constraints_ok}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, BaseResult& g) {
|
||||
inline void from_json(const json& j, BaseResult& g) {
|
||||
j.at("full_score").get_to(g.full_score);
|
||||
j.at("flush_cost").get_to(g.flush_cost);
|
||||
j.at("constraints_ok").get_to(g.constraints_ok);
|
||||
@@ -401,7 +403,7 @@ struct TestCase {
|
||||
|
||||
inline TestCase load_test_case(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
nlohmann::json j = nlohmann::json::parse(f);
|
||||
json j = json::parse(f);
|
||||
TestCase tc;
|
||||
tc.metadata = j.at("metadata").get<TestMetadata>();
|
||||
Slic3r::from_json(j.at("context"), tc.context);
|
||||
@@ -411,9 +413,9 @@ inline TestCase load_test_case(const std::string& path) {
|
||||
}
|
||||
|
||||
inline void save_test_case(const std::string& path, const TestCase& tc) {
|
||||
nlohmann::json j;
|
||||
json j;
|
||||
j["metadata"] = tc.metadata;
|
||||
nlohmann::json ctx_j;
|
||||
json ctx_j;
|
||||
Slic3r::to_json(ctx_j, tc.context);
|
||||
j["context"] = ctx_j;
|
||||
if (tc.base_result)
|
||||
@@ -430,14 +432,14 @@ inline void save_result(const std::string& case_path, const TestResult& result)
|
||||
else
|
||||
result_path += ".result.json";
|
||||
|
||||
nlohmann::json j = result;
|
||||
json j = result;
|
||||
std::ofstream f(result_path);
|
||||
f << j.dump(2);
|
||||
}
|
||||
|
||||
inline TestResult load_result(const std::string& result_path) {
|
||||
std::ifstream f(result_path);
|
||||
nlohmann::json j = nlohmann::json::parse(f);
|
||||
json j = json::parse(f);
|
||||
return j.get<TestResult>();
|
||||
}
|
||||
|
||||
|
||||
@@ -538,110 +538,6 @@ SCENARIO("update_diff_values_to_child_config keeps a child's values on variants
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("update_non_diff_values_to_base_config keeps a project's changed values on variants it does not list",
|
||||
"[Config][Variant]") {
|
||||
std::set<std::string> no_keys;
|
||||
auto variants = [](std::initializer_list<std::string> names) { return new Slic3r::ConfigOptionStrings(names); };
|
||||
|
||||
GIVEN("A filament base with three variants") {
|
||||
Slic3r::DynamicPrintConfig base;
|
||||
base.set_key_value("filament_extruder_variant",
|
||||
variants({"Direct Drive Standard", "Bowden Standard", "Direct Drive High Flow"}));
|
||||
base.set_deserialize_strict("nozzle_temperature", "220,220,220");
|
||||
|
||||
WHEN("the project was saved when the base had only its first variant") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("filament_extruder_variant", variants({"Direct Drive Standard"}));
|
||||
project.set_deserialize_strict("nozzle_temperature", "199");
|
||||
|
||||
AND_WHEN("the project lists the value as changed") {
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"nozzle_temperature"}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("the project's value applies to every variant") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "199,199,199");
|
||||
}
|
||||
}
|
||||
AND_WHEN("the project does not list the value as changed") {
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("the base's values replace it") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "220,220,220");
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("the project lists every variant, in another order") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("filament_extruder_variant",
|
||||
variants({"Bowden Standard", "Direct Drive High Flow", "Direct Drive Standard"}));
|
||||
project.set_deserialize_strict("nozzle_temperature", "190,205,199");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"nozzle_temperature"}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("each variant keeps its own value") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "199,190,205");
|
||||
}
|
||||
}
|
||||
WHEN("the project lists no variants") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_deserialize_strict("nozzle_temperature", "199");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"nozzle_temperature"}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("the project's value applies to every variant") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "199,199,199");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A two-extruder printer base with two variants per extruder") {
|
||||
Slic3r::DynamicPrintConfig base;
|
||||
base.set_key_value("printer_extruder_variant",
|
||||
variants({"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive Standard", "Direct Drive High Flow"}));
|
||||
base.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 1, 2, 2}));
|
||||
base.set_deserialize_strict("retraction_length", "0.8,0.8,0.8,0.8");
|
||||
|
||||
WHEN("the project lists only the Standard variant of each extruder") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("printer_extruder_variant", variants({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||
project.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
|
||||
project.set_deserialize_strict("retraction_length", "1.1,2.2");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"retraction_length"}, "printer_extruder_id", "printer_extruder_variant",
|
||||
Slic3r::printer_options_with_variant_1,
|
||||
Slic3r::printer_options_with_variant_2);
|
||||
THEN("each extruder's High Flow variant takes that extruder's value") {
|
||||
REQUIRE(project.opt_serialize("retraction_length") == "1.1,1.1,2.2,2.2");
|
||||
}
|
||||
}
|
||||
WHEN("the project lists only the Standard variant of each extruder, and the variant lists as changed") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("printer_extruder_variant", variants({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||
project.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
|
||||
project.set_deserialize_strict("machine_max_speed_x", "300,100,400,150");
|
||||
base.set_deserialize_strict("machine_max_speed_x", "500,200,500,200,500,200,500,200");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(),
|
||||
{"machine_max_speed_x", "printer_extruder_id", "printer_extruder_variant"},
|
||||
"printer_extruder_id", "printer_extruder_variant",
|
||||
Slic3r::printer_options_with_variant_1,
|
||||
Slic3r::printer_options_with_variant_2);
|
||||
THEN("the variant lists are the base's") {
|
||||
REQUIRE(project.opt_serialize("printer_extruder_variant") == base.opt_serialize("printer_extruder_variant"));
|
||||
REQUIRE(project.opt_serialize("printer_extruder_id") == "1,1,2,2");
|
||||
}
|
||||
THEN("each extruder's High Flow variant takes that extruder's pair of limits") {
|
||||
REQUIRE(project.opt_serialize("machine_max_speed_x") == "300,100,300,100,400,150,400,150");
|
||||
}
|
||||
}
|
||||
WHEN("the project lists no variants") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_deserialize_strict("retraction_length", "1.1");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"retraction_length"}, "printer_extruder_id", "printer_extruder_variant",
|
||||
Slic3r::printer_options_with_variant_1,
|
||||
Slic3r::printer_options_with_variant_2);
|
||||
THEN("only the first extruder's variants take the project's value") {
|
||||
REQUIRE(project.opt_serialize("retraction_length") == "1.1,1.1,0.8,0.8");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SCENARIO("DynamicPrintConfig JSON serialization", "[Config]") {
|
||||
// WHEN("DynamicPrintConfig is serialized and deserialized") {
|
||||
// auto now = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@@ -799,30 +799,3 @@ TEST_CASE("A per-variant filament option read with a single value gives it to ev
|
||||
config.load_from_ini_string("pressure_advance = 0.021", ForwardCompatibilitySubstitutionRule::Disable);
|
||||
REQUIRE(config.option<ConfigOptionFloats>("pressure_advance")->values == std::vector<double>({0.021, 0.021, 0.021}));
|
||||
}
|
||||
|
||||
// The device drying options hold several values per filament, as many as each filament preset gives.
|
||||
TEST_CASE("The device drying options are rebuilt as each filament's values in slot order", "[Config]")
|
||||
{
|
||||
DynamicPrintConfig two_values, one_value, no_value;
|
||||
two_values.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations", true)->values = {"1", "0"};
|
||||
two_values.option<ConfigOptionFloats>("filament_dev_ams_drying_temperature", true)->values = {45., 45., 55., 55.};
|
||||
one_value.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations", true)->values = {"1"};
|
||||
one_value.option<ConfigOptionFloats>("filament_dev_ams_drying_temperature", true)->values = {65., 65., 75., 75.};
|
||||
|
||||
// values a project stored for three other filaments
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations", true)->values = {"0", "0", "0"};
|
||||
config.option<ConfigOptionFloats>("filament_dev_chamber_drying_time", true)->values = {12., 8., 12.};
|
||||
|
||||
set_filament_dev_options(config, {&two_values, &one_value, &two_values});
|
||||
REQUIRE(config.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations")->values ==
|
||||
std::vector<std::string>({"1", "0", "1", "1", "0"}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("filament_dev_ams_drying_temperature")->values ==
|
||||
std::vector<double>({45., 45., 55., 55., 65., 65., 75., 75., 45., 45., 55., 55.}));
|
||||
// an option no filament defines keeps the stored values
|
||||
REQUIRE(config.option<ConfigOptionFloats>("filament_dev_chamber_drying_time")->values == std::vector<double>({12., 8., 12.}));
|
||||
|
||||
// a filament without the option takes the option's default
|
||||
set_filament_dev_options(config, {&two_values, &no_value});
|
||||
REQUIRE(config.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations")->values == std::vector<std::string>({"1", "0", ""}));
|
||||
}
|
||||
|
||||
@@ -6228,9 +6228,8 @@ TEST_CASE("A per-variant project value maps onto its base preset's variant layou
|
||||
base_finder(&base, calls));
|
||||
CHECK(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>{"Direct Drive Standard", "Direct Drive High Flow"});
|
||||
// The listed key keeps the project's Standard value, and High Flow, which the project does not
|
||||
// list, takes it too, as a user preset's value does.
|
||||
check_double_vector(config.option<ConfigOptionFloats>("outer_wall_speed")->values, {100., 100.});
|
||||
// The listed key keeps the project's Standard value and takes High Flow from the base.
|
||||
check_double_vector(config.option<ConfigOptionFloats>("outer_wall_speed")->values, {100., 300.});
|
||||
check_double_vector(config.option<ConfigOptionFloats>("inner_wall_speed")->values, {250., 350.});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
const char *const BELOW_PAD_TEST_OBJECTS[] = {
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
void test_support_model_collision(const std::string &obj_filename,
|
||||
const sla::SupportTreeConfig &input_supportcfg,
|
||||
const sla::HollowingConfig &hollowingcfg,
|
||||
|
||||
@@ -33,31 +33,33 @@ namespace Slic3r::sla { struct PadConfig; }
|
||||
namespace Slic3r::sla { struct SupportTreeConfig; }
|
||||
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
enum e_validity {
|
||||
ASSUME_NO_EMPTY = 1,
|
||||
ASSUME_MANIFOLD = 2,
|
||||
ASSUME_NO_REPAIR = 4
|
||||
};
|
||||
|
||||
void check_validity(const Slic3r::TriangleMesh &input_mesh,
|
||||
void check_validity(const TriangleMesh &input_mesh,
|
||||
int flags = ASSUME_NO_EMPTY | ASSUME_MANIFOLD |
|
||||
ASSUME_NO_REPAIR);
|
||||
|
||||
struct PadByproducts
|
||||
{
|
||||
Slic3r::ExPolygons model_contours;
|
||||
Slic3r::ExPolygons support_contours;
|
||||
Slic3r::TriangleMesh mesh;
|
||||
ExPolygons model_contours;
|
||||
ExPolygons support_contours;
|
||||
TriangleMesh mesh;
|
||||
};
|
||||
|
||||
void test_concave_hull(const Slic3r::ExPolygons &polys);
|
||||
void test_concave_hull(const ExPolygons &polys);
|
||||
|
||||
void test_pad(const std::string & obj_filename,
|
||||
const Slic3r::sla::PadConfig &padcfg,
|
||||
const sla::PadConfig &padcfg,
|
||||
PadByproducts & out);
|
||||
|
||||
inline void test_pad(const std::string & obj_filename,
|
||||
const Slic3r::sla::PadConfig &padcfg = {})
|
||||
const sla::PadConfig &padcfg = {})
|
||||
{
|
||||
PadByproducts byproducts;
|
||||
test_pad(obj_filename, padcfg, byproducts);
|
||||
@@ -67,53 +69,53 @@ struct SupportByproducts
|
||||
{
|
||||
std::string obj_fname;
|
||||
std::vector<float> slicegrid;
|
||||
std::vector<Slic3r::ExPolygons> model_slices;
|
||||
Slic3r::sla::SupportTreeBuilder supporttree;
|
||||
Slic3r::TriangleMesh input_mesh;
|
||||
std::vector<ExPolygons> model_slices;
|
||||
sla::SupportTreeBuilder supporttree;
|
||||
TriangleMesh input_mesh;
|
||||
};
|
||||
|
||||
const constexpr float CLOSING_RADIUS = 0.005f;
|
||||
|
||||
void check_support_tree_integrity(const Slic3r::sla::SupportTreeBuilder &stree,
|
||||
const Slic3r::sla::SupportTreeConfig &cfg);
|
||||
void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
|
||||
const sla::SupportTreeConfig &cfg);
|
||||
|
||||
void test_supports(const std::string &obj_filename,
|
||||
const Slic3r::sla::SupportTreeConfig &supportcfg,
|
||||
const Slic3r::sla::HollowingConfig &hollowingcfg,
|
||||
const Slic3r::sla::DrainHoles &drainholes,
|
||||
const sla::SupportTreeConfig &supportcfg,
|
||||
const sla::HollowingConfig &hollowingcfg,
|
||||
const sla::DrainHoles &drainholes,
|
||||
SupportByproducts &out);
|
||||
|
||||
inline void test_supports(const std::string &obj_filename,
|
||||
const Slic3r::sla::SupportTreeConfig &supportcfg,
|
||||
const sla::SupportTreeConfig &supportcfg,
|
||||
SupportByproducts &out)
|
||||
{
|
||||
Slic3r::sla::HollowingConfig hcfg;
|
||||
sla::HollowingConfig hcfg;
|
||||
hcfg.enabled = false;
|
||||
test_supports(obj_filename, supportcfg, hcfg, {}, out);
|
||||
}
|
||||
|
||||
inline void test_supports(const std::string &obj_filename,
|
||||
const Slic3r::sla::SupportTreeConfig &supportcfg = {})
|
||||
const sla::SupportTreeConfig &supportcfg = {})
|
||||
{
|
||||
SupportByproducts byproducts;
|
||||
test_supports(obj_filename, supportcfg, byproducts);
|
||||
}
|
||||
|
||||
void export_failed_case(const std::vector<Slic3r::ExPolygons> &support_slices,
|
||||
void export_failed_case(const std::vector<ExPolygons> &support_slices,
|
||||
const SupportByproducts &byproducts);
|
||||
|
||||
|
||||
void test_support_model_collision(
|
||||
const std::string &obj_filename,
|
||||
const Slic3r::sla::SupportTreeConfig &input_supportcfg,
|
||||
const Slic3r::sla::HollowingConfig &hollowingcfg,
|
||||
const Slic3r::sla::DrainHoles &drainholes);
|
||||
const sla::SupportTreeConfig &input_supportcfg,
|
||||
const sla::HollowingConfig &hollowingcfg,
|
||||
const sla::DrainHoles &drainholes);
|
||||
|
||||
inline void test_support_model_collision(
|
||||
const std::string &obj_filename,
|
||||
const Slic3r::sla::SupportTreeConfig &input_supportcfg = {})
|
||||
const sla::SupportTreeConfig &input_supportcfg = {})
|
||||
{
|
||||
Slic3r::sla::HollowingConfig hcfg;
|
||||
sla::HollowingConfig hcfg;
|
||||
hcfg.enabled = false;
|
||||
test_support_model_collision(obj_filename, input_supportcfg, hcfg, {});
|
||||
}
|
||||
@@ -154,8 +156,8 @@ template <class I, class II> void test_pairhash()
|
||||
|
||||
REQUIRE(a != b);
|
||||
|
||||
II hash_ab = Slic3r::sla::pairhash<I, II>(a, b);
|
||||
II hash_ba = Slic3r::sla::pairhash<I, II>(b, a);
|
||||
II hash_ab = sla::pairhash<I, II>(a, b);
|
||||
II hash_ba = sla::pairhash<I, II>(b, a);
|
||||
REQUIRE(hash_ab == hash_ba);
|
||||
|
||||
auto it = ints.find(hash_ab);
|
||||
@@ -178,23 +180,23 @@ static constexpr const TPixel FullBlack = 0;
|
||||
|
||||
template <class A, int N> constexpr int arraysize(const A (&)[N]) { return N; }
|
||||
|
||||
void check_raster_transformations(Slic3r::sla::RasterBase::Orientation o,
|
||||
Slic3r::sla::RasterBase::TMirroring mirroring);
|
||||
void check_raster_transformations(sla::RasterBase::Orientation o,
|
||||
sla::RasterBase::TMirroring mirroring);
|
||||
|
||||
Slic3r::ExPolygon square_with_hole(double v);
|
||||
ExPolygon square_with_hole(double v);
|
||||
|
||||
inline double pixel_area(TPixel px, const Slic3r::sla::PixelDim &pxdim)
|
||||
inline double pixel_area(TPixel px, const sla::PixelDim &pxdim)
|
||||
{
|
||||
return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack);
|
||||
}
|
||||
|
||||
double raster_white_area(const Slic3r::sla::RasterGrayscaleAA &raster);
|
||||
long raster_pxsum(const Slic3r::sla::RasterGrayscaleAA &raster);
|
||||
double raster_white_area(const sla::RasterGrayscaleAA &raster);
|
||||
long raster_pxsum(const sla::RasterGrayscaleAA &raster);
|
||||
|
||||
double predict_error(const Slic3r::ExPolygon &p, const Slic3r::sla::PixelDim &pd);
|
||||
double predict_error(const ExPolygon &p, const sla::PixelDim &pd);
|
||||
|
||||
Slic3r::sla::SupportPoints calc_support_pts(
|
||||
const Slic3r::TriangleMesh & mesh,
|
||||
const Slic3r::sla::SupportPointGenerator::Config &cfg = {});
|
||||
sla::SupportPoints calc_support_pts(
|
||||
const TriangleMesh & mesh,
|
||||
const sla::SupportPointGenerator::Config &cfg = {});
|
||||
|
||||
#endif // SLA_TEST_UTILS_HPP
|
||||
|
||||
@@ -78,34 +78,31 @@ TEST_CASE("Series and managed classification", "[NetworkVersions]")
|
||||
|
||||
TEST_CASE_METHOD(PluginFolderFixture, "Managed builds fold into the series; customs are surfaced", "[NetworkVersions]")
|
||||
{
|
||||
add_plugin("02.08.04.60"); // managed, same series -> folded into the 02.08.04 row
|
||||
add_plugin("02.08.01.55"); // managed, same series -> folded into the 02.08.01 row
|
||||
add_plugin("02.09.00.10"); // managed, unknown series -> not listed
|
||||
add_plugin("02.08.01.55"); // managed, series this build no longer has an ABI for -> not listed
|
||||
add_plugin("02.03.00.62"); // managed, older whitelisted series -> folded into 02.03.00
|
||||
add_plugin("02.01.01.52"); // managed, series with no ABI in this build -> not listed
|
||||
add_plugin("02.08.04_custom"); // custom, whitelisted series -> listed under it
|
||||
add_plugin("02.08.04.52-dev"); // custom (dash-suffixed), whitelisted series -> listed
|
||||
add_plugin("02.08.01_custom"); // custom, whitelisted series -> listed under it
|
||||
add_plugin("02.08.01.52-dev"); // custom (dash-suffixed), whitelisted series -> listed
|
||||
|
||||
auto versions = get_all_available_versions();
|
||||
|
||||
// The specific managed build never gets its own row - the series represents it.
|
||||
REQUIRE(count_version(versions, "02.08.04.60") == 0);
|
||||
REQUIRE(count_version(versions, "02.08.04") == 1);
|
||||
REQUIRE(count_version(versions, "02.09.00.10") == 0);
|
||||
REQUIRE(count_version(versions, "02.08.01.55") == 0);
|
||||
REQUIRE(count_version(versions, "02.08.01") == 0);
|
||||
REQUIRE(count_version(versions, "02.08.01") == 1);
|
||||
REQUIRE(count_version(versions, "02.09.00.10") == 0);
|
||||
REQUIRE(count_version(versions, "02.03.00.62") == 0);
|
||||
REQUIRE(count_version(versions, "02.03.00") == 1);
|
||||
REQUIRE(count_version(versions, "02.01.01.52") == 0);
|
||||
// Custom-named builds are distinct files kept under their own name.
|
||||
REQUIRE(count_version(versions, "02.08.04_custom") == 1);
|
||||
REQUIRE(count_version(versions, "02.08.04.52-dev") == 1);
|
||||
REQUIRE(count_version(versions, "02.08.01_custom") == 1);
|
||||
REQUIRE(count_version(versions, "02.08.01.52-dev") == 1);
|
||||
|
||||
// Newest series first, its customs nested under it (suffix sort: "" < ".52-dev" < "_custom"),
|
||||
// then older series, legacy last.
|
||||
REQUIRE(versions[0].version == "02.08.04");
|
||||
REQUIRE(versions[1].version == "02.08.04.52-dev");
|
||||
REQUIRE(versions[2].version == "02.08.04_custom");
|
||||
REQUIRE(versions[0].version == "02.08.01");
|
||||
REQUIRE(versions[1].version == "02.08.01.52-dev");
|
||||
REQUIRE(versions[2].version == "02.08.01_custom");
|
||||
REQUIRE(versions[3].version == "02.03.00");
|
||||
REQUIRE(versions.back().version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
|
||||
|
||||
@@ -114,9 +111,9 @@ TEST_CASE_METHOD(PluginFolderFixture, "Managed builds fold into the series; cust
|
||||
REQUIRE_FALSE(versions[3].is_latest);
|
||||
|
||||
// Customs sort/render nested under their series (non-empty suffix, base = the series).
|
||||
REQUIRE(versions[1].base_version == "02.08.04");
|
||||
REQUIRE(versions[1].base_version == "02.08.01");
|
||||
REQUIRE_FALSE(versions[1].suffix.empty());
|
||||
REQUIRE(versions[2].base_version == "02.08.04");
|
||||
REQUIRE(versions[2].base_version == "02.08.01");
|
||||
REQUIRE_FALSE(versions[2].suffix.empty());
|
||||
|
||||
// "(Latest)" is the series row, never a nested custom build.
|
||||
@@ -126,20 +123,20 @@ TEST_CASE_METHOD(PluginFolderFixture, "Managed builds fold into the series; cust
|
||||
REQUIRE_FALSE(versions[2].is_latest);
|
||||
|
||||
// The stored default that drives download and update-check decisions is now the series.
|
||||
REQUIRE(std::string(get_latest_network_version()) == "02.08.04");
|
||||
REQUIRE(std::string(get_latest_network_version()) == "02.08.01");
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded series is marked installed", "[NetworkVersions]")
|
||||
{
|
||||
add_plugin("02.08.04.60");
|
||||
add_plugin("02.08.04_custom");
|
||||
add_plugin("02.08.01.55");
|
||||
add_plugin("02.08.01_custom");
|
||||
|
||||
// The loaded plug-in reports its full build (02.08.04.60); the series row is what gets marked.
|
||||
// The loaded plug-in reports its full build (02.08.01.55); the series row is what gets marked.
|
||||
{
|
||||
auto versions = get_all_available_versions("02.08.04.60");
|
||||
auto versions = get_all_available_versions("02.08.01.55");
|
||||
int marked = 0;
|
||||
for (const auto& info : versions)
|
||||
if (info.is_loaded) { ++marked; REQUIRE(info.version == "02.08.04"); }
|
||||
if (info.is_loaded) { ++marked; REQUIRE(info.version == "02.08.01"); }
|
||||
REQUIRE(marked == 1);
|
||||
}
|
||||
|
||||
@@ -155,10 +152,10 @@ TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded series is marked installe
|
||||
|
||||
// A loaded custom build matches its own row, never the bare series.
|
||||
{
|
||||
auto versions = get_all_available_versions("02.08.04_custom");
|
||||
auto versions = get_all_available_versions("02.08.01_custom");
|
||||
int marked = 0;
|
||||
for (const auto& info : versions)
|
||||
if (info.is_loaded) { ++marked; REQUIRE(info.version == "02.08.04_custom"); }
|
||||
if (info.is_loaded) { ++marked; REQUIRE(info.version == "02.08.01_custom"); }
|
||||
REQUIRE(marked == 1);
|
||||
}
|
||||
|
||||
@@ -170,11 +167,11 @@ TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded series is marked installe
|
||||
TEST_CASE("Only whitelisted series pass the load gate", "[NetworkVersions]")
|
||||
{
|
||||
// Each whitelisted series, its builds, and custom-named builds of that series.
|
||||
REQUIRE(is_supported_network_version("02.08.04"));
|
||||
REQUIRE(is_supported_network_version("02.08.04.52"));
|
||||
REQUIRE(is_supported_network_version("02.08.04.60"));
|
||||
REQUIRE(is_supported_network_version("02.08.04_custom"));
|
||||
REQUIRE(is_supported_network_version("02.08.04.52-dev"));
|
||||
REQUIRE(is_supported_network_version("02.08.01"));
|
||||
REQUIRE(is_supported_network_version("02.08.01.52"));
|
||||
REQUIRE(is_supported_network_version("02.08.01.55"));
|
||||
REQUIRE(is_supported_network_version("02.08.01_custom"));
|
||||
REQUIRE(is_supported_network_version("02.08.01.52-dev"));
|
||||
REQUIRE(is_supported_network_version("02.03.00"));
|
||||
REQUIRE(is_supported_network_version("02.03.00.62"));
|
||||
REQUIRE(is_supported_network_version("02.03.00.70"));
|
||||
@@ -182,9 +179,6 @@ TEST_CASE("Only whitelisted series pass the load gate", "[NetworkVersions]")
|
||||
REQUIRE(is_supported_network_version(BAMBU_NETWORK_AGENT_VERSION_LEGACY));
|
||||
|
||||
// Series whitelisted by previous Orca releases that no generation here can call.
|
||||
REQUIRE_FALSE(is_supported_network_version("02.08.01"));
|
||||
REQUIRE_FALSE(is_supported_network_version("02.08.01.55"));
|
||||
REQUIRE_FALSE(is_supported_network_version("02.08.01_custom"));
|
||||
REQUIRE_FALSE(is_supported_network_version("02.01.01.52"));
|
||||
REQUIRE_FALSE(is_supported_network_version("02.00.02.50"));
|
||||
|
||||
@@ -204,9 +198,9 @@ TEST_CASE("Each version resolves to the ABI generation that can call it", "[Netw
|
||||
{
|
||||
// The generation is keyed on the series, so every build of a series - including the
|
||||
// custom-named ones - resolves to the same one.
|
||||
CHECK(network_plugin_abi("02.08.04") == NetworkAbi::Current);
|
||||
CHECK(network_plugin_abi("02.08.04.60") == NetworkAbi::Current);
|
||||
CHECK(network_plugin_abi("02.08.04.52-dev") == NetworkAbi::Current);
|
||||
CHECK(network_plugin_abi("02.08.01") == NetworkAbi::Current);
|
||||
CHECK(network_plugin_abi("02.08.01.55") == NetworkAbi::Current);
|
||||
CHECK(network_plugin_abi("02.08.01.52-dev") == NetworkAbi::Current);
|
||||
CHECK(network_plugin_abi("02.03.00") == NetworkAbi::V0203);
|
||||
CHECK(network_plugin_abi("02.03.00.62") == NetworkAbi::V0203);
|
||||
CHECK(network_plugin_abi("02.03.00_custom") == NetworkAbi::V0203);
|
||||
@@ -214,7 +208,6 @@ TEST_CASE("Each version resolves to the ABI generation that can call it", "[Netw
|
||||
|
||||
// Anything the load gate rejects must dispatch through nothing at all, rather than
|
||||
// defaulting to a layout it does not share.
|
||||
CHECK(network_plugin_abi("02.08.01.55") == NetworkAbi::Unsupported);
|
||||
CHECK(network_plugin_abi("02.01.01.52") == NetworkAbi::Unsupported);
|
||||
CHECK(network_plugin_abi("02.00.02.50") == NetworkAbi::Unsupported);
|
||||
CHECK(network_plugin_abi("02.09.00.10") == NetworkAbi::Unsupported);
|
||||
@@ -241,7 +234,7 @@ TEST_CASE_METHOD(PluginFolderFixture, "Legacy series never adopts discovered bui
|
||||
// With nothing else on disk, the series holds "(Latest)" even though its library is
|
||||
// not installed.
|
||||
for (const auto& info : versions) {
|
||||
if (info.version == "02.08.04") {
|
||||
if (info.version == "02.08.01") {
|
||||
REQUIRE(info.is_latest);
|
||||
REQUIRE_FALSE(info.is_loaded);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user