Add changelog for v0.8 pre-release: Introduce filament mixer color accuracy improvements, and installation instructions. Update README to include support for development. Enhance AboutDialog with updated application name and links.

This commit is contained in:
Rad
2026-02-17 16:01:17 +01:00
parent 2bd72a9f32
commit 77d0e1b2d7
5 changed files with 572 additions and 151 deletions

61
CHANGELOG_v0.8.md Normal file
View File

@@ -0,0 +1,61 @@
## v0.8 Pre-release
EXPERIMENTAL BUILD - LIMITED TESTING
Based on Snapmaker Orca v2.2.4
v0.8 full spectrum pre-release focuses on filament mixer color accuracy and Linux AppImage packaging.
This has had limited testing on the Snapmaker U1 via the community.
### What's New in v0.8
#### Filament Mixer Color Blending (v0.8)
- Replaced the legacy RYB blend path with FilamentMixer.
- Updated mixed-filament preview/display color computation to use FilamentMixer interpolation.
- Improved mixed-filament color consistency for generated combinations.
- Retained legacy RYB conversion helpers in code as reference.
#### Linux AppImage Packaging (new in v0.8)
- Added Linux release artifact: `Snapmaker_Orca_Linux_V2.2.4.AppImage`.
- AppImage is now the primary Linux release artifact for end users.
- Current Linux build target is `x86_64`.
- Current runtime baseline is glibc `2.38+`.
- `Snapmaker_Orca.tar` remains available as an advanced/manual fallback.
### Installation
#### Windows
1. Download `Snapmaker_Orca.zip`.
2. Extract to a folder.
3. Run the executable.
#### macOS
1. Download the macOS build (`arm64` for Apple Silicon or `x86_64` for Intel).
2. If the release asset is a `.zip`, unzip it first.
3. Open the `.dmg`.
4. Drag `Snapmaker_Orca.app` into `Applications`.
5. Launch the app from `Applications`.
#### Linux (AppImage)
1. Download `Snapmaker_Orca_Linux_V2.2.4.AppImage`.
2. Run `chmod +x Snapmaker_Orca_Linux_V2.2.4.AppImage`.
3. Run `./Snapmaker_Orca_Linux_V2.2.4.AppImage`.
### Warning
- Use at your own risk.
- May produce incorrect G-code in edge cases.
- Mixed-filament behavior is still experimental in some scenarios.
- This release has had limited real-printer validation.
### Features Not Yet Fully Tested
1. Linux AppImage behavior across all desktop environments/distributions.
2. Mixed-filament visual color matching across different filament brands/materials.
### Known Issues
- Older Linux distributions may fail to run this AppImage due to glibc mismatch.
- Some systems may require `libfuse2` for AppImage execution.
- On-screen color blend preview may not exactly match physical print results.
### Credits
- FilamentMixer color blending integration is powered by the FilamentMixer library by [justinh-rahb](https://github.com/justinh-rahb).
- Library repository: [https://github.com/justinh-rahb/filament-mixer](https://github.com/justinh-rahb/filament-mixer).

View File

@@ -7,6 +7,14 @@
---
## ☕ Support Development
If you find this fun or interesting!
<a href="https://www.buymeacoffee.com/ratdoux" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
---
## ⚠️ **IMPORTANT DISCLAIMER** ⚠️
**This fork is currently in active development and has NOT been tested on actual hardware! **

View File

@@ -5175,8 +5175,12 @@ LayerResult GCode::process_layer(const Print& print,
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layer_to_print.layer();
m_object_layer_over_raft = object_layer_over_raft;
if (m_config.reduce_crossing_wall)
m_avoid_crossing_perimeters.init_layer(*m_layer);
const bool saved_reduce_crossing_wall = m_config.reduce_crossing_wall.value;
// Local-Z phase-b emits many short micro-passes. Avoid-crossing
// travel planning is expensive and fragile on these fragments, so
// keep travel simple here.
m_config.reduce_crossing_wall.value = false;
m_avoid_crossing_perimeters.disable_once();
const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
@@ -5189,6 +5193,7 @@ LayerResult GCode::process_layer(const Print& print,
gcode += this->extrude_perimeters(print, island.by_region, first_layer, false);
gcode += this->extrude_perimeters(print, island.by_region, first_layer, true);
}
m_config.reduce_crossing_wall.value = saved_reduce_crossing_wall;
}
}
m_nominal_z = saved_nominal_z;

View File

@@ -1015,6 +1015,21 @@ static bool fit_pass_heights_to_interval(std::vector<double> &passes, double bas
return std::all_of(passes.begin(), passes.end(), within);
}
static bool sanitize_local_z_pass_heights(std::vector<double> &passes, double base_height, double lower_bound, double upper_bound)
{
if (passes.empty() || base_height <= EPSILON)
return false;
const double lo = std::max<double>(0.01, lower_bound);
const double hi = std::max<double>(lo, upper_bound);
for (double &h : passes) {
if (!std::isfinite(h))
h = lo;
h = std::clamp(h, lo, hi);
}
return fit_pass_heights_to_interval(passes, base_height, lo, hi);
}
static std::vector<double> build_uniform_local_z_pass_heights(double base_height, double lo, double hi)
{
std::vector<double> out;
@@ -1079,7 +1094,6 @@ static std::vector<double> build_local_z_alternating_pass_heights(double base_he
const double cycle_h = std::max<double>(EPSILON, gradient_h_a + gradient_h_b);
const double ratio_a = std::clamp(gradient_h_a / cycle_h, 0.0, 1.0);
const double ratio_b = 1.0 - ratio_a;
size_t min_passes = size_t(std::max<double>(2.0, std::ceil((base_height - EPSILON) / hi)));
if ((min_passes % 2) != 0)
@@ -1091,11 +1105,44 @@ static std::vector<double> build_local_z_alternating_pass_heights(double base_he
if (max_passes < 2 || min_passes > max_passes)
return build_uniform_local_z_pass_heights(base_height, lo, hi);
const double target_step = 0.5 * (lo + hi);
size_t target_passes =
size_t(std::max<double>(2.0, std::llround(base_height / std::max<double>(target_step, EPSILON))));
if ((target_passes % 2) != 0) {
const size_t round_up = (target_passes < max_passes) ? (target_passes + 1) : max_passes;
const size_t round_down = (target_passes > min_passes) ? (target_passes - 1) : min_passes;
if (round_up > max_passes)
target_passes = round_down;
else if (round_down < min_passes)
target_passes = round_up;
else {
const size_t up_dist = round_up - target_passes;
const size_t down_dist = target_passes - round_down;
target_passes = (up_dist <= down_dist) ? round_up : round_down;
}
}
target_passes = std::clamp(target_passes, min_passes, max_passes);
bool has_best = false;
std::vector<double> best_passes;
double best_ratio_error = 0.0;
size_t best_pass_distance = 0;
double best_max_height = 0.0;
size_t best_pass_count = 0;
for (size_t pass_count = min_passes; pass_count <= max_passes; pass_count += 2) {
const size_t pair_count = pass_count / 2;
const double pair_h = base_height / double(pair_count);
const double h_a = pair_h * ratio_a;
const double h_b = pair_h * ratio_b;
if (pair_count == 0)
continue;
const double pair_h = base_height / double(pair_count);
const double h_a_min = std::max(lo, pair_h - hi);
const double h_a_max = std::min(hi, pair_h - lo);
if (h_a_min > h_a_max + EPSILON)
continue;
const double h_a = std::clamp(pair_h * ratio_a, h_a_min, h_a_max);
const double h_b = pair_h - h_a;
std::vector<double> out;
out.reserve(pass_count);
@@ -1103,13 +1150,68 @@ static std::vector<double> build_local_z_alternating_pass_heights(double base_he
out.emplace_back(h_a);
out.emplace_back(h_b);
}
if (fit_pass_heights_to_interval(out, base_height, lo, hi))
return out;
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
continue;
const double ratio_actual = (h_a + h_b > EPSILON) ? (h_a / (h_a + h_b)) : 0.5;
const double ratio_error = std::abs(ratio_actual - ratio_a);
const size_t pass_distance =
(pass_count > target_passes) ? (pass_count - target_passes) : (target_passes - pass_count);
const double max_height = std::max(h_a, h_b);
const bool better_ratio = !has_best || (ratio_error + 1e-6 < best_ratio_error);
const bool similar_ratio = has_best && std::abs(ratio_error - best_ratio_error) <= 1e-6;
const bool better_distance = similar_ratio && (pass_distance < best_pass_distance);
const bool similar_distance = similar_ratio && (pass_distance == best_pass_distance);
const bool better_max_height = similar_distance && (max_height + 1e-6 < best_max_height);
const bool similar_max_height = similar_distance && std::abs(max_height - best_max_height) <= 1e-6;
const bool better_pass_count = similar_max_height && (pass_count > best_pass_count);
if (better_ratio || better_distance || better_max_height || better_pass_count) {
has_best = true;
best_passes = std::move(out);
best_ratio_error = ratio_error;
best_pass_distance = pass_distance;
best_max_height = max_height;
best_pass_count = pass_count;
}
}
if (has_best)
return best_passes;
return build_uniform_local_z_pass_heights(base_height, lo, hi);
}
static std::vector<double> build_local_z_shared_pass_heights(double base_height, double lower_bound, double upper_bound)
{
if (base_height <= EPSILON)
return {};
const double lo = std::max<double>(0.01, lower_bound);
const double hi = std::max<double>(lo, upper_bound);
if (base_height < 2.0 * lo - EPSILON)
return { base_height };
// In shared (dense multi-zone) mode keep a single pair of pass planes for
// the whole nominal layer, anchored to the configured lower / upper bounds.
double h_small = lo;
double h_large = base_height - h_small;
if (h_large > hi + EPSILON) {
h_large = hi;
h_small = base_height - h_large;
}
if (h_small < lo - EPSILON || h_small > hi + EPSILON ||
h_large < lo - EPSILON || h_large > hi + EPSILON)
return build_uniform_local_z_pass_heights(base_height, lo, hi);
std::vector<double> out { h_small, h_large };
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
return build_uniform_local_z_pass_heights(base_height, lo, hi);
if (out.size() == 2 && out[0] > out[1])
std::swap(out[0], out[1]);
return out;
}
static std::vector<double> build_local_z_pass_heights(double base_height,
double lower_bound,
double upper_bound,
@@ -1360,10 +1462,11 @@ static std::vector<unsigned int> pointillism_sequence_for_row(const MixedFilamen
static bool local_z_eligible_mixed_row(const MixedFilament &mf)
{
// Local-Z flow-height modulation is intended for custom gradient rows.
// Keep auto-generated premix rows and manual pattern rows on nominal layers.
// Local-Z flow-height modulation should apply to all mixed rows that are
// resolved as A/B blends on model surfaces, not only custom rows.
// Exclude explicit manual patterns and same-layer pointillism rows, which
// have their own distribution semantics.
return mf.enabled &&
mf.custom &&
mf.manual_pattern.empty() &&
mf.distribution_mode != int(MixedFilament::SameLayerPointillisme);
}
@@ -1831,7 +1934,6 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
<< " object=" << object_name;
return;
}
coordf_t mixed_lower = float_from_full_config(full_cfg, "mixed_filament_height_lower_bound",
coordf_t(print_cfg.mixed_filament_height_lower_bound.value));
coordf_t mixed_upper = float_from_full_config(full_cfg, "mixed_filament_height_upper_bound",
@@ -1892,12 +1994,16 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
size_t split_intervals_without_painted_masks = 0;
size_t strict_ab_assignments = 0;
size_t alternating_height_intervals = 0;
// Preserve per-row local-Z phase across layers so pass orientation does not
// flip when a different mixed row dominates layer pass heights.
std::vector<uint8_t> row_next_component_a(mixed_rows.size(), uint8_t(1));
std::vector<uint8_t> row_phase_initialized(mixed_rows.size(), uint8_t(0));
int cadence_index = 0;
size_t shared_multi_row_fallback_intervals = 0;
constexpr size_t LOCAL_Z_MAX_ISOLATED_ACTIVE_ROWS = 2;
constexpr size_t LOCAL_Z_MAX_ISOLATED_MASK_COMPONENTS = 24;
constexpr size_t LOCAL_Z_MAX_ISOLATED_MASK_VERTICES = 1200;
constexpr bool LOCAL_Z_SHARED_FALLBACK_ENABLED = false;
// Keep local-Z cadence isolated per mixed row so independent painted zones
// do not influence each other when resolving fallback cadence.
std::vector<int> row_cadence_index(mixed_rows.size(), 0);
// Reset row cadence at the start of each disjoint painted zone.
std::vector<uint8_t> row_active_prev_layer(mixed_rows.size(), uint8_t(0));
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
throw_on_cancel();
@@ -1912,6 +2018,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
ExPolygons mixed_masks;
size_t mixed_state_count = 0;
std::vector<uint8_t> row_active_this_layer(mixed_rows.size(), uint8_t(0));
size_t dominant_mixed_idx = size_t(-1);
double dominant_mixed_area = -1.0;
double dominant_gradient_h_a = 0.0;
@@ -1933,6 +2040,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
continue;
interval.has_mixed_paint = true;
row_active_this_layer[size_t(mixed_idx)] = uint8_t(1);
++mixed_state_count;
append(mixed_masks, state_masks);
@@ -1947,6 +2055,11 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
dominant_gradient_h_a, dominant_gradient_h_b);
dominant_gradient_valid = true;
}
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) {
row_cadence_index[row_idx] = 0;
}
}
total_mixed_state_layers += mixed_state_count;
if (!mixed_masks.empty())
mixed_masks = union_ex(mixed_masks);
@@ -1963,18 +2076,101 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
}
}
const size_t active_mixed_rows = size_t(std::count(row_active_this_layer.begin(), row_active_this_layer.end(), uint8_t(1)));
std::vector<ExPolygons> row_state_masks(mixed_rows.size());
std::vector<unsigned int> row_state_ids(mixed_rows.size(), 0);
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
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);
if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx]))
continue;
row_state_ids[row_idx] = state_id;
append(row_state_masks[row_idx], state_masks);
}
for (ExPolygons &state_masks : row_state_masks)
if (state_masks.size() > 1)
state_masks = union_ex(state_masks);
size_t active_row_mask_components = 0;
size_t active_row_mask_vertices = 0;
for (size_t row_idx = 0; row_idx < row_state_masks.size(); ++row_idx)
if (row_active_this_layer[row_idx] != 0) {
active_row_mask_components += row_state_masks[row_idx].size();
for (const ExPolygon &expoly : row_state_masks[row_idx]) {
active_row_mask_vertices += expoly.contour.points.size();
for (const Polygon &hole : expoly.holes)
active_row_mask_vertices += hole.points.size();
}
}
std::vector<std::vector<double>> isolated_row_pass_heights(mixed_rows.size());
bool isolated_multi_row_mode = false;
const bool shared_multi_row_fallback =
LOCAL_Z_SHARED_FALLBACK_ENABLED &&
interval.has_mixed_paint &&
preferred_a <= EPSILON &&
preferred_b <= EPSILON &&
active_mixed_rows > 1 &&
(active_mixed_rows > LOCAL_Z_MAX_ISOLATED_ACTIVE_ROWS ||
active_row_mask_components > LOCAL_Z_MAX_ISOLATED_MASK_COMPONENTS ||
active_row_mask_vertices > LOCAL_Z_MAX_ISOLATED_MASK_VERTICES);
if (shared_multi_row_fallback)
++shared_multi_row_fallback_intervals;
if (interval.has_mixed_paint &&
preferred_a <= EPSILON &&
preferred_b <= EPSILON &&
!shared_multi_row_fallback &&
active_mixed_rows > 1) {
size_t isolated_rows_with_split = 0;
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
if (row_active_this_layer[row_idx] == 0)
continue;
double row_h_a = 0.0;
double row_h_b = 0.0;
compute_local_z_gradient_component_heights(mixed_rows[row_idx].mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
std::vector<double> row_passes = build_local_z_alternating_pass_heights(interval.base_height,
mixed_lower,
mixed_upper,
row_h_a,
row_h_b);
if (row_passes.empty())
row_passes.emplace_back(interval.base_height);
if (!sanitize_local_z_pass_heights(row_passes, interval.base_height, mixed_lower, mixed_upper))
row_passes = build_uniform_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper);
if (row_passes.size() > 1)
++isolated_rows_with_split;
isolated_row_pass_heights[row_idx] = std::move(row_passes);
}
if (isolated_rows_with_split > 0) {
isolated_multi_row_mode = true;
++alternating_height_intervals;
}
}
std::vector<double> pass_heights;
if (interval.has_mixed_paint) {
if (interval.has_mixed_paint && !isolated_multi_row_mode) {
// Local-Z mode should emit an A/B/A/B pattern for mixed regions and
// derive relative heights from mixed-filament gradient bounds.
if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
if (dominant_gradient_valid) {
if (shared_multi_row_fallback) {
pass_heights = build_local_z_shared_pass_heights(interval.base_height, mixed_lower, mixed_upper);
if (pass_heights.size() > 1)
++alternating_height_intervals;
} else if (dominant_gradient_valid) {
pass_heights = build_local_z_alternating_pass_heights(interval.base_height, mixed_lower, mixed_upper,
dominant_gradient_h_a, dominant_gradient_h_b);
if (pass_heights.size() > 1)
++alternating_height_intervals;
} else {
pass_heights = build_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper, preferred_a, preferred_b);
pass_heights = build_uniform_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper);
}
} else {
pass_heights = build_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper, preferred_a, preferred_b);
@@ -1983,6 +2179,11 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
else
pass_heights.emplace_back(interval.base_height);
if (interval.has_mixed_paint) {
if (!sanitize_local_z_pass_heights(pass_heights, interval.base_height, mixed_lower, mixed_upper))
pass_heights = build_uniform_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper);
}
// Keep auto local-Z 2-pass cadence order stable across layers even if the
// dominant mixed row changes. Per-row phase assignment still controls
// which filament gets pass-0 vs pass-1.
@@ -1994,136 +2195,253 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
std::swap(pass_heights[0], pass_heights[1]);
}
const bool split_interval = interval.has_mixed_paint && pass_heights.size() > 1;
size_t pass_count_for_log = pass_heights.size();
double pass_min_height_for_log = pass_heights.empty() ? 0.0 : *std::min_element(pass_heights.begin(), pass_heights.end());
double pass_max_height_for_log = pass_heights.empty() ? 0.0 : *std::max_element(pass_heights.begin(), pass_heights.end());
const bool split_interval = interval.has_mixed_paint && (isolated_multi_row_mode || pass_heights.size() > 1);
const bool force_height_resolve = true;
if (split_interval) {
// Derive per-row start phase against the selected layer pass heights so
// complementary mixed rows can coexist on the same nominal layer.
std::vector<uint8_t> start_with_component_a = row_next_component_a;
std::vector<uint8_t> row_seen_in_interval(mixed_rows.size(), uint8_t(0));
if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
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 MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (!local_z_eligible_mixed_row(mf))
continue;
row_seen_in_interval[size_t(mixed_idx)] = uint8_t(1);
if (row_phase_initialized[size_t(mixed_idx)] != 0)
continue;
double row_h_a = 0.0;
double row_h_b = 0.0;
compute_local_z_gradient_component_heights(mf.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
double err_ab = 0.0;
double err_ba = 0.0;
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
const double expected_ab = (pass_i % 2) == 0 ? row_h_a : row_h_b;
const double expected_ba = (pass_i % 2) == 0 ? row_h_b : row_h_a;
err_ab += std::abs(pass_heights[pass_i] - expected_ab);
err_ba += std::abs(pass_heights[pass_i] - expected_ba);
}
if (err_ba + 1e-6 < err_ab)
start_with_component_a[size_t(mixed_idx)] = uint8_t(0);
}
}
++split_intervals;
double z_cursor = interval.z_lo;
size_t pass_idx = 0;
bool interval_has_split_painted_masks = false;
interval.sublayer_height = *std::min_element(pass_heights.begin(), pass_heights.end());
for (const double pass_height_nominal : pass_heights) {
if (z_cursor >= interval.z_hi - EPSILON)
break;
const double pass_height = std::min<double>(pass_height_nominal, interval.z_hi - z_cursor);
const double z_next = std::min<double>(interval.z_hi, z_cursor + pass_height);
if (isolated_multi_row_mode) {
std::vector<SubLayerPlan> isolated_plans;
isolated_plans.reserve(std::max<size_t>(2, active_mixed_rows * 2));
SubLayerPlan plan;
plan.layer_id = layer_id;
plan.pass_index = pass_idx;
plan.split_interval = true;
plan.z_lo = z_cursor;
plan.z_hi = z_next;
plan.print_z = z_next;
plan.flow_height = pass_height;
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
++split_passes_total;
bool pass_has_painted_masks = false;
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
if (row_active_this_layer[row_idx] == 0)
continue;
const ExPolygons &state_masks = row_state_masks[row_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
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 MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (!local_z_eligible_mixed_row(mf))
continue;
row_seen_in_interval[size_t(mixed_idx)] = uint8_t(1);
++forced_height_resolve_calls;
unsigned int target_extruder = 0;
if (mf.component_a > 0 && mf.component_a <= num_physical &&
mf.component_b > 0 && mf.component_b <= num_physical) {
const bool start_a = start_with_component_a[size_t(mixed_idx)] != 0;
const bool even_pass = (pass_idx % 2) == 0;
// Enforce strict per-pass alternation inside split local-Z intervals.
target_extruder = even_pass
? (start_a ? mf.component_a : mf.component_b)
: (start_a ? mf.component_b : mf.component_a);
++strict_ab_assignments;
const MixedFilament &mf = mixed_rows[row_idx];
const std::vector<double> &row_passes_raw = isolated_row_pass_heights[row_idx];
const std::vector<double> row_passes = row_passes_raw.empty()
? std::vector<double>{ interval.base_height }
: row_passes_raw;
const bool valid_pair = mf.component_a > 0 && mf.component_a <= num_physical &&
mf.component_b > 0 && mf.component_b <= num_physical;
bool start_with_a = true;
if (valid_pair && preferred_a <= EPSILON && preferred_b <= EPSILON) {
double row_h_a = 0.0;
double row_h_b = 0.0;
compute_local_z_gradient_component_heights(mf.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
double err_ab = 0.0;
double err_ba = 0.0;
for (size_t pass_i = 0; pass_i < row_passes.size(); ++pass_i) {
const double expected_ab = (pass_i % 2) == 0 ? row_h_a : row_h_b;
const double expected_ba = (pass_i % 2) == 0 ? row_h_b : row_h_a;
err_ab += std::abs(row_passes[pass_i] - expected_ab);
err_ba += std::abs(row_passes[pass_i] - expected_ba);
}
if (err_ba + 1e-6 < err_ab)
start_with_a = false;
}
if (target_extruder == 0) {
target_extruder = mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true);
double z_cursor = interval.z_lo;
for (size_t pass_i = 0; pass_i < row_passes.size(); ++pass_i) {
if (z_cursor >= interval.z_hi - EPSILON)
break;
const double pass_height = std::min<double>(row_passes[pass_i], interval.z_hi - z_cursor);
if (pass_height <= EPSILON)
continue;
const double z_next = std::min<double>(interval.z_hi, z_cursor + pass_height);
SubLayerPlan plan;
plan.layer_id = layer_id;
plan.pass_index = isolated_plans.size();
plan.split_interval = true;
plan.z_lo = z_cursor;
plan.z_hi = z_next;
plan.print_z = z_next;
plan.flow_height = pass_height;
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
++split_passes_total;
++forced_height_resolve_calls;
unsigned int target_extruder = 0;
if (valid_pair) {
const bool even_pass = (pass_i % 2) == 0;
target_extruder = even_pass
? (start_with_a ? mf.component_a : mf.component_b)
: (start_with_a ? mf.component_b : mf.component_a);
++strict_ab_assignments;
}
if (target_extruder == 0) {
const unsigned int state_id = row_state_ids[row_idx];
if (state_id != 0) {
target_extruder = mixed_mgr.resolve(state_id,
num_physical,
row_cadence_index[row_idx],
float(plan.print_z),
float(plan.flow_height),
force_height_resolve);
}
}
if (target_extruder == 0 || target_extruder > num_physical) {
++forced_height_resolve_invalid_target;
} else {
append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks);
++split_passes_with_painted_masks;
interval_has_split_painted_masks = true;
}
isolated_plans.emplace_back(std::move(plan));
++row_cadence_index[row_idx];
z_cursor = z_next;
}
if (target_extruder == 0 || target_extruder > num_physical) {
++forced_height_resolve_invalid_target;
continue;
}
append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks);
pass_has_painted_masks = true;
}
for (ExPolygons &masks : plan.painted_masks_by_extruder)
if (masks.size() > 1)
masks = union_ex(masks);
if (pass_has_painted_masks) {
++split_passes_with_painted_masks;
interval_has_split_painted_masks = true;
}
if (z_next >= interval.z_hi - EPSILON)
plan.base_masks = base_masks;
if (!isolated_plans.empty()) {
std::sort(isolated_plans.begin(), isolated_plans.end(), [](const SubLayerPlan &lhs, const SubLayerPlan &rhs) {
if (std::abs(lhs.print_z - rhs.print_z) > EPSILON)
return lhs.print_z < rhs.print_z;
if (std::abs(lhs.z_lo - rhs.z_lo) > EPSILON)
return lhs.z_lo < rhs.z_lo;
return lhs.pass_index < rhs.pass_index;
});
double min_flow_height = isolated_plans.front().flow_height;
double max_flow_height = isolated_plans.front().flow_height;
for (size_t idx = 0; idx < isolated_plans.size(); ++idx) {
isolated_plans[idx].pass_index = idx;
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);
}
isolated_plans.back().base_masks = base_masks;
interval.sublayer_height = min_flow_height;
pass_count_for_log = isolated_plans.size();
pass_min_height_for_log = min_flow_height;
pass_max_height_for_log = max_flow_height;
for (SubLayerPlan &plan : isolated_plans) {
plans.emplace_back(std::move(plan));
++interval.sublayer_count;
++total_generated_sublayer_cnt;
}
}
} else {
// Derive per-row orientation against pass heights so each mixed row
// maps thicker/thinner subpasses to the intended component.
std::vector<uint8_t> start_with_component_a(mixed_rows.size(), uint8_t(1));
if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
plans.emplace_back(std::move(plan));
++interval.sublayer_count;
++total_generated_sublayer_cnt;
++pass_idx;
++cadence_index;
z_cursor = z_next;
}
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
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 MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (!local_z_eligible_mixed_row(mf))
continue;
if (pass_idx > 0) {
const bool toggle_after_interval = (pass_idx % 2) != 0;
for (size_t mixed_idx = 0; mixed_idx < mixed_rows.size(); ++mixed_idx) {
if (row_seen_in_interval[mixed_idx] == 0)
continue;
const bool start_a = start_with_component_a[mixed_idx] != 0;
const bool next_a = toggle_after_interval ? !start_a : start_a;
row_next_component_a[mixed_idx] = next_a ? uint8_t(1) : uint8_t(0);
row_phase_initialized[mixed_idx] = uint8_t(1);
double row_h_a = 0.0;
double row_h_b = 0.0;
compute_local_z_gradient_component_heights(mf.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
double err_ab = 0.0;
double err_ba = 0.0;
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
const double expected_ab = (pass_i % 2) == 0 ? row_h_a : row_h_b;
const double expected_ba = (pass_i % 2) == 0 ? row_h_b : row_h_a;
err_ab += std::abs(pass_heights[pass_i] - expected_ab);
err_ba += std::abs(pass_heights[pass_i] - expected_ba);
}
if (err_ba + 1e-6 < err_ab)
start_with_component_a[size_t(mixed_idx)] = uint8_t(0);
}
}
double z_cursor = interval.z_lo;
size_t pass_idx = 0;
interval.sublayer_height = *std::min_element(pass_heights.begin(), pass_heights.end());
for (const double pass_height_nominal : pass_heights) {
if (z_cursor >= interval.z_hi - EPSILON)
break;
const double pass_height = std::min<double>(pass_height_nominal, interval.z_hi - z_cursor);
const double z_next = std::min<double>(interval.z_hi, z_cursor + pass_height);
SubLayerPlan plan;
plan.layer_id = layer_id;
plan.pass_index = pass_idx;
plan.split_interval = true;
plan.z_lo = z_cursor;
plan.z_hi = z_next;
plan.print_z = z_next;
plan.flow_height = pass_height;
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
++split_passes_total;
bool pass_has_painted_masks = false;
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) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
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];
if (!local_z_eligible_mixed_row(mf))
continue;
row_seen_in_pass[row_idx] = uint8_t(1);
++forced_height_resolve_calls;
unsigned int target_extruder = 0;
if (mf.component_a > 0 && mf.component_a <= num_physical &&
mf.component_b > 0 && mf.component_b <= num_physical) {
const bool start_a = start_with_component_a[row_idx] != 0;
const bool even_pass = (pass_idx % 2) == 0;
// Local-Z mode alternates A/B on every subpass.
target_extruder = even_pass
? (start_a ? mf.component_a : mf.component_b)
: (start_a ? mf.component_b : mf.component_a);
++strict_ab_assignments;
}
if (target_extruder == 0) {
target_extruder = mixed_mgr.resolve(state_id,
num_physical,
row_cadence_index[row_idx],
float(plan.print_z),
float(plan.flow_height),
force_height_resolve);
}
if (target_extruder == 0 || target_extruder > num_physical) {
++forced_height_resolve_invalid_target;
continue;
}
append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks);
pass_has_painted_masks = true;
}
for (ExPolygons &masks : plan.painted_masks_by_extruder)
if (masks.size() > 1)
masks = union_ex(masks);
if (pass_has_painted_masks) {
++split_passes_with_painted_masks;
interval_has_split_painted_masks = true;
}
if (z_next >= interval.z_hi - EPSILON)
plan.base_masks = base_masks;
plans.emplace_back(std::move(plan));
++interval.sublayer_count;
++total_generated_sublayer_cnt;
++pass_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_cadence_index[mixed_idx];
z_cursor = z_next;
}
}
if (!interval_has_split_painted_masks)
@@ -2141,6 +2459,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
plan.flow_height = interval.base_height;
plan.base_masks = base_masks;
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
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) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
@@ -2150,12 +2469,22 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
const MixedFilament *mixed_row = mixed_mgr.mixed_filament_from_id(state_id, num_physical);
if (mixed_row == nullptr || !local_z_eligible_mixed_row(*mixed_row))
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];
if (!local_z_eligible_mixed_row(mixed_row))
continue;
row_seen_in_interval[row_idx] = uint8_t(1);
++forced_height_resolve_calls;
const unsigned int target_extruder =
mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true);
mixed_mgr.resolve(state_id,
num_physical,
row_cadence_index[row_idx],
float(plan.print_z),
float(plan.flow_height),
force_height_resolve);
if (target_extruder == 0 || target_extruder > num_physical) {
++forced_height_resolve_invalid_target;
continue;
@@ -2169,7 +2498,9 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
plans.emplace_back(std::move(plan));
interval.sublayer_count = 1;
++total_generated_sublayer_cnt;
++cadence_index;
for (size_t mixed_idx = 0; mixed_idx < row_seen_in_interval.size(); ++mixed_idx)
if (row_seen_in_interval[mixed_idx] != 0)
++row_cadence_index[mixed_idx];
}
if (interval.has_mixed_paint) {
@@ -2178,16 +2509,20 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
<< " layer_id=" << layer_id
<< " base_height=" << interval.base_height
<< " split=" << split_interval
<< " isolated_multi_row_mode=" << (isolated_multi_row_mode ? 1 : 0)
<< " shared_multi_row_fallback=" << (shared_multi_row_fallback ? 1 : 0)
<< " active_mixed_rows=" << active_mixed_rows
<< " active_row_mask_components=" << active_row_mask_components
<< " active_row_mask_vertices=" << active_row_mask_vertices
<< " mixed_states=" << mixed_state_count
<< " pass_count=" << pass_heights.size()
<< " pass_min_height="
<< (pass_heights.empty() ? 0.0 : *std::min_element(pass_heights.begin(), pass_heights.end()))
<< " pass_max_height="
<< (pass_heights.empty() ? 0.0 : *std::max_element(pass_heights.begin(), pass_heights.end()))
<< " pass_count=" << pass_count_for_log
<< " pass_min_height=" << pass_min_height_for_log
<< " pass_max_height=" << pass_max_height_for_log
<< " mixed_mask_count=" << mixed_masks.size()
<< " base_mask_count=" << base_masks.size();
}
row_active_prev_layer = row_active_this_layer;
intervals.emplace_back(std::move(interval));
}
@@ -2204,6 +2539,10 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
<< " split_passes_total=" << split_passes_total
<< " split_passes_with_painted_masks=" << split_passes_with_painted_masks
<< " alternating_height_intervals=" << alternating_height_intervals
<< " shared_multi_row_fallback_intervals=" << shared_multi_row_fallback_intervals
<< " max_isolated_active_rows=" << LOCAL_Z_MAX_ISOLATED_ACTIVE_ROWS
<< " max_isolated_mask_components=" << LOCAL_Z_MAX_ISOLATED_MASK_COMPONENTS
<< " max_isolated_mask_vertices=" << LOCAL_Z_MAX_ISOLATED_MASK_VERTICES
<< " strict_ab_assignments=" << strict_ab_assignments
<< " mixed_state_layers=" << total_mixed_state_layers
<< " forced_height_resolve_calls=" << forced_height_resolve_calls

