- Adds optional whole-object Local-Z support for mixed wall regions instead of limiting Local-Z strictly to painted mixed zones.

- Adds an experimental direct multicolor Local-Z solver with carry-over error for 3+ color mixed rows, reducing pair-cadence banding.
This commit is contained in:
Rad
2026-04-20 04:59:50 +02:00
parent 66a14b1724
commit 01c609a083
17 changed files with 646 additions and 72 deletions
+51
View File
@@ -0,0 +1,51 @@
# Snapmaker Orca FullSpectrum v0.9.8
Local-Z multicolor improvements, duplicate gap-fill cleanup, and mixed-filament workflow fixes.
Compared to the previous FullSpectrum v0.9.7 release.
Based on Snapmaker Orca v2.3.1, with the bundled print-preprocessing Flutter/web resources kept on the newer 2.3.12 line.
## Quick Overview
- Adds optional whole-object Local-Z support for mixed wall regions instead of limiting Local-Z strictly to painted mixed zones.
- Adds an experimental direct multicolor Local-Z solver with carry-over error for 3+ color mixed rows, reducing pair-cadence banding.
- Fixes several Local-Z boundary issues so painted colors, fixed-color islands, and whole-object mixed masks cooperate more cleanly.
- Fixes a duplicated inner-loop gap-fill regression that could overprint narrow collapsed regions.
- Fixes mixed-filament bias-state and print-preprocessing remap issues that required restarting or blocked otherwise valid remaps.
## More Detailed Overview
### Local-Z Dithering
- Restores and relabels the Local-Z lower and upper height bounds in the Others tab so they are clearly presented as Local-Z controls.
- Adds `Apply Local-Z to whole mixed objects` as an experimental opt-in, allowing default mixed wall regions to keep Local-Z active outside painted islands.
- Adds `Use direct multicolor Local-Z solver` as an experimental opt-in for 3+ color rows, using direct component allocation with carry-over error across layers instead of collapsing everything into pair cadence first.
- Keeps the older pair-based Local-Z path as the default for compatibility, while letting the new direct solver be tested independently.
- Fixes several planner/runtime issues around painted zones inside whole-object Local-Z bodies, including lost painted Local-Z treatment, phase resets at zone boundaries, and overlap/regrowth artifacts at fixed-color painted caps.
- Improves Local-Z summary text in the mixed-filament panel so the UI reports when the direct multicolor solver is active instead of describing the older pair-cadence model.
### Gap Fill And Toolpath Generation
- Removes the collapsed infill gap-fill fallback that was appending duplicate gap-fill passes in narrow looped regions.
- Resolves the regression where certain capsule-like inner loops could receive a doubled gap-fill contour and get visibly over-squished.
- Keeps the safer older path that avoided the duplicate loop in the reproduced meshes.
### Mixed-Filament Workflow Fixes
- Fixes mixed-filament bias controls getting stuck disabled after loading certain `.3mf` projects and then creating a new project without restarting.
- Keeps the mixed-filament project config mirrored back into the sidebar state when presets or projects are reloaded programmatically.
- Fixes print-preprocessing filament remap for the newer web dialog by sending file-side nozzle diameter data expected by the 2.3.12 frontend.
- Reduces the mismatch between automatic tray selection and manual remap eligibility in the preprocessing flow.
### UI Cleanup
- Retires the old height-weighted cadence and pointillisme-facing controls from the Others tab while keeping compatibility for older stored configs and projects.
- Keeps the Local-Z-related controls that are still in active use visible and synchronized with the current mixed-filament workflow.
## Notes
- `cmake --build build --target Snapmaker_Orca --config Release --parallel 4` succeeded.
- Much of this release was validated through repeated slice-preview and hardware-oriented regression checks for mixed-filament and Local-Z behavior.
- The active build tree in this workspace still had no registered `ctest` cases, so broader automated test reruns were not available from `ctest` here.
- macOS builds from this fork remain unsigned and not notarized.
- Release downloads for this fork are available here: https://github.com/ratdoux/OrcaSlicer-FullSpectrum/releases
- Use at your own risk.
+1 -1
View File
@@ -7,7 +7,7 @@
#define SLIC3R_APP_KEY "Snapmaker_Orca" #define SLIC3R_APP_KEY "Snapmaker_Orca"
#define SLIC3R_VERSION "01.10.01.70" #define SLIC3R_VERSION "01.10.01.70"
#define Snapmaker_VERSION "2.3.1" #define Snapmaker_VERSION "2.3.1"
#define FULLSPECTRUM_VERSION "0.9.7" #define FULLSPECTRUM_VERSION "0.9.8"
#define MIN_FIRM_VER "1.0.0" #define MIN_FIRM_VER "1.0.0"
#ifndef GIT_COMMIT_HASH #ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized #define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized
+36 -7
View File
@@ -4865,6 +4865,7 @@ LayerResult GCode::process_layer(const Print& print,
constexpr double LOCAL_Z_BASE_MASK_EXPAND_MM = 0.04; constexpr double LOCAL_Z_BASE_MASK_EXPAND_MM = 0.04;
const float local_z_perimeter_mask_expand = float(scale_(LOCAL_Z_PERIMETER_MASK_EXPAND_MM)); const float local_z_perimeter_mask_expand = float(scale_(LOCAL_Z_PERIMETER_MASK_EXPAND_MM));
const float local_z_base_mask_expand = float(scale_(LOCAL_Z_BASE_MASK_EXPAND_MM)); const float local_z_base_mask_expand = float(scale_(LOCAL_Z_BASE_MASK_EXPAND_MM));
const bool local_z_whole_objects_enabled = print.full_print_config().opt_bool("dithering_local_z_whole_objects");
struct LocalZPassBucket { struct LocalZPassBucket {
const SubLayerPlan* plan { nullptr }; const SubLayerPlan* plan { nullptr };
@@ -4933,17 +4934,45 @@ LayerResult GCode::process_layer(const Print& print,
LocalZPassBucket bucket; LocalZPassBucket bucket;
bucket.plan = &plan; bucket.plan = &plan;
bucket.compensated_masks_by_extruder.assign(plan.painted_masks_by_extruder.size(), ExPolygons()); const size_t plan_mask_slots = std::max(plan.painted_masks_by_extruder.size(), plan.fixed_painted_masks_by_extruder.size());
bucket.compensated_masks_by_extruder.assign(plan_mask_slots, ExPolygons());
bool pass_has_raw_masks = false; bool pass_has_raw_masks = false;
bool pass_has_compensated_masks = false; bool pass_has_compensated_masks = false;
for (size_t extruder_id = 0; extruder_id < plan.painted_masks_by_extruder.size(); ++extruder_id) { ExPolygons fixed_raw_masks_union;
const ExPolygons& raw_masks = plan.painted_masks_by_extruder[extruder_id]; for (const ExPolygons &fixed_masks : plan.fixed_painted_masks_by_extruder)
if (raw_masks.empty()) if (!fixed_masks.empty())
append(fixed_raw_masks_union, fixed_masks);
if (!fixed_raw_masks_union.empty() && fixed_raw_masks_union.size() > 1)
fixed_raw_masks_union = union_ex(fixed_raw_masks_union);
const ExPolygons fixed_compensated_guard =
fixed_raw_masks_union.empty() ? ExPolygons() : local_z_compensate_masks(fixed_raw_masks_union, local_z_perimeter_mask_expand, true);
for (size_t extruder_id = 0; extruder_id < plan_mask_slots; ++extruder_id) {
const ExPolygons mixed_raw_masks =
extruder_id < plan.painted_masks_by_extruder.size() ? plan.painted_masks_by_extruder[extruder_id] : ExPolygons();
const ExPolygons fixed_raw_masks =
extruder_id < plan.fixed_painted_masks_by_extruder.size() ? plan.fixed_painted_masks_by_extruder[extruder_id] : ExPolygons();
if (mixed_raw_masks.empty() && fixed_raw_masks.empty())
continue; continue;
pass_has_raw_masks = true; pass_has_raw_masks = true;
raw_mask_polygon_count += raw_masks.size(); raw_mask_polygon_count += mixed_raw_masks.size() + fixed_raw_masks.size();
append(raw_mixed_masks_union, raw_masks); if (!mixed_raw_masks.empty())
ExPolygons compensated = local_z_compensate_masks(raw_masks, local_z_perimeter_mask_expand, true); append(raw_mixed_masks_union, mixed_raw_masks);
if (!fixed_raw_masks.empty())
append(raw_mixed_masks_union, fixed_raw_masks);
ExPolygons compensated;
if (!mixed_raw_masks.empty()) {
ExPolygons compensated_mixed = local_z_compensate_masks(mixed_raw_masks, local_z_perimeter_mask_expand, true);
if (local_z_whole_objects_enabled && !fixed_compensated_guard.empty())
compensated_mixed = diff_ex(compensated_mixed, fixed_compensated_guard);
if (!compensated_mixed.empty())
append(compensated, compensated_mixed);
}
if (!fixed_raw_masks.empty())
append(compensated, fixed_raw_masks);
if (!compensated.empty() && compensated.size() > 1)
compensated = union_ex(compensated);
if (!compensated.empty()) { if (!compensated.empty()) {
pass_has_compensated_masks = true; pass_has_compensated_masks = true;
compensated_mask_polygon_count += compensated.size(); compensated_mask_polygon_count += compensated.size();
+1
View File
@@ -120,6 +120,7 @@ struct MixedFilamentPreviewSettings
double preferred_a_height { 0.0 }; double preferred_a_height { 0.0 };
double preferred_b_height { 0.0 }; double preferred_b_height { 0.0 };
bool local_z_mode { false }; bool local_z_mode { false };
bool local_z_direct_multicolor { false };
size_t wall_loops { 1 }; size_t wall_loops { 1 };
}; };
+2
View File
@@ -82,6 +82,8 @@ static std::vector<std::string> s_project_options {
"mixed_color_layer_height_b", "mixed_color_layer_height_b",
"dithering_z_step_size", "dithering_z_step_size",
"dithering_local_z_mode", "dithering_local_z_mode",
"dithering_local_z_whole_objects",
"dithering_local_z_direct_multicolor",
"dithering_step_painted_zones_only", "dithering_step_painted_zones_only",
}; };
+1
View File
@@ -292,6 +292,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "dithering_z_step_size" || opt_key == "dithering_z_step_size"
|| opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_mode"
|| opt_key == "dithering_local_z_whole_objects" || opt_key == "dithering_local_z_whole_objects"
|| opt_key == "dithering_local_z_direct_multicolor"
|| opt_key == "dithering_step_painted_zones_only" || opt_key == "dithering_step_painted_zones_only"
|| opt_key == "mixed_filament_gradient_mode" || opt_key == "mixed_filament_gradient_mode"
|| opt_key == "mixed_filament_height_lower_bound" || opt_key == "mixed_filament_height_lower_bound"
+1
View File
@@ -77,6 +77,7 @@ struct SubLayerPlan
double print_z { 0.0 }; double print_z { 0.0 };
double flow_height { 0.0 }; double flow_height { 0.0 };
std::vector<ExPolygons> painted_masks_by_extruder; std::vector<ExPolygons> painted_masks_by_extruder;
std::vector<ExPolygons> fixed_painted_masks_by_extruder;
ExPolygons base_masks; ExPolygons base_masks;
}; };
+3
View File
@@ -1205,6 +1205,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
new_full_config.option("dithering_z_step_size", true); new_full_config.option("dithering_z_step_size", true);
new_full_config.option("dithering_local_z_mode", true); new_full_config.option("dithering_local_z_mode", true);
new_full_config.option("dithering_local_z_whole_objects", true); new_full_config.option("dithering_local_z_whole_objects", true);
new_full_config.option("dithering_local_z_direct_multicolor", true);
new_full_config.option("dithering_step_painted_zones_only", true); new_full_config.option("dithering_step_painted_zones_only", true);
new_full_config.option("mixed_filament_gradient_mode", true); new_full_config.option("mixed_filament_gradient_mode", true);
new_full_config.option("mixed_filament_height_lower_bound", true); new_full_config.option("mixed_filament_height_lower_bound", true);
@@ -1219,6 +1220,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_config.option("dithering_z_step_size", true); m_config.option("dithering_z_step_size", true);
m_config.option("dithering_local_z_mode", true); m_config.option("dithering_local_z_mode", true);
m_config.option("dithering_local_z_whole_objects", true); m_config.option("dithering_local_z_whole_objects", true);
m_config.option("dithering_local_z_direct_multicolor", true);
m_config.option("dithering_step_painted_zones_only", true); m_config.option("dithering_step_painted_zones_only", true);
m_config.option("mixed_filament_gradient_mode", true); m_config.option("mixed_filament_gradient_mode", true);
m_config.option("mixed_filament_height_lower_bound", true); m_config.option("mixed_filament_height_lower_bound", true);
@@ -1233,6 +1235,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_default_object_config.option("dithering_z_step_size", true); m_default_object_config.option("dithering_z_step_size", true);
m_default_object_config.option("dithering_local_z_mode", true); m_default_object_config.option("dithering_local_z_mode", true);
m_default_object_config.option("dithering_local_z_whole_objects", true); m_default_object_config.option("dithering_local_z_whole_objects", true);
m_default_object_config.option("dithering_local_z_direct_multicolor", true);
m_default_object_config.option("dithering_step_painted_zones_only", true); m_default_object_config.option("dithering_step_painted_zones_only", true);
m_default_object_config.option("mixed_filament_gradient_mode", true); m_default_object_config.option("mixed_filament_gradient_mode", true);
m_default_object_config.option("mixed_filament_height_lower_bound", true); m_default_object_config.option("mixed_filament_height_lower_bound", true);
+8
View File
@@ -4300,6 +4300,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("dithering_local_z_direct_multicolor", coBool);
def->label = L("Use direct multicolor Local-Z solver");
def->category = L("Others");
def->tooltip = L("Experimental. For mixed rows with 3 or more physical filaments, allocate Local-Z sublayers directly across all components with carry-over error between layers instead of collapsing them into pair cadence.\n\n"
"This can reduce visible banding in multicolor Local-Z blends at the cost of more toolchanges. It is ignored when explicit Local-Z A/B heights are set.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("dithering_step_painted_zones_only", coBool); def = this->add("dithering_step_painted_zones_only", coBool);
def->label = L("Use step size in painted zones only"); def->label = L("Use step size in painted zones only");
def->category = L("Others"); def->category = L("Others");
+1
View File
@@ -1369,6 +1369,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloat, dithering_z_step_size)) ((ConfigOptionFloat, dithering_z_step_size))
((ConfigOptionBool, dithering_local_z_mode)) ((ConfigOptionBool, dithering_local_z_mode))
((ConfigOptionBool, dithering_local_z_whole_objects)) ((ConfigOptionBool, dithering_local_z_whole_objects))
((ConfigOptionBool, dithering_local_z_direct_multicolor))
((ConfigOptionBool, dithering_step_painted_zones_only)) ((ConfigOptionBool, dithering_step_painted_zones_only))
((ConfigOptionString, printer_model)) ((ConfigOptionString, printer_model))
((ConfigOptionFloat, resolution)) ((ConfigOptionFloat, resolution))
+1
View File
@@ -950,6 +950,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "dithering_z_step_size" || opt_key == "dithering_z_step_size"
|| opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_mode"
|| opt_key == "dithering_local_z_whole_objects" || opt_key == "dithering_local_z_whole_objects"
|| opt_key == "dithering_local_z_direct_multicolor"
|| opt_key == "dithering_step_painted_zones_only" || opt_key == "dithering_step_painted_zones_only"
|| opt_key == "mixed_filament_component_bias_enabled" || opt_key == "mixed_filament_component_bias_enabled"
|| opt_key == "mixed_filament_region_collapse" || opt_key == "mixed_filament_region_collapse"
+491 -62
View File
@@ -1297,6 +1297,28 @@ static std::vector<double> build_uniform_local_z_pass_heights(double base_height
return out; return out;
} }
static std::vector<double> build_uniform_local_z_pass_heights_exact(double base_height,
double lower_bound,
double upper_bound,
size_t pass_count)
{
if (base_height <= EPSILON || pass_count == 0)
return {};
const double lo = std::max<double>(0.01, lower_bound);
const double hi = std::max<double>(lo, upper_bound);
if (pass_count == 1)
return { base_height };
if (double(pass_count) * lo > base_height + EPSILON || double(pass_count) * hi < base_height - EPSILON)
return {};
std::vector<double> out(pass_count, base_height / double(pass_count));
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
return {};
return out;
}
static inline void compute_local_z_gradient_component_heights(int mix_b_percent, double lower_bound, double upper_bound, static inline void compute_local_z_gradient_component_heights(int mix_b_percent, double lower_bound, double upper_bound,
double &h_a, double &h_b) double &h_a, double &h_b)
{ {
@@ -1891,6 +1913,29 @@ static bool local_z_eligible_mixed_row(const MixedFilament &mf)
mf.distribution_mode != int(MixedFilament::SameLayerPointillisme); mf.distribution_mode != int(MixedFilament::SameLayerPointillisme);
} }
static bool local_z_direct_multicolor_row(const MixedFilament &mf,
size_t num_physical,
std::vector<unsigned int> *component_ids = nullptr,
std::vector<int> *component_weights = nullptr)
{
if (!local_z_eligible_mixed_row(mf))
return false;
const std::vector<unsigned int> ids = decode_gradient_component_ids(mf, num_physical);
if (ids.size() < 3)
return false;
if (component_ids != nullptr)
*component_ids = ids;
if (component_weights != nullptr) {
std::vector<int> weights = decode_gradient_component_weights(mf, ids.size());
if (weights.empty())
weights.assign(ids.size(), 1);
*component_weights = std::move(weights);
}
return true;
}
struct LocalZActivePair struct LocalZActivePair
{ {
unsigned int component_a = 0; unsigned int component_a = 0;
@@ -1998,6 +2043,170 @@ static std::vector<LocalZActivePair> build_local_z_pair_cycle_for_row(const Mixe
return out; return out;
} }
static std::vector<double> build_local_z_direct_multicolor_pass_heights(const MixedFilament &mf,
const std::vector<int> &component_weights,
double base_height,
double lower_bound,
double upper_bound,
size_t component_count)
{
if (base_height <= EPSILON || component_count == 0)
return {};
const double lo = std::max<double>(0.01, lower_bound);
const double hi = std::max<double>(lo, upper_bound);
const size_t min_passes = size_t(std::max<double>(1.0, std::ceil((base_height - EPSILON) / hi)));
const size_t max_passes = size_t(std::max<double>(1.0, std::floor((base_height + EPSILON) / lo)));
if (max_passes == 0)
return { base_height };
std::vector<int> positive_weights;
positive_weights.reserve(component_weights.size());
for (const int weight : component_weights)
if (weight > 0)
positive_weights.emplace_back(weight);
if (positive_weights.empty())
positive_weights.assign(component_count, 1);
const int total_weight = std::max(1, std::accumulate(positive_weights.begin(), positive_weights.end(), 0));
std::vector<double> component_targets;
component_targets.reserve(positive_weights.size());
size_t ideal_passes = 0;
for (const int weight : positive_weights) {
const double target = base_height * double(weight) / double(total_weight);
component_targets.emplace_back(target);
ideal_passes += size_t(std::max<double>(1.0, std::ceil((target - EPSILON) / hi)));
}
size_t pass_limit = max_passes;
if (mf.local_z_max_sublayers >= 2)
pass_limit = std::min(pass_limit, size_t(std::max(2, mf.local_z_max_sublayers)));
pass_limit = std::max(pass_limit, min_passes);
size_t desired_passes = std::clamp(std::max(component_targets.size(), ideal_passes), min_passes, pass_limit);
std::vector<double> bins = component_targets;
while (bins.size() > desired_passes) {
std::sort(bins.begin(), bins.end());
const double merged = bins[0] + bins[1];
bins.erase(bins.begin(), bins.begin() + 2);
bins.emplace_back(merged);
}
while (bins.size() < desired_passes) {
auto it = std::max_element(bins.begin(), bins.end());
if (it == bins.end())
break;
const double value = *it;
if (value < 2.0 * lo - EPSILON)
break;
double first = std::clamp(value * 0.5, lo, value - lo);
double second = value - first;
if (first < lo - EPSILON || second < lo - EPSILON)
break;
*it = first;
bins.emplace_back(second);
}
if (bins.empty())
return build_uniform_local_z_pass_heights(base_height, lo, hi, desired_passes);
std::sort(bins.begin(), bins.end(), std::greater<double>());
for (double &value : bins)
value = std::clamp(value, lo, hi);
if (fit_pass_heights_to_interval(bins, base_height, lo, hi))
return bins;
for (size_t pass_count = desired_passes; pass_count >= min_passes; --pass_count) {
std::vector<double> exact = build_uniform_local_z_pass_heights_exact(base_height, lower_bound, upper_bound, pass_count);
if (!exact.empty())
return exact;
if (pass_count == min_passes)
break;
}
return build_uniform_local_z_pass_heights(base_height, lo, hi, desired_passes);
}
static std::vector<unsigned int> build_local_z_direct_multicolor_sequence(const std::vector<unsigned int> &component_ids,
const std::vector<int> &component_weights,
const std::vector<double> &pass_heights,
std::vector<double> &carry_error_mm)
{
if (component_ids.empty() || pass_heights.empty())
return {};
std::vector<unsigned int> filtered_ids;
std::vector<int> filtered_weights;
filtered_ids.reserve(component_ids.size());
filtered_weights.reserve(component_ids.size());
for (size_t idx = 0; idx < component_ids.size(); ++idx) {
const int weight = idx < component_weights.size() ? std::max(0, component_weights[idx]) : 0;
if (weight <= 0)
continue;
filtered_ids.emplace_back(component_ids[idx]);
filtered_weights.emplace_back(weight);
}
if (filtered_ids.empty()) {
filtered_ids = component_ids;
filtered_weights.assign(component_ids.size(), 1);
}
if (filtered_ids.empty())
return {};
if (carry_error_mm.size() != filtered_ids.size())
carry_error_mm.assign(filtered_ids.size(), 0.0);
const double total_height = std::accumulate(pass_heights.begin(), pass_heights.end(), 0.0);
const int total_weight = std::max(1, std::accumulate(filtered_weights.begin(), filtered_weights.end(), 0));
std::vector<double> desired_heights(filtered_ids.size(), 0.0);
for (size_t idx = 0; idx < filtered_ids.size(); ++idx)
desired_heights[idx] = total_height * double(filtered_weights[idx]) / double(total_weight) + carry_error_mm[idx];
std::vector<double> assigned_heights(filtered_ids.size(), 0.0);
std::vector<unsigned int> sequence;
sequence.reserve(pass_heights.size());
int previous_choice = -1;
for (const double pass_height : pass_heights) {
size_t best_idx = 0;
double best_score = -std::numeric_limits<double>::infinity();
double best_need = -std::numeric_limits<double>::infinity();
for (size_t idx = 0; idx < filtered_ids.size(); ++idx) {
const double remaining_need = desired_heights[idx] - assigned_heights[idx];
double score = remaining_need;
if (int(idx) == previous_choice)
score -= 0.35 * pass_height;
if (score > best_score + 1e-9 ||
(std::abs(score - best_score) <= 1e-9 &&
(remaining_need > best_need + 1e-9 ||
(std::abs(remaining_need - best_need) <= 1e-9 && filtered_ids[idx] < filtered_ids[best_idx])))) {
best_idx = idx;
best_score = score;
best_need = remaining_need;
}
}
assigned_heights[best_idx] += pass_height;
previous_choice = int(best_idx);
sequence.emplace_back(filtered_ids[best_idx]);
}
for (size_t idx = 0; idx < filtered_ids.size(); ++idx)
carry_error_mm[idx] = desired_heights[idx] - assigned_heights[idx];
const double error_sum = std::accumulate(carry_error_mm.begin(), carry_error_mm.end(), 0.0);
if (!carry_error_mm.empty() && std::abs(error_sum) > 1e-9) {
const double correction = error_sum / double(carry_error_mm.size());
for (double &value : carry_error_mm)
value -= correction;
}
return sequence;
}
static LocalZActivePair derive_local_z_active_pair(const MixedFilament &mf, static LocalZActivePair derive_local_z_active_pair(const MixedFilament &mf,
const std::vector<LocalZActivePair> &pair_cycle, const std::vector<LocalZActivePair> &pair_cycle,
size_t num_physical, size_t num_physical,
@@ -2723,6 +2932,92 @@ static std::vector<std::vector<ExPolygons>> local_z_planner_segmentation_with_wh
return augmented; return augmented;
} }
static std::vector<ExPolygons> collect_local_z_fixed_state_masks_by_extruder(const std::vector<ExPolygons> &layer_segmentation,
const size_t num_physical)
{
std::vector<ExPolygons> masks_by_extruder(num_physical);
for (size_t channel_idx = 1; channel_idx < layer_segmentation.size(); ++channel_idx) {
const ExPolygons &state_masks = layer_segmentation[channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
if (state_id == 0 || state_id > num_physical)
continue;
append(masks_by_extruder[state_id - 1], state_masks);
}
for (ExPolygons &masks : masks_by_extruder)
if (masks.size() > 1)
masks = union_ex(masks);
return masks_by_extruder;
}
static std::vector<ExPolygons> build_local_z_transition_fixed_masks_for_pass(
const std::vector<ExPolygons> &current_masks_by_extruder,
const std::vector<ExPolygons> &prev_masks_by_extruder,
const std::vector<ExPolygons> &next_masks_by_extruder,
const size_t pass_idx,
const size_t pass_count)
{
if (pass_count <= 1)
return current_masks_by_extruder;
std::vector<ExPolygons> pass_masks_by_extruder(current_masks_by_extruder.size());
const bool is_lowest_pass = pass_idx == 0;
const bool is_highest_pass = pass_idx + 1 >= pass_count;
for (size_t extruder_idx = 0; extruder_idx < current_masks_by_extruder.size(); ++extruder_idx) {
const ExPolygons &current_masks = current_masks_by_extruder[extruder_idx];
if (current_masks.empty())
continue;
const ExPolygons prev_masks = extruder_idx < prev_masks_by_extruder.size() ? prev_masks_by_extruder[extruder_idx] : ExPolygons();
const ExPolygons next_masks = extruder_idx < next_masks_by_extruder.size() ? next_masks_by_extruder[extruder_idx] : ExPolygons();
const ExPolygons current_and_prev = prev_masks.empty() ? ExPolygons() : intersection_ex(current_masks, prev_masks);
const ExPolygons current_and_next = next_masks.empty() ? ExPolygons() : intersection_ex(current_masks, next_masks);
const ExPolygons persistent =
current_and_prev.empty() || current_and_next.empty() ? ExPolygons() : intersection_ex(current_and_prev, current_and_next);
ExPolygons entering = current_and_next;
if (!entering.empty() && !current_and_prev.empty())
entering = diff_ex(entering, current_and_prev);
ExPolygons exiting = current_and_prev;
if (!exiting.empty() && !current_and_next.empty())
exiting = diff_ex(exiting, current_and_next);
ExPolygons covered;
if (!persistent.empty())
append(covered, persistent);
if (!entering.empty())
append(covered, entering);
if (!exiting.empty())
append(covered, exiting);
if (covered.size() > 1)
covered = union_ex(covered);
const ExPolygons isolated = covered.empty() ? current_masks : diff_ex(current_masks, covered);
ExPolygons assigned;
if (!persistent.empty())
append(assigned, persistent);
if (is_lowest_pass && !exiting.empty())
append(assigned, exiting);
if (is_highest_pass) {
if (!entering.empty())
append(assigned, entering);
if (!isolated.empty())
append(assigned, isolated);
}
if (assigned.size() > 1)
assigned = union_ex(assigned);
pass_masks_by_extruder[extruder_idx] = std::move(assigned);
}
return pass_masks_by_extruder;
}
template<typename ThrowOnCancel> template<typename ThrowOnCancel>
static void build_local_z_plan(PrintObject &print_object, const std::vector<std::vector<ExPolygons>> &segmentation, ThrowOnCancel throw_on_cancel) static void build_local_z_plan(PrintObject &print_object, const std::vector<std::vector<ExPolygons>> &segmentation, ThrowOnCancel throw_on_cancel)
{ {
@@ -2744,6 +3039,9 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
const bool local_z_mode = bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value); const bool local_z_mode = bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value);
const bool local_z_whole_objects = const bool local_z_whole_objects =
bool_from_full_config(full_cfg, "dithering_local_z_whole_objects", print_cfg.dithering_local_z_whole_objects.value); bool_from_full_config(full_cfg, "dithering_local_z_whole_objects", print_cfg.dithering_local_z_whole_objects.value);
const bool local_z_direct_multicolor =
bool_from_full_config(full_cfg, "dithering_local_z_direct_multicolor",
print_cfg.dithering_local_z_direct_multicolor.value);
if (!local_z_mode) { if (!local_z_mode) {
BOOST_LOG_TRIVIAL(debug) << "Local-Z plan skipped: mode disabled" BOOST_LOG_TRIVIAL(debug) << "Local-Z plan skipped: mode disabled"
<< " object=" << object_name; << " object=" << object_name;
@@ -2771,6 +3069,21 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager(); const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
const auto &mixed_rows = mixed_mgr.mixed_filaments(); const auto &mixed_rows = mixed_mgr.mixed_filaments();
std::vector<std::vector<unsigned int>> row_direct_component_ids(mixed_rows.size());
std::vector<std::vector<int>> row_direct_component_weights(mixed_rows.size());
std::vector<std::vector<double>> row_direct_component_error_mm(mixed_rows.size());
std::vector<uint8_t> row_uses_direct_multicolor_solver(mixed_rows.size(), uint8_t(0));
if (local_z_direct_multicolor && preferred_a <= EPSILON && preferred_b <= EPSILON) {
for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) {
if (local_z_direct_multicolor_row(mixed_rows[row_idx],
num_physical,
&row_direct_component_ids[row_idx],
&row_direct_component_weights[row_idx])) {
row_uses_direct_multicolor_solver[row_idx] = uint8_t(1);
row_direct_component_error_mm[row_idx].assign(row_direct_component_ids[row_idx].size(), 0.0);
}
}
}
std::vector<uint8_t> pointillism_row_eligible(mixed_rows.size(), uint8_t(0)); std::vector<uint8_t> pointillism_row_eligible(mixed_rows.size(), uint8_t(0));
for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) { for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) {
const std::vector<unsigned int> sequence = pointillism_sequence_for_row(mixed_rows[row_idx], num_physical); const std::vector<unsigned int> sequence = pointillism_sequence_for_row(mixed_rows[row_idx], num_physical);
@@ -2814,6 +3127,8 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
std::vector<std::vector<LocalZActivePair>> row_pair_cycles(mixed_rows.size()); std::vector<std::vector<LocalZActivePair>> row_pair_cycles(mixed_rows.size());
std::vector<uint8_t> row_uses_layer_cycle_pair(mixed_rows.size(), uint8_t(0)); std::vector<uint8_t> row_uses_layer_cycle_pair(mixed_rows.size(), uint8_t(0));
for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) { for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) {
if (row_uses_direct_multicolor_solver[row_idx] != 0)
continue;
row_pair_cycles[row_idx] = build_local_z_pair_cycle_for_row(mixed_rows[row_idx], num_physical); row_pair_cycles[row_idx] = build_local_z_pair_cycle_for_row(mixed_rows[row_idx], num_physical);
if (!row_pair_cycles[row_idx].empty()) if (!row_pair_cycles[row_idx].empty())
row_uses_layer_cycle_pair[row_idx] = uint8_t(1); row_uses_layer_cycle_pair[row_idx] = uint8_t(1);
@@ -2826,6 +3141,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
<< " mixed_upper=" << mixed_upper << " mixed_upper=" << mixed_upper
<< " preferred_a=" << preferred_a << " preferred_a=" << preferred_a
<< " preferred_b=" << preferred_b << " preferred_b=" << preferred_b
<< " direct_multicolor=" << (local_z_direct_multicolor ? 1 : 0)
<< " physical_filaments=" << num_physical; << " physical_filaments=" << num_physical;
std::vector<LocalZInterval> intervals; std::vector<LocalZInterval> intervals;
@@ -2906,6 +3222,10 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
} }
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) { for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
if (row_active_this_layer[row_idx] != 0 && row_active_prev_layer[row_idx] == 0) { if (row_active_this_layer[row_idx] != 0 && row_active_prev_layer[row_idx] == 0) {
if (row_uses_direct_multicolor_solver[row_idx] != 0 &&
row_direct_component_error_mm[row_idx].size() == row_direct_component_ids[row_idx].size()) {
std::fill(row_direct_component_error_mm[row_idx].begin(), row_direct_component_error_mm[row_idx].end(), 0.0);
}
const bool can_sync_to_dominant = const bool can_sync_to_dominant =
local_z_whole_objects && local_z_whole_objects &&
dominant_mixed_idx < mixed_rows.size() && dominant_mixed_idx < mixed_rows.size() &&
@@ -2924,6 +3244,8 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) { for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx])) if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx]))
continue; continue;
if (row_uses_direct_multicolor_solver[row_idx] != 0)
continue;
const int cadence_index = row_uses_layer_cycle_pair[row_idx] != 0 const int cadence_index = row_uses_layer_cycle_pair[row_idx] != 0
? row_layer_cycle_index[row_idx] ? row_layer_cycle_index[row_idx]
@@ -2935,9 +3257,11 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
const LocalZActivePair &dominant_pair = row_active_pairs[dominant_mixed_idx]; const LocalZActivePair &dominant_pair = row_active_pairs[dominant_mixed_idx];
const int dominant_mix_b_percent = const int dominant_mix_b_percent =
dominant_pair.valid_pair(num_physical) ? dominant_pair.mix_b_percent : mixed_rows[dominant_mixed_idx].mix_b_percent; dominant_pair.valid_pair(num_physical) ? dominant_pair.mix_b_percent : mixed_rows[dominant_mixed_idx].mix_b_percent;
compute_local_z_gradient_component_heights(dominant_mix_b_percent, mixed_lower, mixed_upper, if (row_uses_direct_multicolor_solver[dominant_mixed_idx] == 0) {
dominant_gradient_h_a, dominant_gradient_h_b); compute_local_z_gradient_component_heights(dominant_mix_b_percent, mixed_lower, mixed_upper,
dominant_gradient_valid = true; dominant_gradient_h_a, dominant_gradient_h_b);
dominant_gradient_valid = true;
}
} }
total_mixed_state_layers += mixed_state_count; total_mixed_state_layers += mixed_state_count;
if (!mixed_masks.empty()) if (!mixed_masks.empty())
@@ -2985,6 +3309,12 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
for (ExPolygons &state_masks : fixed_state_masks_by_extruder) for (ExPolygons &state_masks : fixed_state_masks_by_extruder)
if (state_masks.size() > 1) if (state_masks.size() > 1)
state_masks = union_ex(state_masks); state_masks = union_ex(state_masks);
const std::vector<ExPolygons> prev_fixed_state_masks_by_extruder =
layer_id > 0 ? collect_local_z_fixed_state_masks_by_extruder(segmentation[layer_id - 1], num_physical)
: std::vector<ExPolygons>(num_physical);
const std::vector<ExPolygons> next_fixed_state_masks_by_extruder =
layer_id + 1 < segmentation.size() ? collect_local_z_fixed_state_masks_by_extruder(segmentation[layer_id + 1], num_physical)
: std::vector<ExPolygons>(num_physical);
ExPolygons fixed_state_masks_union; ExPolygons fixed_state_masks_union;
for (const ExPolygons &state_masks : fixed_state_masks_by_extruder) for (const ExPolygons &state_masks : fixed_state_masks_by_extruder)
@@ -3004,6 +3334,22 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
if (mixed_masks.size() > 1) if (mixed_masks.size() > 1)
mixed_masks = union_ex(mixed_masks); mixed_masks = union_ex(mixed_masks);
} }
if (local_z_whole_objects && !fixed_state_masks_union.empty()) {
constexpr double LOCAL_Z_WHOLE_OBJECT_FIXED_GUARD_MM = 0.10;
ExPolygons fixed_state_guard_masks = offset_ex(fixed_state_masks_union, float(scale_(LOCAL_Z_WHOLE_OBJECT_FIXED_GUARD_MM)));
if (fixed_state_guard_masks.empty())
fixed_state_guard_masks = fixed_state_masks_union;
else if (fixed_state_guard_masks.size() > 1)
fixed_state_guard_masks = union_ex(fixed_state_guard_masks);
for (ExPolygons &state_masks : row_state_masks) {
if (state_masks.empty())
continue;
state_masks = diff_ex(state_masks, fixed_state_guard_masks);
if (state_masks.size() > 1)
state_masks = union_ex(state_masks);
}
}
size_t active_row_mask_components = 0; size_t active_row_mask_components = 0;
size_t active_row_mask_vertices = 0; size_t active_row_mask_vertices = 0;
@@ -3040,19 +3386,29 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
if (row_active_this_layer[row_idx] == 0) if (row_active_this_layer[row_idx] == 0)
continue; continue;
double row_h_a = 0.0; std::vector<double> row_passes;
double row_h_b = 0.0; if (row_uses_direct_multicolor_solver[row_idx] != 0) {
const LocalZActivePair &active_pair = row_active_pairs[row_idx]; row_passes = build_local_z_direct_multicolor_pass_heights(mixed_rows[row_idx],
const int row_mix_b_percent = row_direct_component_weights[row_idx],
active_pair.valid_pair(num_physical) ? active_pair.mix_b_percent : mixed_rows[row_idx].mix_b_percent; interval.base_height,
compute_local_z_gradient_component_heights(row_mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b); mixed_lower,
std::vector<double> row_passes = active_pair.uses_layer_cycle_sequence mixed_upper,
? build_local_z_two_pass_heights(interval.base_height, mixed_lower, mixed_upper, row_h_a, row_h_b) row_direct_component_ids[row_idx].size());
: build_local_z_alternating_pass_heights(interval.base_height, } else {
mixed_lower, double row_h_a = 0.0;
mixed_upper, double row_h_b = 0.0;
row_h_a, const LocalZActivePair &active_pair = row_active_pairs[row_idx];
row_h_b); const int row_mix_b_percent =
active_pair.valid_pair(num_physical) ? active_pair.mix_b_percent : mixed_rows[row_idx].mix_b_percent;
compute_local_z_gradient_component_heights(row_mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
row_passes = active_pair.uses_layer_cycle_sequence
? build_local_z_two_pass_heights(interval.base_height, mixed_lower, mixed_upper, row_h_a, row_h_b)
: build_local_z_alternating_pass_heights(interval.base_height,
mixed_lower,
mixed_upper,
row_h_a,
row_h_b);
}
if (row_passes.empty()) if (row_passes.empty())
row_passes.emplace_back(interval.base_height); row_passes.emplace_back(interval.base_height);
if (!sanitize_local_z_pass_heights(row_passes, interval.base_height, mixed_lower, mixed_upper)) if (!sanitize_local_z_pass_heights(row_passes, interval.base_height, mixed_lower, mixed_upper))
@@ -3076,6 +3432,16 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
pass_heights = build_local_z_shared_pass_heights(interval.base_height, mixed_lower, mixed_upper); pass_heights = build_local_z_shared_pass_heights(interval.base_height, mixed_lower, mixed_upper);
if (pass_heights.size() > 1) if (pass_heights.size() > 1)
++alternating_height_intervals; ++alternating_height_intervals;
} else if (dominant_mixed_idx < mixed_rows.size() &&
row_uses_direct_multicolor_solver[dominant_mixed_idx] != 0) {
pass_heights = build_local_z_direct_multicolor_pass_heights(mixed_rows[dominant_mixed_idx],
row_direct_component_weights[dominant_mixed_idx],
interval.base_height,
mixed_lower,
mixed_upper,
row_direct_component_ids[dominant_mixed_idx].size());
if (pass_heights.size() > 1)
++alternating_height_intervals;
} else if (dominant_gradient_valid) { } else if (dominant_gradient_valid) {
const bool dominant_uses_pair_cycle = const bool dominant_uses_pair_cycle =
dominant_mixed_idx < mixed_rows.size() && row_active_pairs[dominant_mixed_idx].uses_layer_cycle_sequence; dominant_mixed_idx < mixed_rows.size() && row_active_pairs[dominant_mixed_idx].uses_layer_cycle_sequence;
@@ -3144,13 +3510,20 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
? std::vector<double>{ interval.base_height } ? std::vector<double>{ interval.base_height }
: row_passes_raw; : row_passes_raw;
const LocalZActivePair &active_pair = row_active_pairs[row_idx]; const LocalZActivePair &active_pair = row_active_pairs[row_idx];
const bool uses_direct_multicolor = row_uses_direct_multicolor_solver[row_idx] != 0;
const bool valid_pair = active_pair.valid_pair(num_physical); const bool valid_pair = active_pair.valid_pair(num_physical);
const int orientation_cadence_index = active_pair.uses_layer_cycle_sequence const int orientation_cadence_index = active_pair.uses_layer_cycle_sequence
? row_layer_cycle_index[row_idx] ? row_layer_cycle_index[row_idx]
: row_cadence_index[row_idx]; : row_cadence_index[row_idx];
const std::vector<unsigned int> direct_sequence = uses_direct_multicolor
? build_local_z_direct_multicolor_sequence(row_direct_component_ids[row_idx],
row_direct_component_weights[row_idx],
row_passes,
row_direct_component_error_mm[row_idx])
: std::vector<unsigned int>();
bool start_with_a = true; bool start_with_a = true;
if (valid_pair && preferred_a <= EPSILON && preferred_b <= EPSILON) { if (!uses_direct_multicolor && valid_pair && preferred_a <= EPSILON && preferred_b <= EPSILON) {
double row_h_a = 0.0; double row_h_a = 0.0;
double row_h_b = 0.0; double row_h_b = 0.0;
compute_local_z_gradient_component_heights(active_pair.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b); compute_local_z_gradient_component_heights(active_pair.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
@@ -3180,11 +3553,15 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
plan.print_z = z_next; plan.print_z = z_next;
plan.flow_height = pass_height; plan.flow_height = pass_height;
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons()); plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
plan.fixed_painted_masks_by_extruder.assign(num_physical, ExPolygons());
++split_passes_total; ++split_passes_total;
++forced_height_resolve_calls; ++forced_height_resolve_calls;
unsigned int target_extruder = 0; unsigned int target_extruder = 0;
if (valid_pair) { if (uses_direct_multicolor) {
if (pass_i < direct_sequence.size())
target_extruder = direct_sequence[pass_i];
} else if (valid_pair) {
const bool even_pass = (pass_i % 2) == 0; const bool even_pass = (pass_i % 2) == 0;
target_extruder = even_pass target_extruder = even_pass
? (start_with_a ? active_pair.component_a : active_pair.component_b) ? (start_with_a ? active_pair.component_a : active_pair.component_b)
@@ -3212,24 +3589,17 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
++split_passes_with_painted_masks; ++split_passes_with_painted_masks;
interval_has_split_painted_masks = true; interval_has_split_painted_masks = true;
} }
for (size_t extruder_idx = 0; extruder_idx < fixed_state_masks_by_extruder.size(); ++extruder_idx)
if (!fixed_state_masks_by_extruder[extruder_idx].empty())
append(plan.painted_masks_by_extruder[extruder_idx], fixed_state_masks_by_extruder[extruder_idx]);
if (!fixed_state_masks_union.empty()) {
++split_passes_with_painted_masks;
interval_has_split_painted_masks = true;
}
for (ExPolygons &masks : plan.painted_masks_by_extruder) for (ExPolygons &masks : plan.painted_masks_by_extruder)
if (masks.size() > 1) if (masks.size() > 1)
masks = union_ex(masks); masks = union_ex(masks);
isolated_plans.emplace_back(std::move(plan)); isolated_plans.emplace_back(std::move(plan));
row_used = true; row_used = true;
if (!active_pair.uses_layer_cycle_sequence) if (!uses_direct_multicolor && !active_pair.uses_layer_cycle_sequence)
++row_cadence_index[row_idx]; ++row_cadence_index[row_idx];
z_cursor = z_next; z_cursor = z_next;
} }
if (row_used && active_pair.uses_layer_cycle_sequence) if (row_used && !uses_direct_multicolor && active_pair.uses_layer_cycle_sequence)
++row_layer_cycle_index[row_idx]; ++row_layer_cycle_index[row_idx];
} }
@@ -3247,6 +3617,31 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
isolated_plans[idx].pass_index = idx; isolated_plans[idx].pass_index = idx;
min_flow_height = std::min(min_flow_height, isolated_plans[idx].flow_height); min_flow_height = std::min(min_flow_height, isolated_plans[idx].flow_height);
max_flow_height = std::max(max_flow_height, isolated_plans[idx].flow_height); max_flow_height = std::max(max_flow_height, isolated_plans[idx].flow_height);
const std::vector<ExPolygons> fixed_masks_for_pass =
build_local_z_transition_fixed_masks_for_pass(fixed_state_masks_by_extruder,
prev_fixed_state_masks_by_extruder,
next_fixed_state_masks_by_extruder,
idx,
isolated_plans.size());
bool plan_has_fixed_masks = false;
for (size_t extruder_idx = 0; extruder_idx < fixed_masks_for_pass.size() &&
extruder_idx < isolated_plans[idx].fixed_painted_masks_by_extruder.size();
++extruder_idx) {
if (fixed_masks_for_pass[extruder_idx].empty())
continue;
append(isolated_plans[idx].fixed_painted_masks_by_extruder[extruder_idx], fixed_masks_for_pass[extruder_idx]);
plan_has_fixed_masks = true;
}
for (ExPolygons &masks : isolated_plans[idx].painted_masks_by_extruder)
if (masks.size() > 1)
masks = union_ex(masks);
for (ExPolygons &masks : isolated_plans[idx].fixed_painted_masks_by_extruder)
if (masks.size() > 1)
masks = union_ex(masks);
if (plan_has_fixed_masks) {
++split_passes_with_painted_masks;
interval_has_split_painted_masks = true;
}
} }
isolated_plans.back().base_masks = base_masks; isolated_plans.back().base_masks = base_masks;
interval.sublayer_height = min_flow_height; interval.sublayer_height = min_flow_height;
@@ -3263,10 +3658,19 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
// Derive per-row orientation against pass heights so each mixed row // Derive per-row orientation against pass heights so each mixed row
// maps thicker/thinner subpasses to the intended component. // maps thicker/thinner subpasses to the intended component.
std::vector<uint8_t> start_with_component_a(mixed_rows.size(), uint8_t(1)); std::vector<uint8_t> start_with_component_a(mixed_rows.size(), uint8_t(1));
std::vector<std::vector<unsigned int>> row_direct_pass_sequences(mixed_rows.size());
if (preferred_a <= EPSILON && preferred_b <= EPSILON) { if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) { for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx])) if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx]))
continue; continue;
if (row_uses_direct_multicolor_solver[row_idx] != 0) {
row_direct_pass_sequences[row_idx] =
build_local_z_direct_multicolor_sequence(row_direct_component_ids[row_idx],
row_direct_component_weights[row_idx],
pass_heights,
row_direct_component_error_mm[row_idx]);
continue;
}
const LocalZActivePair &active_pair = row_active_pairs[row_idx]; const LocalZActivePair &active_pair = row_active_pairs[row_idx];
if (!active_pair.valid_pair(num_physical)) if (!active_pair.valid_pair(num_physical))
@@ -3305,32 +3709,33 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
plan.print_z = z_next; plan.print_z = z_next;
plan.flow_height = pass_height; plan.flow_height = pass_height;
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons()); plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
plan.fixed_painted_masks_by_extruder.assign(num_physical, ExPolygons());
++split_passes_total; ++split_passes_total;
bool pass_has_painted_masks = false; bool pass_has_painted_masks = false;
std::vector<uint8_t> row_seen_in_pass(mixed_rows.size(), uint8_t(0)); std::vector<uint8_t> row_seen_in_pass(mixed_rows.size(), uint8_t(0));
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) { for (size_t row_idx = 0; row_idx < row_state_masks.size(); ++row_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx]; const ExPolygons &state_masks = row_state_masks[row_idx];
if (state_masks.empty()) if (state_masks.empty())
continue; continue;
const unsigned int state_id = segmentation_channel_filament_id(channel_idx); const unsigned int state_id = row_state_ids[row_idx];
if (!mixed_mgr.is_mixed(state_id, num_physical)) if (state_id == 0)
continue; continue;
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size())
continue;
const size_t row_idx = size_t(mixed_idx);
const MixedFilament &mf = mixed_rows[row_idx]; const MixedFilament &mf = mixed_rows[row_idx];
if (!local_z_eligible_mixed_row(mf)) if (!local_z_eligible_mixed_row(mf))
continue; continue;
const LocalZActivePair &active_pair = row_active_pairs[row_idx]; const LocalZActivePair &active_pair = row_active_pairs[row_idx];
const bool uses_direct_multicolor = row_uses_direct_multicolor_solver[row_idx] != 0;
row_seen_in_pass[row_idx] = uint8_t(1); row_seen_in_pass[row_idx] = uint8_t(1);
if (active_pair.uses_layer_cycle_sequence) if (!uses_direct_multicolor && active_pair.uses_layer_cycle_sequence)
row_seen_sequence_in_interval[row_idx] = uint8_t(1); row_seen_sequence_in_interval[row_idx] = uint8_t(1);
++forced_height_resolve_calls; ++forced_height_resolve_calls;
unsigned int target_extruder = 0; unsigned int target_extruder = 0;
if (active_pair.valid_pair(num_physical)) { if (uses_direct_multicolor) {
if (pass_idx < row_direct_pass_sequences[row_idx].size())
target_extruder = row_direct_pass_sequences[row_idx][pass_idx];
} else if (active_pair.valid_pair(num_physical)) {
const bool start_a = start_with_component_a[row_idx] != 0; const bool start_a = start_with_component_a[row_idx] != 0;
const bool even_pass = (pass_idx % 2) == 0; const bool even_pass = (pass_idx % 2) == 0;
// Local-Z mode alternates A/B on every subpass. // Local-Z mode alternates A/B on every subpass.
@@ -3357,14 +3762,23 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks); append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks);
pass_has_painted_masks = true; pass_has_painted_masks = true;
} }
for (size_t extruder_idx = 0; extruder_idx < fixed_state_masks_by_extruder.size(); ++extruder_idx) const std::vector<ExPolygons> fixed_masks_for_pass =
if (!fixed_state_masks_by_extruder[extruder_idx].empty()) { build_local_z_transition_fixed_masks_for_pass(fixed_state_masks_by_extruder,
append(plan.painted_masks_by_extruder[extruder_idx], fixed_state_masks_by_extruder[extruder_idx]); prev_fixed_state_masks_by_extruder,
next_fixed_state_masks_by_extruder,
pass_idx,
pass_heights.size());
for (size_t extruder_idx = 0; extruder_idx < fixed_masks_for_pass.size(); ++extruder_idx)
if (!fixed_masks_for_pass[extruder_idx].empty()) {
append(plan.fixed_painted_masks_by_extruder[extruder_idx], fixed_masks_for_pass[extruder_idx]);
pass_has_painted_masks = true; pass_has_painted_masks = true;
} }
for (ExPolygons &masks : plan.painted_masks_by_extruder) for (ExPolygons &masks : plan.painted_masks_by_extruder)
if (masks.size() > 1) if (masks.size() > 1)
masks = union_ex(masks); masks = union_ex(masks);
for (ExPolygons &masks : plan.fixed_painted_masks_by_extruder)
if (masks.size() > 1)
masks = union_ex(masks);
if (pass_has_painted_masks) { if (pass_has_painted_masks) {
++split_passes_with_painted_masks; ++split_passes_with_painted_masks;
interval_has_split_painted_masks = true; interval_has_split_painted_masks = true;
@@ -3378,12 +3792,15 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
++total_generated_sublayer_cnt; ++total_generated_sublayer_cnt;
++pass_idx; ++pass_idx;
for (size_t mixed_idx = 0; mixed_idx < row_seen_in_pass.size(); ++mixed_idx) for (size_t mixed_idx = 0; mixed_idx < row_seen_in_pass.size(); ++mixed_idx)
if (row_seen_in_pass[mixed_idx] != 0 && row_uses_layer_cycle_pair[mixed_idx] == 0) if (row_seen_in_pass[mixed_idx] != 0 &&
row_uses_layer_cycle_pair[mixed_idx] == 0 &&
row_uses_direct_multicolor_solver[mixed_idx] == 0)
++row_cadence_index[mixed_idx]; ++row_cadence_index[mixed_idx];
z_cursor = z_next; z_cursor = z_next;
} }
for (size_t row_idx = 0; row_idx < row_seen_sequence_in_interval.size(); ++row_idx) for (size_t row_idx = 0; row_idx < row_seen_sequence_in_interval.size(); ++row_idx)
if (row_seen_sequence_in_interval[row_idx] != 0) if (row_seen_sequence_in_interval[row_idx] != 0 &&
row_uses_direct_multicolor_solver[row_idx] == 0)
++row_layer_cycle_index[row_idx]; ++row_layer_cycle_index[row_idx];
} }
if (!interval_has_split_painted_masks) if (!interval_has_split_painted_masks)
@@ -3401,35 +3818,43 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
plan.flow_height = interval.base_height; plan.flow_height = interval.base_height;
plan.base_masks = base_masks; plan.base_masks = base_masks;
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons()); plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
plan.fixed_painted_masks_by_extruder.assign(num_physical, ExPolygons());
std::vector<uint8_t> row_seen_in_interval(mixed_rows.size(), uint8_t(0)); std::vector<uint8_t> row_seen_in_interval(mixed_rows.size(), uint8_t(0));
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) { for (size_t row_idx = 0; row_idx < row_state_masks.size(); ++row_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx]; const ExPolygons &state_masks = row_state_masks[row_idx];
if (state_masks.empty()) if (state_masks.empty())
continue; continue;
const unsigned int state_id = segmentation_channel_filament_id(channel_idx); const unsigned int state_id = row_state_ids[row_idx];
if (!mixed_mgr.is_mixed(state_id, num_physical)) if (state_id == 0)
continue; continue;
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size())
continue;
const size_t row_idx = size_t(mixed_idx);
const MixedFilament &mixed_row = mixed_rows[row_idx]; const MixedFilament &mixed_row = mixed_rows[row_idx];
if (!local_z_eligible_mixed_row(mixed_row)) if (!local_z_eligible_mixed_row(mixed_row))
continue; continue;
row_seen_in_interval[row_idx] = uint8_t(1); row_seen_in_interval[row_idx] = uint8_t(1);
++forced_height_resolve_calls; ++forced_height_resolve_calls;
const int resolve_cadence_index = row_uses_layer_cycle_pair[row_idx] != 0 unsigned int target_extruder = 0;
? row_layer_cycle_index[row_idx] if (row_uses_direct_multicolor_solver[row_idx] != 0) {
: row_cadence_index[row_idx]; const std::vector<unsigned int> direct_sequence =
const unsigned int target_extruder = build_local_z_direct_multicolor_sequence(row_direct_component_ids[row_idx],
mixed_mgr.resolve(state_id, row_direct_component_weights[row_idx],
num_physical, std::vector<double>{ interval.base_height },
resolve_cadence_index, row_direct_component_error_mm[row_idx]);
float(plan.print_z), if (!direct_sequence.empty())
float(plan.flow_height), target_extruder = direct_sequence.front();
force_height_resolve); } else {
const int resolve_cadence_index = row_uses_layer_cycle_pair[row_idx] != 0
? row_layer_cycle_index[row_idx]
: row_cadence_index[row_idx];
target_extruder =
mixed_mgr.resolve(state_id,
num_physical,
resolve_cadence_index,
float(plan.print_z),
float(plan.flow_height),
force_height_resolve);
}
if (target_extruder == 0 || target_extruder > num_physical) { if (target_extruder == 0 || target_extruder > num_physical) {
++forced_height_resolve_invalid_target; ++forced_height_resolve_invalid_target;
continue; continue;
@@ -3438,17 +3863,21 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
} }
for (size_t extruder_idx = 0; extruder_idx < fixed_state_masks_by_extruder.size(); ++extruder_idx) for (size_t extruder_idx = 0; extruder_idx < fixed_state_masks_by_extruder.size(); ++extruder_idx)
if (!fixed_state_masks_by_extruder[extruder_idx].empty()) if (!fixed_state_masks_by_extruder[extruder_idx].empty())
append(plan.painted_masks_by_extruder[extruder_idx], fixed_state_masks_by_extruder[extruder_idx]); append(plan.fixed_painted_masks_by_extruder[extruder_idx], fixed_state_masks_by_extruder[extruder_idx]);
for (ExPolygons &masks : plan.painted_masks_by_extruder) for (ExPolygons &masks : plan.painted_masks_by_extruder)
if (masks.size() > 1) if (masks.size() > 1)
masks = union_ex(masks); masks = union_ex(masks);
for (ExPolygons &masks : plan.fixed_painted_masks_by_extruder)
if (masks.size() > 1)
masks = union_ex(masks);
plans.emplace_back(std::move(plan)); plans.emplace_back(std::move(plan));
interval.sublayer_count = 1; interval.sublayer_count = 1;
++total_generated_sublayer_cnt; ++total_generated_sublayer_cnt;
for (size_t mixed_idx = 0; mixed_idx < row_seen_in_interval.size(); ++mixed_idx) for (size_t mixed_idx = 0; mixed_idx < row_seen_in_interval.size(); ++mixed_idx)
if (row_seen_in_interval[mixed_idx] != 0) if (row_seen_in_interval[mixed_idx] != 0)
(row_uses_layer_cycle_pair[mixed_idx] != 0 ? row_layer_cycle_index[mixed_idx] : row_cadence_index[mixed_idx])++; if (row_uses_direct_multicolor_solver[mixed_idx] == 0)
(row_uses_layer_cycle_pair[mixed_idx] != 0 ? row_layer_cycle_index[mixed_idx] : row_cadence_index[mixed_idx])++;
} }
if (interval.has_mixed_paint) { if (interval.has_mixed_paint) {
+1
View File
@@ -785,6 +785,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
config->has("dithering_local_z_mode") && config->option("dithering_local_z_mode") != nullptr && config->has("dithering_local_z_mode") && config->option("dithering_local_z_mode") != nullptr &&
config->opt_bool("dithering_local_z_mode"); config->opt_bool("dithering_local_z_mode");
toggle_line("dithering_local_z_whole_objects", local_z_dithering_enabled); toggle_line("dithering_local_z_whole_objects", local_z_dithering_enabled);
toggle_line("dithering_local_z_direct_multicolor", local_z_dithering_enabled);
toggle_line("local_z_wipe_tower_purge_lines", have_prime_tower && !is_BBL_Printer && local_z_dithering_enabled); toggle_line("local_z_wipe_tower_purge_lines", have_prime_tower && !is_BBL_Printer && local_z_dithering_enabled);
WipeTowerWallType wipe_tower_wall_type = config->opt_enum<WipeTowerWallType>("wipe_tower_wall_type"); WipeTowerWallType wipe_tower_wall_type = config->opt_enum<WipeTowerWallType>("wipe_tower_wall_type");
+1
View File
@@ -6412,6 +6412,7 @@ void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool ch
"dithering_z_step_size", "dithering_z_step_size",
"dithering_local_z_mode", "dithering_local_z_mode",
"dithering_local_z_whole_objects", "dithering_local_z_whole_objects",
"dithering_local_z_direct_multicolor",
"dithering_step_painted_zones_only", "dithering_step_painted_zones_only",
"mixed_filament_pointillism_pixel_size", "mixed_filament_pointillism_pixel_size",
"mixed_filament_pointillism_line_gap", "mixed_filament_pointillism_line_gap",
+30
View File
@@ -5671,6 +5671,10 @@ MixedFilamentDisplayContext build_mixed_filament_display_context(const std::vect
if (print_cfg != nullptr && print_cfg->has("wall_loops")) if (print_cfg != nullptr && print_cfg->has("wall_loops"))
context.preview_settings.wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops")))); context.preview_settings.wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops"))));
context.preview_settings.local_z_mode = get_mixed_bool("dithering_local_z_mode", false); context.preview_settings.local_z_mode = get_mixed_bool("dithering_local_z_mode", false);
context.preview_settings.local_z_direct_multicolor =
get_mixed_bool("dithering_local_z_direct_multicolor", false) &&
context.preview_settings.preferred_a_height <= EPSILON &&
context.preview_settings.preferred_b_height <= EPSILON;
context.component_bias_enabled = get_mixed_bool("mixed_filament_component_bias_enabled", false); context.component_bias_enabled = get_mixed_bool("mixed_filament_component_bias_enabled", false);
return context; return context;
@@ -5866,6 +5870,26 @@ std::string MixedFilamentConfigPanel::summarize_local_z_breakdown(const MixedFil
}; };
const std::vector<unsigned int> ids = decode_gradient_ids(mf.gradient_component_ids); const std::vector<unsigned int> ids = decode_gradient_ids(mf.gradient_component_ids);
if (preview_settings.local_z_mode && preview_settings.local_z_direct_multicolor && ids.size() >= 3) {
const std::vector<int> normalized = normalize_gradient_weights(weights, ids.size());
const size_t effective_sublayers =
mf.local_z_max_sublayers >= 2 ? size_t(std::max(2, mf.local_z_max_sublayers)) : ids.size();
std::ostringstream ss;
ss << "Local-Z direct multicolor solver: ";
for (size_t idx = 0; idx < ids.size(); ++idx) {
if (idx > 0)
ss << ", ";
const int pct = idx < normalized.size() ? normalized[idx] : 0;
ss << 'F' << ids[idx] << ' ' << pct << '%';
}
ss << ".\nCarry-over error is distributed directly across all " << ids.size()
<< " components instead of collapsing them into pair cadence.";
if (mf.local_z_max_sublayers >= 2)
ss << "\nEffective Local-Z cap: up to " << effective_sublayers << " sublayers per nominal layer.";
return ss.str();
}
if (ids.size() >= 4) { if (ids.size() >= 4) {
const std::vector<int> normalized = normalize_gradient_weights(weights, ids.size()); const std::vector<int> normalized = normalize_gradient_weights(weights, ids.size());
const std::vector<unsigned int> pair_tokens = { 1, 2 }; const std::vector<unsigned int> pair_tokens = { 1, 2 };
@@ -7281,6 +7305,10 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
if (print_cfg && print_cfg->has("wall_loops")) if (print_cfg && print_cfg->has("wall_loops"))
wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops")))); wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops"))));
const bool local_z_mode = get_mixed_bool("dithering_local_z_mode", false); const bool local_z_mode = get_mixed_bool("dithering_local_z_mode", false);
const bool local_z_direct_multicolor =
get_mixed_bool("dithering_local_z_direct_multicolor", false) &&
preferred_local_z_a <= EPSILON &&
preferred_local_z_b <= EPSILON;
const bool component_bias_enabled = get_mixed_bool("mixed_filament_component_bias_enabled", false); const bool component_bias_enabled = get_mixed_bool("mixed_filament_component_bias_enabled", false);
float pointillism_pixel_size = std::max(0.f, get_mixed_float("mixed_filament_pointillism_pixel_size", 0.f)); float pointillism_pixel_size = std::max(0.f, get_mixed_float("mixed_filament_pointillism_pixel_size", 0.f));
float pointillism_line_gap = std::max(0.f, get_mixed_float("mixed_filament_pointillism_line_gap", 0.f)); float pointillism_line_gap = std::max(0.f, get_mixed_float("mixed_filament_pointillism_line_gap", 0.f));
@@ -7294,6 +7322,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
preferred_local_z_a, preferred_local_z_a,
preferred_local_z_b, preferred_local_z_b,
local_z_mode, local_z_mode,
local_z_direct_multicolor,
wall_loops wall_loops
}; };
const MixedFilamentDisplayContext display_context { const MixedFilamentDisplayContext display_context {
@@ -10493,6 +10522,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
"dithering_z_step_size", "dithering_z_step_size",
"dithering_local_z_mode", "dithering_local_z_mode",
"dithering_local_z_whole_objects", "dithering_local_z_whole_objects",
"dithering_local_z_direct_multicolor",
"dithering_step_painted_zones_only" "dithering_step_painted_zones_only"
}; };
preset_bundle->project_config.apply_only(config_loaded, imported_project_option_keys, true); preset_bundle->project_config.apply_only(config_loaded, imported_project_option_keys, true);
+15
View File
@@ -1519,6 +1519,19 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
field->set_value(boost::any(false), false); field->set_value(boost::any(false), false);
update_dirty(); update_dirty();
} }
if (!local_z_enabled &&
m_config->has("dithering_local_z_direct_multicolor") &&
m_config->option("dithering_local_z_direct_multicolor") != nullptr &&
m_config->opt_bool("dithering_local_z_direct_multicolor")) {
change_opt_value(*m_config, "dithering_local_z_direct_multicolor", boost::any(false));
if (m_type == Preset::TYPE_PRINT) {
DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config;
project_cfg.set_key_value("dithering_local_z_direct_multicolor", new ConfigOptionBool(false));
}
if (Field *field = this->get_field("dithering_local_z_direct_multicolor"))
field->set_value(boost::any(false), false);
update_dirty();
}
} }
@@ -1829,6 +1842,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
opt_key == "dithering_z_step_size" || opt_key == "dithering_z_step_size" ||
opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_mode" ||
opt_key == "dithering_local_z_whole_objects" || opt_key == "dithering_local_z_whole_objects" ||
opt_key == "dithering_local_z_direct_multicolor" ||
opt_key == "dithering_step_painted_zones_only" || opt_key == "dithering_step_painted_zones_only" ||
opt_key == "mixed_filament_definitions")) { opt_key == "mixed_filament_definitions")) {
DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config; DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config;
@@ -2574,6 +2588,7 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops"
optgroup->append_single_option_line("dithering_z_step_size"); optgroup->append_single_option_line("dithering_z_step_size");
optgroup->append_single_option_line("dithering_local_z_mode"); optgroup->append_single_option_line("dithering_local_z_mode");
optgroup->append_single_option_line("dithering_local_z_whole_objects"); optgroup->append_single_option_line("dithering_local_z_whole_objects");
optgroup->append_single_option_line("dithering_local_z_direct_multicolor");
optgroup->append_single_option_line("dithering_step_painted_zones_only"); optgroup->append_single_option_line("dithering_step_painted_zones_only");
optgroup = page->new_optgroup(L("Fuzzy Skin"), L"fuzzy_skin"); optgroup = page->new_optgroup(L("Fuzzy Skin"), L"fuzzy_skin");
+2 -2
View File
@@ -10,8 +10,8 @@ endif()
if(NOT DEFINED BBL_INTERNAL_TESTING) if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0") set(BBL_INTERNAL_TESTING "0")
endif() endif()
set(Snapmaker_VERSION "0.9.7") set(Snapmaker_VERSION "0.9.8")
set(FULLSPECTRUM_VERSION "0.9.7") set(FULLSPECTRUM_VERSION "0.9.8")
set(MIN_FIRM_VER "1.0.0") set(MIN_FIRM_VER "1.0.0")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
Snapmaker_VERSION_MATCH ${Snapmaker_VERSION}) Snapmaker_VERSION_MATCH ${Snapmaker_VERSION})