View File

@@ -210,8 +210,12 @@ void CopyrightsDialog::onCloseDialog(wxEvent &)
}
AboutDialog::AboutDialog()
: DPIDialog(static_cast<wxWindow *>(wxGetApp().mainframe),wxID_ANY,from_u8((boost::format(_utf8(L("About %s"))) % (wxGetApp().is_editor() ? SLIC3R_APP_FULL_NAME : GCODEVIEWER_APP_NAME)).str()),wxDefaultPosition,
wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE)
: DPIDialog(static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY,
wxString::Format(_L("About %s"), _L("Snapmaker Orca Full Spectrum")),
wxDefaultPosition,
wxDefaultSize,
/*wxCAPTION*/wxDEFAULT_DIALOG_STYLE)
{
SetFont(wxGetApp().normal_font());
SetBackgroundColour(*wxWHITE);
@@ -242,7 +246,7 @@ AboutDialog::AboutDialog()
// version
{
vesizer->Add(0, FromDIP(165), 1, wxEXPAND, FromDIP(5));
auto version_string = _L("Snapmaker Orca ") + " " + std::string(Snapmaker_VERSION);
auto version_string = _L("Snapmaker Orca Full Spectrum") + " " + std::string(Snapmaker_VERSION);
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
wxStaticText* bs_version = new wxStaticText(this, wxID_ANY, wxString::Format("Based on Orca Slicer"), wxDefaultPosition, wxDefaultSize);
bs_version->SetFont(Label::Body_12);
@@ -350,7 +354,11 @@ AboutDialog::AboutDialog()
(boost::format(
"<html>"
"<body>"
"<p style=\"text-align:left\"><a style=\"color:#009789\" href=\"www.snapmaker.com\">www.snapmaker.com</ a></p>"
"<p style=\"text-align:left\">"
"<a style=\"color:#009789\" href=\"https://www.snapmaker.com\">www.snapmaker.com</a>"
"&nbsp;&nbsp;|&nbsp;&nbsp;"
"<a style=\"color:#009789\" href=\"https://github.com/ratdoux/OrcaSlicer-FullSpectrum\">GitHub</a>"
"</p>"
"</body>"
"</html>")
).str());