mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-24 17:26:47 +00:00
Add changelog for v0.7-alpha pre-release: Introduce experimental features including Local Z reliability improvements, manual pattern mixed filaments, selective expansion/contraction controls, and a refreshed UI for mixed filaments. Enhance configuration options with surface indentation for mixed filament behavior tuning. Update PrintConfig and GUI components to support new features and improve user experience.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
## v0.7-alpha Pre-release
|
||||
|
||||
EXPERIMENTAL BUILD - NOT TESTED
|
||||
Based on Snapmaker Orca v2.2.4
|
||||
|
||||
v0.7 full spectrum alpha focuses on Local Z reliability, better mixed-filament control, and a cleaner custom Gradient/Pattern workflow.
|
||||
|
||||
This has had limited testing on the Snapmaker U1 via the community.
|
||||
|
||||
### What's New in v0.7
|
||||
|
||||
#### Local Z Dithering Reliability (v0.7)
|
||||
- Improved handling for overlapping mixed-painted zones on the same XY plane.
|
||||
- Fixed cases where layer-height modulation could flatten or become equal when a second zone starts.
|
||||
- Fixed stale first-zone behavior where editing one gradient did not correctly update resulting layer heights.
|
||||
- Reduced repeated same-color layer runs in complex multi-zone transitions.
|
||||
- Mixed passes and base passes are separated more cleanly in perimeter clipping paths.
|
||||
|
||||
#### Manual Pattern Mixed Filaments (v0.6, refined in v0.7)
|
||||
- Pattern add button in Mixed Filaments UI (alongside Gradient add).
|
||||
- Custom repeating layer patterns (for example `11112222` for a 4:4 ratio).
|
||||
- Pattern parser accepts `1/2` or `A/B` with separators (`/`, `-`, `_`, `|`, etc.).
|
||||
- Manual pattern takes precedence; blend percentage is auto-derived from the pattern.
|
||||
- Backward-compatible parsing for older custom mixed-row formats.
|
||||
|
||||
#### Local Z Dithering Mode (v0.4, refined in v0.7)
|
||||
- Local Z dithering mode in Process -> Others (`dithering_local_z_mode`).
|
||||
- Mixed-painted zones are split into local sub-passes with pass-specific flow height.
|
||||
- Runtime guard remains active when wipe tower or wiping overrides are enabled.
|
||||
|
||||
#### Selective Expansion/Contraction (v0.7)
|
||||
- Added selective expansion/contraction controls for mixed-color behavior tuning in challenging transition regions.
|
||||
- Intended to help with edge consistency where mixed passes interact with neighboring toolpaths.
|
||||
|
||||
#### Mixed Filaments UI Refresh (v0.7)
|
||||
- More modern and compact layout for custom Gradient and Pattern mixed filaments.
|
||||
- Reduced spacing and tighter row cards for better readability with many mixed rows.
|
||||
- Cleaner preview area with summary inline for faster scanning.
|
||||
|
||||
#### Config/Save/Load Reliability (v0.5 + v0.7 hardening)
|
||||
- Config comparison checks type and value.
|
||||
- Improved dirty-state detection, diffing, and unsaved-changes behavior.
|
||||
- Mixed filament project settings load more consistently.
|
||||
- Stronger logging for mixed-row parsing and config application.
|
||||
|
||||
#### Same-layer Pointillisme (new in v0.7, experimental)
|
||||
- What it is: a same-layer color interleaving mode that tries to distribute multiple filaments inside a single layer instead of alternating by layer.
|
||||
- Goal: test whether very small in-layer color "dots" can produce smoother visual blends than normal layer cycling.
|
||||
- Status: extremely experimental and may produce unusable results.
|
||||
- Reality check: this is likely a dead end for production use on current hardware and may be removed or replaced in a future release.
|
||||
|
||||
### Installation
|
||||
1. Download `Snapmaker_Orca.zip`.
|
||||
2. Extract to a folder.
|
||||
3. Run the executable.
|
||||
|
||||
### Warning
|
||||
- Use at your own risk.
|
||||
- May produce incorrect G-code.
|
||||
- Local Z behavior is still experimental.
|
||||
- Seeking testers with U1 printers.
|
||||
|
||||
### Features Not Yet Tested
|
||||
1. Local Z height dithering
|
||||
2. Patterns
|
||||
3. Selective expansion/contraction
|
||||
4. Advanced dithering
|
||||
5. Height-weight cadence
|
||||
|
||||
### Known Issues
|
||||
- Untested on real hardware.
|
||||
- Local Z mode still needs validation with real prints.
|
||||
- Advanced dithering output may require calibration.
|
||||
- Pointillisme is research-grade only right now and is likely not viable for stable, repeatable prints.
|
||||
- Startup crash may still occur in some setups and needs further debugging.
|
||||
@@ -47,6 +47,7 @@ static std::vector<std::string> s_project_options {
|
||||
"mixed_filament_height_upper_bound",
|
||||
"mixed_filament_cycle_layers",
|
||||
"mixed_filament_advanced_dithering",
|
||||
"mixed_filament_surface_indentation",
|
||||
"mixed_filament_definitions",
|
||||
"mixed_color_layer_height_a",
|
||||
"mixed_color_layer_height_b",
|
||||
|
||||
@@ -253,6 +253,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "mixed_filament_height_upper_bound"
|
||||
|| opt_key == "mixed_filament_cycle_layers"
|
||||
|| opt_key == "mixed_filament_advanced_dithering"
|
||||
|| opt_key == "mixed_filament_surface_indentation"
|
||||
|| opt_key == "mixed_filament_definitions"
|
||||
// Spiral Vase forces different kind of slicing than the normal model:
|
||||
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
||||
|
||||
@@ -1177,6 +1177,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
new_full_config.option("mixed_filament_advanced_dithering", true);
|
||||
new_full_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||
new_full_config.option("mixed_filament_pointillism_line_gap", true);
|
||||
new_full_config.option("mixed_filament_surface_indentation", true);
|
||||
new_full_config.option("mixed_filament_definitions", true);
|
||||
m_config.option("dithering_z_step_size", true);
|
||||
m_config.option("dithering_local_z_mode", true);
|
||||
@@ -1188,6 +1189,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_config.option("mixed_filament_advanced_dithering", true);
|
||||
m_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||
m_config.option("mixed_filament_pointillism_line_gap", true);
|
||||
m_config.option("mixed_filament_surface_indentation", true);
|
||||
m_config.option("mixed_filament_definitions", true);
|
||||
m_default_object_config.option("dithering_z_step_size", true);
|
||||
m_default_object_config.option("dithering_local_z_mode", true);
|
||||
@@ -1199,6 +1201,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_default_object_config.option("mixed_filament_advanced_dithering", true);
|
||||
m_default_object_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||
m_default_object_config.option("mixed_filament_pointillism_line_gap", true);
|
||||
m_default_object_config.option("mixed_filament_surface_indentation", true);
|
||||
m_default_object_config.option("mixed_filament_definitions", true);
|
||||
// BBS
|
||||
int used_filaments = this->extruders(true).size();
|
||||
@@ -1304,6 +1307,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
bool mixed_advanced_dither = false;
|
||||
float mixed_pointillism_pixel_size = 0.f;
|
||||
float mixed_pointillism_line_gap = 0.f;
|
||||
float mixed_surface_indentation = 0.f;
|
||||
std::string mixed_custom_definitions;
|
||||
if (new_full_config.has("mixed_filament_gradient_mode")) {
|
||||
if (const ConfigOptionBool *opt = new_full_config.option<ConfigOptionBool>("mixed_filament_gradient_mode"))
|
||||
@@ -1327,6 +1331,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
mixed_pointillism_pixel_size = float(new_full_config.opt_float("mixed_filament_pointillism_pixel_size"));
|
||||
if (new_full_config.has("mixed_filament_pointillism_line_gap"))
|
||||
mixed_pointillism_line_gap = float(new_full_config.opt_float("mixed_filament_pointillism_line_gap"));
|
||||
if (new_full_config.has("mixed_filament_surface_indentation"))
|
||||
mixed_surface_indentation = float(new_full_config.opt_float("mixed_filament_surface_indentation"));
|
||||
if (new_full_config.has("mixed_filament_definitions"))
|
||||
mixed_custom_definitions = new_full_config.opt_string("mixed_filament_definitions");
|
||||
|
||||
@@ -1336,6 +1342,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
mixed_cycle_layers = std::max(2, mixed_cycle_layers);
|
||||
mixed_pointillism_pixel_size = std::max(0.f, mixed_pointillism_pixel_size);
|
||||
mixed_pointillism_line_gap = std::max(0.f, mixed_pointillism_line_gap);
|
||||
mixed_surface_indentation = std::clamp(mixed_surface_indentation, -2.f, 2.f);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Print::apply mixed settings"
|
||||
<< ", gradient_mode=" << mixed_gradient_mode
|
||||
@@ -1345,6 +1352,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
<< ", advanced_dither=" << (mixed_advanced_dither ? 1 : 0)
|
||||
<< ", pointillism_pixel_size=" << mixed_pointillism_pixel_size
|
||||
<< ", pointillism_line_gap=" << mixed_pointillism_line_gap
|
||||
<< ", surface_indentation=" << mixed_surface_indentation
|
||||
<< ", custom_definitions_len=" << mixed_custom_definitions.size()
|
||||
<< ", physical_extruders=" << num_extruders;
|
||||
|
||||
|
||||
@@ -4198,7 +4198,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def->label = L("Pointillisme pixel size");
|
||||
def->category = L("Others");
|
||||
def->tooltip = L("Length of one pointillisme segment along an extrusion path for same-layer pointillisme mode. "
|
||||
"Set to 0 to use automatic nozzle-based sizing.");
|
||||
"Set to 0 to use automatic nozzle-based sizing.\n\n"
|
||||
"Warning: Same-layer pointillisme is extremely experimental and may produce unusable results.");
|
||||
def->sidetext = "mm";
|
||||
def->min = 0.;
|
||||
def->mode = comAdvanced;
|
||||
@@ -4208,12 +4209,25 @@ void PrintConfigDef::init_fff_params()
|
||||
def->label = L("Pointillisme line gap");
|
||||
def->category = L("Others");
|
||||
def->tooltip = L("Optional non-extruded spacing between adjacent pointillisme segments. "
|
||||
"Increase carefully to improve separation and print quality.");
|
||||
"Increase carefully to improve separation and print quality.\n\n"
|
||||
"Warning: Same-layer pointillisme is extremely experimental and may produce unusable results.");
|
||||
def->sidetext = "mm";
|
||||
def->min = 0.;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.0));
|
||||
|
||||
def = this->add("mixed_filament_surface_indentation", coFloat);
|
||||
def->label = L("Selective Expansion contraction");
|
||||
def->category = L("Others");
|
||||
def->tooltip = L("XY offset applied to mixed-filament painted regions before region assignment.\n\n"
|
||||
"Positive values contract the mixed zone inward. Negative values expand it outward.\n\n"
|
||||
"This applies to mixed filament usage in layer cadence, height cadence, same-layer pointillisme, and local Z dithering.");
|
||||
def->sidetext = "mm";
|
||||
def->min = -2.0;
|
||||
def->max = 2.0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.0));
|
||||
|
||||
def = this->add("mixed_filament_definitions", coString);
|
||||
def->label = L("Mixed filament custom definitions");
|
||||
def->tooltip = L("Serialized custom mixed filament rows.\n\n"
|
||||
|
||||
@@ -1360,6 +1360,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionBool, mixed_filament_advanced_dithering))
|
||||
((ConfigOptionFloat, mixed_filament_pointillism_pixel_size))
|
||||
((ConfigOptionFloat, mixed_filament_pointillism_line_gap))
|
||||
((ConfigOptionFloat, mixed_filament_surface_indentation))
|
||||
((ConfigOptionString, mixed_filament_definitions))
|
||||
((ConfigOptionFloat, dithering_z_step_size))
|
||||
((ConfigOptionBool, dithering_local_z_mode))
|
||||
|
||||
@@ -973,6 +973,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "mixed_filament_height_upper_bound"
|
||||
|| opt_key == "mixed_filament_cycle_layers"
|
||||
|| opt_key == "mixed_filament_advanced_dithering"
|
||||
|| opt_key == "mixed_filament_surface_indentation"
|
||||
|| opt_key == "mixed_filament_definitions") {
|
||||
// Mixed filament gradient controls affect layer cadence and virtual
|
||||
// tool distribution, so force a re-slice prompt like other
|
||||
|
||||
@@ -871,6 +871,109 @@ static coordf_t float_from_full_config(const DynamicPrintConfig &full_cfg, const
|
||||
return coordf_t(full_cfg.opt_float(key));
|
||||
}
|
||||
|
||||
static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation)
|
||||
{
|
||||
const Print *print = print_object.print();
|
||||
if (print == nullptr || segmentation.empty())
|
||||
return false;
|
||||
|
||||
const PrintConfig &print_cfg = print->config();
|
||||
const DynamicPrintConfig &full_cfg = print->full_print_config();
|
||||
coordf_t indentation_mm = float_from_full_config(full_cfg, "mixed_filament_surface_indentation",
|
||||
coordf_t(print_cfg.mixed_filament_surface_indentation.value));
|
||||
indentation_mm = std::clamp(indentation_mm, coordf_t(-2.f), coordf_t(2.f));
|
||||
if (std::abs(indentation_mm) <= EPSILON)
|
||||
return false;
|
||||
|
||||
const size_t num_physical = print_cfg.filament_colour.size();
|
||||
const size_t num_channels = segmentation.front().size();
|
||||
if (num_channels <= num_physical)
|
||||
return false;
|
||||
|
||||
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
||||
const bool expand_outward = indentation_mm < 0.f;
|
||||
const float delta_scaled = float(scale_(std::abs(double(indentation_mm))));
|
||||
if (delta_scaled <= float(EPSILON))
|
||||
return false;
|
||||
|
||||
size_t changed_layers = 0;
|
||||
size_t changed_states = 0;
|
||||
size_t emptied_states = 0;
|
||||
size_t overlap_clipped_states = 0;
|
||||
|
||||
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
|
||||
if (segmentation[layer_id].size() != num_channels)
|
||||
continue;
|
||||
|
||||
bool layer_changed = false;
|
||||
ExPolygons occupied;
|
||||
if (expand_outward) {
|
||||
for (size_t channel_idx = 0; channel_idx < num_channels; ++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))
|
||||
append(occupied, state_masks);
|
||||
}
|
||||
if (occupied.size() > 1)
|
||||
occupied = union_ex(occupied);
|
||||
}
|
||||
|
||||
for (size_t channel_idx = num_physical; channel_idx < num_channels; ++channel_idx) {
|
||||
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;
|
||||
|
||||
ExPolygons adjusted = offset_ex(state_masks, expand_outward ? delta_scaled : -delta_scaled);
|
||||
if (!adjusted.empty() && adjusted.size() > 1)
|
||||
adjusted = union_ex(adjusted);
|
||||
|
||||
if (expand_outward && !adjusted.empty() && !occupied.empty()) {
|
||||
ExPolygons clipped = diff_ex(adjusted, occupied, ApplySafetyOffset::Yes);
|
||||
if (std::abs(area(clipped)) + EPSILON < std::abs(area(adjusted)))
|
||||
++overlap_clipped_states;
|
||||
adjusted = std::move(clipped);
|
||||
if (!adjusted.empty() && adjusted.size() > 1)
|
||||
adjusted = union_ex(adjusted);
|
||||
}
|
||||
|
||||
state_masks = std::move(adjusted);
|
||||
if (state_masks.empty())
|
||||
++emptied_states;
|
||||
++changed_states;
|
||||
layer_changed = true;
|
||||
|
||||
if (expand_outward && !state_masks.empty()) {
|
||||
append(occupied, state_masks);
|
||||
if (occupied.size() > 1)
|
||||
occupied = union_ex(occupied);
|
||||
}
|
||||
}
|
||||
|
||||
if (layer_changed)
|
||||
++changed_layers;
|
||||
}
|
||||
|
||||
if (changed_states == 0)
|
||||
return false;
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Mixed surface indentation applied"
|
||||
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
||||
<< " indentation_mm=" << indentation_mm
|
||||
<< " direction=" << (expand_outward ? "outward" : "inward")
|
||||
<< " changed_layers=" << changed_layers
|
||||
<< " changed_states=" << changed_states
|
||||
<< " emptied_states=" << emptied_states
|
||||
<< " overlap_clipped_states=" << overlap_clipped_states;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fit_pass_heights_to_interval(std::vector<double> &passes, double base_height, double lo, double hi)
|
||||
{
|
||||
if (passes.empty() || base_height <= EPSILON)
|
||||
@@ -1255,6 +1358,16 @@ static std::vector<unsigned int> pointillism_sequence_for_row(const MixedFilamen
|
||||
return sequence;
|
||||
}
|
||||
|
||||
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.
|
||||
return mf.enabled &&
|
||||
mf.custom &&
|
||||
mf.manual_pattern.empty() &&
|
||||
mf.distribution_mode != int(MixedFilament::SameLayerPointillisme);
|
||||
}
|
||||
|
||||
static size_t unique_extruder_count(const std::vector<unsigned int> &sequence, size_t num_physical)
|
||||
{
|
||||
if (sequence.empty() || num_physical == 0)
|
||||
@@ -1773,20 +1886,16 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
size_t total_generated_sublayer_cnt = 0;
|
||||
size_t total_mixed_state_layers = 0;
|
||||
size_t forced_height_resolve_calls = 0;
|
||||
size_t forced_height_resolve_non_custom_calls = 0;
|
||||
size_t forced_height_resolve_invalid_target = 0;
|
||||
size_t split_passes_total = 0;
|
||||
size_t split_passes_with_painted_masks = 0;
|
||||
size_t split_intervals_without_painted_masks = 0;
|
||||
size_t strict_ab_assignments = 0;
|
||||
size_t alternating_height_intervals = 0;
|
||||
size_t gradient_lock_mismatch_layers = 0;
|
||||
size_t gradient_lock_unset_mixed_layers = 0;
|
||||
size_t locked_gradient_source_layer = size_t(-1);
|
||||
size_t locked_gradient_mixed_idx = size_t(-1);
|
||||
double locked_gradient_h_a = 0.0;
|
||||
double locked_gradient_h_b = 0.0;
|
||||
bool locked_gradient_valid = false;
|
||||
// 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;
|
||||
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
|
||||
@@ -1813,16 +1922,24 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
if (state_masks.empty())
|
||||
continue;
|
||||
const unsigned int state_id = unsigned(channel_idx + 1);
|
||||
if (mixed_mgr.is_mixed(state_id, num_physical)) {
|
||||
interval.has_mixed_paint = true;
|
||||
++mixed_state_count;
|
||||
append(mixed_masks, state_masks);
|
||||
const double mixed_area = std::abs(area(state_masks));
|
||||
if (mixed_area > dominant_mixed_area) {
|
||||
dominant_mixed_area = mixed_area;
|
||||
const int resolved_mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
||||
dominant_mixed_idx = resolved_mixed_idx >= 0 ? size_t(resolved_mixed_idx) : size_t(-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;
|
||||
|
||||
interval.has_mixed_paint = true;
|
||||
++mixed_state_count;
|
||||
append(mixed_masks, state_masks);
|
||||
|
||||
const double mixed_area = std::abs(area(state_masks));
|
||||
if (mixed_area > dominant_mixed_area) {
|
||||
dominant_mixed_area = mixed_area;
|
||||
dominant_mixed_idx = size_t(mixed_idx);
|
||||
}
|
||||
}
|
||||
if (dominant_mixed_idx < mixed_rows.size()) {
|
||||
@@ -1830,25 +1947,6 @@ 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;
|
||||
}
|
||||
if (interval.has_mixed_paint && preferred_a <= EPSILON && preferred_b <= EPSILON) {
|
||||
if (!locked_gradient_valid && dominant_gradient_valid) {
|
||||
locked_gradient_valid = true;
|
||||
locked_gradient_source_layer = layer_id;
|
||||
locked_gradient_mixed_idx = dominant_mixed_idx;
|
||||
locked_gradient_h_a = dominant_gradient_h_a;
|
||||
locked_gradient_h_b = dominant_gradient_h_b;
|
||||
BOOST_LOG_TRIVIAL(warning) << "Local-Z gradient lock acquired"
|
||||
<< " object=" << object_name
|
||||
<< " layer_id=" << layer_id
|
||||
<< " mixed_idx=" << locked_gradient_mixed_idx
|
||||
<< " h_a=" << locked_gradient_h_a
|
||||
<< " h_b=" << locked_gradient_h_b;
|
||||
}
|
||||
if (!locked_gradient_valid)
|
||||
++gradient_lock_unset_mixed_layers;
|
||||
else if (dominant_gradient_valid && dominant_mixed_idx != locked_gradient_mixed_idx)
|
||||
++gradient_lock_mismatch_layers;
|
||||
}
|
||||
total_mixed_state_layers += mixed_state_count;
|
||||
if (!mixed_masks.empty())
|
||||
mixed_masks = union_ex(mixed_masks);
|
||||
@@ -1870,12 +1968,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
// 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 (locked_gradient_valid) {
|
||||
pass_heights = build_local_z_alternating_pass_heights(interval.base_height, mixed_lower, mixed_upper,
|
||||
locked_gradient_h_a, locked_gradient_h_b);
|
||||
if (pass_heights.size() > 1)
|
||||
++alternating_height_intervals;
|
||||
} else if (dominant_gradient_valid) {
|
||||
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)
|
||||
@@ -1890,8 +1983,59 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
else
|
||||
pass_heights.emplace_back(interval.base_height);
|
||||
|
||||
// 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.
|
||||
if (interval.has_mixed_paint &&
|
||||
preferred_a <= EPSILON &&
|
||||
preferred_b <= EPSILON &&
|
||||
pass_heights.size() == 2 &&
|
||||
pass_heights[0] > pass_heights[1]) {
|
||||
std::swap(pass_heights[0], pass_heights[1]);
|
||||
}
|
||||
|
||||
const bool split_interval = interval.has_mixed_paint && pass_heights.size() > 1;
|
||||
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;
|
||||
@@ -1923,19 +2067,24 @@ 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;
|
||||
++forced_height_resolve_calls;
|
||||
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() || !mixed_rows[size_t(mixed_idx)].custom)
|
||||
++forced_height_resolve_non_custom_calls;
|
||||
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 (mixed_idx >= 0 && size_t(mixed_idx) < mixed_rows.size()) {
|
||||
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
|
||||
if (mf.component_a > 0 && mf.component_a <= num_physical &&
|
||||
mf.component_b > 0 && mf.component_b <= num_physical) {
|
||||
// Enforce strict per-pass alternation inside split local-Z intervals.
|
||||
target_extruder = ((pass_idx % 2) == 0) ? mf.component_a : mf.component_b;
|
||||
++strict_ab_assignments;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (target_extruder == 0) {
|
||||
target_extruder = mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true);
|
||||
@@ -1965,6 +2114,18 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
++cadence_index;
|
||||
z_cursor = z_next;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (!interval_has_split_painted_masks)
|
||||
++split_intervals_without_painted_masks;
|
||||
} else {
|
||||
@@ -1989,10 +2150,10 @@ 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))
|
||||
continue;
|
||||
++forced_height_resolve_calls;
|
||||
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() || !mixed_rows[size_t(mixed_idx)].custom)
|
||||
++forced_height_resolve_non_custom_calls;
|
||||
const unsigned int target_extruder =
|
||||
mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true);
|
||||
if (target_extruder == 0 || target_extruder > num_physical) {
|
||||
@@ -2046,15 +2207,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
<< " strict_ab_assignments=" << strict_ab_assignments
|
||||
<< " mixed_state_layers=" << total_mixed_state_layers
|
||||
<< " forced_height_resolve_calls=" << forced_height_resolve_calls
|
||||
<< " forced_height_resolve_non_custom_calls=" << forced_height_resolve_non_custom_calls
|
||||
<< " forced_height_resolve_invalid_target=" << forced_height_resolve_invalid_target
|
||||
<< " gradient_lock_valid=" << locked_gradient_valid
|
||||
<< " gradient_lock_source_layer=" << locked_gradient_source_layer
|
||||
<< " gradient_lock_mixed_idx=" << locked_gradient_mixed_idx
|
||||
<< " gradient_lock_h_a=" << locked_gradient_h_a
|
||||
<< " gradient_lock_h_b=" << locked_gradient_h_b
|
||||
<< " gradient_lock_mismatch_layers=" << gradient_lock_mismatch_layers
|
||||
<< " gradient_lock_unset_mixed_layers=" << gradient_lock_unset_mixed_layers
|
||||
<< " mixed_lower=" << mixed_lower
|
||||
<< " mixed_upper=" << mixed_upper
|
||||
<< " preferred_a=" << preferred_a
|
||||
@@ -2474,6 +2627,7 @@ void PrintObject::slice_volumes()
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation";
|
||||
std::vector<std::vector<ExPolygons>> mm_segmentation = multi_material_segmentation_by_painting(*this, [print]() { print->throw_if_canceled(); });
|
||||
apply_mixed_surface_indentation(*this, mm_segmentation);
|
||||
// Same-layer pointillisme is applied in G-code path domain (segment-level assignment),
|
||||
// not by XY state mask splitting, to avoid boolean-induced voids.
|
||||
BOOST_LOG_TRIVIAL(info) << "Same-layer pointillisme uses path-domain G-code segmentation";
|
||||
|
||||
+68
-136
@@ -2215,7 +2215,7 @@ public:
|
||||
, m_value(std::clamp(value_percent, 0, 100))
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
SetMinSize(wxSize(FromDIP(96), FromDIP(14)));
|
||||
SetMinSize(wxSize(FromDIP(96), FromDIP(12)));
|
||||
Bind(wxEVT_PAINT, &MixedGradientSelector::on_paint, this);
|
||||
Bind(wxEVT_LEFT_DOWN, &MixedGradientSelector::on_left_down, this);
|
||||
Bind(wxEVT_LEFT_UP, &MixedGradientSelector::on_left_up, this);
|
||||
@@ -2785,7 +2785,7 @@ public:
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
SetMinSize(wxSize(FromDIP(132), FromDIP(24)));
|
||||
SetMinSize(wxSize(FromDIP(120), FromDIP(20)));
|
||||
Bind(wxEVT_PAINT, &MixedMixPreview::on_paint, this);
|
||||
}
|
||||
|
||||
@@ -3254,6 +3254,7 @@ void Sidebar::update_mixed_filament_panel()
|
||||
int cycle_layers = std::max(2, get_mixed_int("mixed_filament_cycle_layers", 4));
|
||||
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 mixed_surface_indentation = std::clamp(get_mixed_float("mixed_filament_surface_indentation", 0.f), -2.f, 2.f);
|
||||
bool advanced_dithering = get_mixed_bool("mixed_filament_advanced_dithering", false);
|
||||
const std::string mixed_definitions = get_mixed_string("mixed_filament_definitions");
|
||||
|
||||
@@ -3273,20 +3274,27 @@ void Sidebar::update_mixed_filament_panel()
|
||||
set_mixed_int("mixed_filament_cycle_layers", cycle_layers);
|
||||
set_mixed_float("mixed_filament_pointillism_pixel_size", pointillism_pixel_size);
|
||||
set_mixed_float("mixed_filament_pointillism_line_gap", pointillism_line_gap);
|
||||
set_mixed_float("mixed_filament_surface_indentation", mixed_surface_indentation);
|
||||
set_mixed_string("mixed_filament_definitions", mixed_mgr.serialize_custom_entries());
|
||||
}
|
||||
|
||||
auto &mixed = mixed_mgr.mixed_filaments();
|
||||
const int compact_gap_x = FromDIP(6);
|
||||
const int compact_gap_y = FromDIP(4);
|
||||
const int compact_row_pad = FromDIP(6);
|
||||
const wxColour mixed_rows_bg(246, 248, 251);
|
||||
const wxColour mixed_row_bg(255, 255, 255);
|
||||
p->m_panel_mixed_filaments->SetBackgroundColour(mixed_rows_bg);
|
||||
|
||||
p->m_sizer_mixed_filaments->Clear(true);
|
||||
|
||||
// Header with title and add buttons (gradient / pattern).
|
||||
auto *header_row = new wxPanel(p->m_panel_mixed_filaments, wxID_ANY);
|
||||
header_row->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
header_row->SetBackgroundColour(mixed_rows_bg);
|
||||
auto *header_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *mixed_title = new wxStaticText(header_row, wxID_ANY, _L("Mixed Filaments"));
|
||||
mixed_title->SetFont(Label::Head_14);
|
||||
header_sizer->Add(mixed_title, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8));
|
||||
header_sizer->Add(mixed_title, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
|
||||
|
||||
auto add_custom_row = [this, preset_bundle, physical_colors, get_mixed_mode, get_mixed_int, get_mixed_float, get_mixed_bool, set_mixed_string, notify_mixed_change](bool pattern_row) {
|
||||
if (physical_colors.size() < 2)
|
||||
@@ -3329,25 +3337,25 @@ void Sidebar::update_mixed_filament_panel()
|
||||
};
|
||||
|
||||
auto *grad_label = new wxStaticText(header_row, wxID_ANY, _L("Gradient"));
|
||||
header_sizer->Add(grad_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
|
||||
auto *add_gradient_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)));
|
||||
header_sizer->Add(grad_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(3));
|
||||
auto *add_gradient_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(22), FromDIP(20)), wxBU_EXACTFIT);
|
||||
add_gradient_btn->SetToolTip(_L("Add custom gradient mixed filament"));
|
||||
add_gradient_btn->Enable(num_physical >= 2);
|
||||
add_gradient_btn->Bind(wxEVT_BUTTON, [add_custom_row](wxCommandEvent &) { add_custom_row(false); });
|
||||
header_sizer->Add(add_gradient_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(8));
|
||||
header_sizer->Add(add_gradient_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap_x);
|
||||
|
||||
auto *pattern_label = new wxStaticText(header_row, wxID_ANY, _L("Pattern"));
|
||||
header_sizer->Add(pattern_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
|
||||
auto *add_pattern_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)));
|
||||
header_sizer->Add(pattern_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(3));
|
||||
auto *add_pattern_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(22), FromDIP(20)), wxBU_EXACTFIT);
|
||||
add_pattern_btn->SetToolTip(_L("Add custom pattern mixed filament"));
|
||||
add_pattern_btn->Enable(num_physical >= 2);
|
||||
add_pattern_btn->Bind(wxEVT_BUTTON, [add_custom_row](wxCommandEvent &) { add_custom_row(true); });
|
||||
header_sizer->Add(add_pattern_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(8));
|
||||
header_sizer->Add(add_pattern_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap_x);
|
||||
|
||||
header_row->SetSizer(header_sizer);
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
|
||||
p->m_sizer_mixed_filaments->Add(header_row, 0, wxEXPAND);
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(1));
|
||||
p->m_sizer_mixed_filaments->Add(header_row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(2));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(1));
|
||||
|
||||
if (mixed.empty()) {
|
||||
p->m_panel_mixed_filaments->Hide();
|
||||
@@ -3355,125 +3363,50 @@ void Sidebar::update_mixed_filament_panel()
|
||||
return;
|
||||
}
|
||||
|
||||
// Global gradient settings row.
|
||||
auto *settings_row = new wxPanel(p->m_panel_mixed_filaments, wxID_ANY);
|
||||
settings_row->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
auto *settings_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
settings_sizer->AddSpacer(FromDIP(4));
|
||||
|
||||
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Lower")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
auto *lower_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.01, 10.0, lower_bound, 0.01);
|
||||
settings_sizer->Add(lower_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
||||
|
||||
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Upper")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
auto *upper_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.01, 10.0, upper_bound, 0.01);
|
||||
settings_sizer->Add(upper_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
||||
|
||||
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Cycle")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
auto *cycle_spin = new wxSpinCtrl(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(FromDIP(56), -1), wxSP_ARROW_KEYS, 2, 32, cycle_layers);
|
||||
settings_sizer->Add(cycle_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(3));
|
||||
|
||||
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Pixel")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
auto *pixel_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.0, 10.0, pointillism_pixel_size, 0.01);
|
||||
pixel_spin->SetToolTip(_L("Pointillisme segment length in mm. 0 means automatic nozzle-based sizing."));
|
||||
settings_sizer->Add(pixel_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
||||
|
||||
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Gap")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
auto *gap_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.0, 10.0, pointillism_line_gap, 0.01);
|
||||
gap_spin->SetToolTip(_L("Optional spacing between pointillisme line segments (mm)."));
|
||||
settings_sizer->Add(gap_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(3));
|
||||
settings_row->SetSizer(settings_sizer);
|
||||
p->m_sizer_mixed_filaments->Add(settings_row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(4));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
|
||||
|
||||
auto *rows_scroller = new wxScrolledWindow(p->m_panel_mixed_filaments, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxTAB_TRAVERSAL);
|
||||
rows_scroller->SetScrollRate(0, FromDIP(8));
|
||||
rows_scroller->SetScrollRate(0, FromDIP(6));
|
||||
rows_scroller->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT);
|
||||
rows_scroller->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
rows_scroller->SetBackgroundColour(mixed_rows_bg);
|
||||
auto *rows_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
rows_scroller->SetSizer(rows_sizer);
|
||||
|
||||
auto apply_settings = [this, preset_bundle, get_mixed_mode, get_mixed_bool, lower_spin, upper_spin, cycle_spin, pixel_spin, gap_spin,
|
||||
set_mixed_int, set_mixed_float, set_mixed_string, notify_mixed_change]() {
|
||||
const int mode = get_mixed_mode(false) ? 1 : 0;
|
||||
const bool advanced = get_mixed_bool("mixed_filament_advanced_dithering", false);
|
||||
float lo = std::max(0.01f, float(lower_spin->GetValue()));
|
||||
float hi = std::max(lo, float(upper_spin->GetValue()));
|
||||
int cycle = std::max(2, cycle_spin->GetValue());
|
||||
float pixel = std::max(0.f, float(pixel_spin->GetValue()));
|
||||
float gap = std::max(0.f, float(gap_spin->GetValue()));
|
||||
if (pixel > 1e-6f)
|
||||
gap = std::min(gap, pixel * 0.90f);
|
||||
if (std::abs(float(upper_spin->GetValue()) - hi) > 1e-6f)
|
||||
upper_spin->SetValue(hi);
|
||||
if (std::abs(float(gap_spin->GetValue()) - gap) > 1e-6f)
|
||||
gap_spin->SetValue(gap);
|
||||
|
||||
set_mixed_float("mixed_filament_height_lower_bound", lo);
|
||||
set_mixed_float("mixed_filament_height_upper_bound", hi);
|
||||
set_mixed_int("mixed_filament_cycle_layers", cycle);
|
||||
set_mixed_float("mixed_filament_pointillism_pixel_size", pixel);
|
||||
set_mixed_float("mixed_filament_pointillism_line_gap", gap);
|
||||
|
||||
auto &mgr = preset_bundle->mixed_filaments;
|
||||
mgr.apply_gradient_settings(mode, lo, hi, cycle, advanced);
|
||||
set_mixed_string("mixed_filament_definitions", mgr.serialize_custom_entries());
|
||||
notify_mixed_change();
|
||||
|
||||
this->CallAfter([this]() {
|
||||
update_dynamic_filament_list();
|
||||
update_mixed_filament_panel();
|
||||
});
|
||||
};
|
||||
lower_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
|
||||
upper_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
|
||||
cycle_spin->Bind(wxEVT_SPINCTRL, [apply_settings](wxSpinEvent &) { apply_settings(); });
|
||||
pixel_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
|
||||
gap_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
|
||||
lower_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
upper_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
cycle_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
pixel_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
gap_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
lower_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
upper_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
cycle_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
pixel_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
gap_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
|
||||
lower_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
|
||||
upper_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
|
||||
cycle_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
|
||||
pixel_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
|
||||
gap_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
|
||||
|
||||
for (size_t mixed_id = 0; mixed_id < mixed.size(); ++mixed_id) {
|
||||
MixedFilament &mf = mixed[mixed_id];
|
||||
auto *row = new wxPanel(rows_scroller, wxID_ANY);
|
||||
row->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern);
|
||||
const bool pattern_row_mode = !normalized_pattern.empty();
|
||||
|
||||
auto *row = new wxPanel(rows_scroller, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE);
|
||||
row->SetBackgroundColour(mixed_row_bg);
|
||||
auto *row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
auto *content_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto *title_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
wxColour swatch_color(mf.display_color);
|
||||
auto *swatch = new wxPanel(row, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(14), FromDIP(14)));
|
||||
auto *swatch = new wxPanel(row, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(12), FromDIP(12)));
|
||||
swatch->SetBackgroundColour(swatch_color);
|
||||
swatch->SetMinSize(wxSize(FromDIP(14), FromDIP(14)));
|
||||
title_row->Add(swatch, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(4));
|
||||
swatch->SetMinSize(wxSize(FromDIP(12), FromDIP(12)));
|
||||
title_row->Add(swatch, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
const int virtual_filament_id = int(num_physical + mixed_id + 1);
|
||||
auto *name_label = new wxStaticText(row, wxID_ANY, wxString::Format("Mixed Filament %d", virtual_filament_id));
|
||||
title_row->Add(name_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
title_row->Add(name_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
|
||||
|
||||
if (!mf.custom) {
|
||||
auto *pair_label = new wxStaticText(row, wxID_ANY, wxString::Format("(Filament %u + Filament %u)",
|
||||
unsigned(mf.component_a), unsigned(mf.component_b)));
|
||||
title_row->Add(pair_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
pair_label->SetForegroundColour(wxColour(96, 96, 96));
|
||||
title_row->Add(pair_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
|
||||
} else {
|
||||
auto *pair_label = new wxStaticText(row, wxID_ANY, wxString::Format("F%u + F%u",
|
||||
unsigned(mf.component_a), unsigned(mf.component_b)));
|
||||
pair_label->SetForegroundColour(wxColour(96, 96, 96));
|
||||
title_row->Add(pair_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
|
||||
|
||||
title_row->AddStretchSpacer(1);
|
||||
auto *kind_label = new wxStaticText(row, wxID_ANY, pattern_row_mode ? _L("Pattern") : _L("Gradient"));
|
||||
kind_label->SetForegroundColour(wxColour(84, 84, 84));
|
||||
title_row->Add(kind_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
|
||||
}
|
||||
content_sizer->Add(title_row, 0, wxEXPAND);
|
||||
|
||||
@@ -3494,10 +3427,8 @@ void Sidebar::update_mixed_filament_panel()
|
||||
picker_row->Add(choice_a, 1, wxALIGN_CENTER_VERTICAL);
|
||||
picker_row->Add(new wxStaticText(row, wxID_ANY, "+"), 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(6));
|
||||
picker_row->Add(choice_b, 1, wxALIGN_CENTER_VERTICAL);
|
||||
content_sizer->Add(picker_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(picker_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
|
||||
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern);
|
||||
const bool pattern_row_mode = !normalized_pattern.empty();
|
||||
MixedGradientSelector *blend_selector = nullptr;
|
||||
wxStaticText *blend_label = nullptr;
|
||||
wxTextCtrl *pattern_ctrl = nullptr;
|
||||
@@ -3523,20 +3454,21 @@ void Sidebar::update_mixed_filament_panel()
|
||||
distribution_row->Add(new wxStaticText(row, wxID_ANY, _L("Mode")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
distribution_choice = new wxChoice(row, wxID_ANY, wxDefaultPosition, wxDefaultSize, distribution_choices);
|
||||
distribution_choice->SetSelection(row_distribution_mode);
|
||||
distribution_choice->SetToolTip(_L("Choose whether this mixed row alternates by layer or interleaves colors on the same layer."));
|
||||
distribution_choice->SetToolTip(_L("Choose whether this mixed row alternates by layer or interleaves colors on the same layer.\n\n"
|
||||
"Warning: Same-layer pointillisme is extremely experimental and may produce unusable results."));
|
||||
distribution_row->Add(distribution_choice, 1, wxALIGN_CENTER_VERTICAL);
|
||||
content_sizer->Add(distribution_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(distribution_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
|
||||
if (pattern_row_mode) {
|
||||
auto *pattern_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
pattern_row->Add(new wxStaticText(row, wxID_ANY, _L("Pattern")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
pattern_ctrl = new wxTextCtrl(row, wxID_ANY, from_u8(normalized_pattern), wxDefaultPosition,
|
||||
wxSize(FromDIP(170), -1), wxTE_PROCESS_ENTER);
|
||||
wxSize(FromDIP(152), -1), wxTE_PROCESS_ENTER);
|
||||
pattern_ctrl->SetToolTip(_L("Manual repeating pattern. Use 1/2 or A/B for component A/B, "
|
||||
"and 3..9 for direct physical filament IDs. "
|
||||
"Example: 1/1/1/1/2/2/2/2 or 1/2/3/4."));
|
||||
pattern_row->Add(pattern_ctrl, 1, wxALIGN_CENTER_VERTICAL);
|
||||
content_sizer->Add(pattern_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(pattern_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
|
||||
auto *insert_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
insert_row->Add(new wxStaticText(row, wxID_ANY, _L("Insert")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
@@ -3544,10 +3476,10 @@ void Sidebar::update_mixed_filament_panel()
|
||||
pattern_insert_choice->SetSelection(component_a - 1);
|
||||
pattern_insert_choice->SetToolTip(_L("Select a physical filament to append into the pattern."));
|
||||
insert_row->Add(pattern_insert_choice, 1, wxALIGN_CENTER_VERTICAL);
|
||||
pattern_insert_btn = new wxButton(row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(28), FromDIP(24)), wxBU_EXACTFIT);
|
||||
pattern_insert_btn = new wxButton(row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)), wxBU_EXACTFIT);
|
||||
pattern_insert_btn->SetToolTip(_L("Append selected filament ID to pattern"));
|
||||
insert_row->Add(pattern_insert_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
content_sizer->Add(insert_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(insert_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
|
||||
auto *quick_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
quick_row->Add(new wxStaticText(row, wxID_ANY, _L("Filaments")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
@@ -3562,7 +3494,7 @@ void Sidebar::update_mixed_filament_panel()
|
||||
pattern_quick_filament_buttons.emplace_back(btn);
|
||||
}
|
||||
quick_row->Add(quick_buttons, 1, wxALIGN_CENTER_VERTICAL);
|
||||
content_sizer->Add(quick_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(quick_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
} else {
|
||||
wxArrayString optional_filament_choices;
|
||||
optional_filament_choices.Add(_L("None"));
|
||||
@@ -3594,7 +3526,7 @@ void Sidebar::update_mixed_filament_panel()
|
||||
blend_selector = new MixedGradientSelector(row, color_a, color_b, std::clamp(mf.mix_b_percent, 0, 100));
|
||||
auto *blend_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
blend_row->Add(blend_selector, 1, wxEXPAND);
|
||||
content_sizer->Add(blend_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(blend_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
|
||||
const bool same_layer_mode = row_distribution_mode == int(MixedFilament::SameLayerPointillisme);
|
||||
blend_label = new wxStaticText(row, wxID_ANY, multi_gradient_mode ?
|
||||
@@ -3607,7 +3539,7 @@ void Sidebar::update_mixed_filament_panel()
|
||||
auto *ratio_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
ratio_row->AddStretchSpacer(1);
|
||||
ratio_row->Add(blend_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
content_sizer->Add(ratio_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(ratio_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
|
||||
if (num_physical >= 3 && !simple_mode) {
|
||||
add_extra_color_btn = new wxButton(row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)), wxBU_EXACTFIT);
|
||||
@@ -3624,7 +3556,7 @@ void Sidebar::update_mixed_filament_panel()
|
||||
extra_row->Add(choice_c, 1, wxALIGN_CENTER_VERTICAL);
|
||||
extra_row->Add(new wxStaticText(row, wxID_ANY, "+"), 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(6));
|
||||
extra_row->Add(choice_d, 1, wxALIGN_CENTER_VERTICAL);
|
||||
content_sizer->Add(extra_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
content_sizer->Add(extra_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
}
|
||||
|
||||
if (blend_selector) {
|
||||
@@ -3640,14 +3572,14 @@ void Sidebar::update_mixed_filament_panel()
|
||||
}
|
||||
|
||||
auto *preview_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
preview_row->Add(new wxStaticText(row, wxID_ANY, _L("Preview")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
preview_row->Add(new wxStaticText(row, wxID_ANY, _L("Preview")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap_x);
|
||||
mix_preview = new MixedMixPreview(row);
|
||||
preview_row->Add(mix_preview, 1, wxALIGN_CENTER_VERTICAL);
|
||||
content_sizer->Add(preview_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
|
||||
mix_summary_label = new wxStaticText(row, wxID_ANY, wxEmptyString);
|
||||
mix_summary_label->SetForegroundColour(wxColour(90, 90, 90));
|
||||
content_sizer->Add(mix_summary_label, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
|
||||
preview_row->Add(mix_summary_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
|
||||
content_sizer->Add(preview_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
|
||||
|
||||
{
|
||||
std::vector<unsigned int> initial_sequence;
|
||||
@@ -3978,11 +3910,11 @@ void Sidebar::update_mixed_filament_panel()
|
||||
}
|
||||
}
|
||||
|
||||
row_sizer->Add(content_sizer, 1, wxEXPAND);
|
||||
row_sizer->Add(content_sizer, 1, wxEXPAND | wxALL, compact_row_pad);
|
||||
|
||||
auto *chk = new wxCheckBox(row, wxID_ANY, wxEmptyString);
|
||||
chk->SetValue(mf.enabled);
|
||||
row_sizer->Add(chk, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxTOP, FromDIP(2));
|
||||
row_sizer->Add(chk, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_row_pad);
|
||||
chk->Bind(wxEVT_CHECKBOX, [this, preset_bundle, mixed_id, chk, set_mixed_string, notify_mixed_change](wxCommandEvent &) {
|
||||
auto &mgr = preset_bundle->mixed_filaments;
|
||||
auto &mfs = mgr.mixed_filaments();
|
||||
@@ -3999,16 +3931,16 @@ void Sidebar::update_mixed_filament_panel()
|
||||
});
|
||||
|
||||
row->SetSizer(row_sizer);
|
||||
rows_sizer->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(4));
|
||||
rows_sizer->AddSpacer(FromDIP(1));
|
||||
rows_sizer->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(2));
|
||||
rows_sizer->AddSpacer(FromDIP(2));
|
||||
}
|
||||
|
||||
rows_sizer->AddSpacer(FromDIP(4));
|
||||
rows_sizer->AddSpacer(FromDIP(2));
|
||||
rows_scroller->Layout();
|
||||
rows_scroller->FitInside();
|
||||
|
||||
const int min_h = FromDIP(72);
|
||||
const int max_h = FromDIP(260);
|
||||
const int min_h = FromDIP(68);
|
||||
const int max_h = FromDIP(220);
|
||||
const int content_h = std::max(0, rows_scroller->GetVirtualSize().GetHeight());
|
||||
const int desired_h = std::clamp(content_h, min_h, max_h);
|
||||
rows_scroller->SetMinSize(wxSize(-1, desired_h));
|
||||
@@ -4016,8 +3948,8 @@ void Sidebar::update_mixed_filament_panel()
|
||||
if (prev_rows_view_y > 0)
|
||||
rows_scroller->Scroll(0, prev_rows_view_y);
|
||||
|
||||
p->m_sizer_mixed_filaments->Add(rows_scroller, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(0));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
|
||||
p->m_sizer_mixed_filaments->Add(rows_scroller, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(2));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
|
||||
p->m_panel_mixed_filaments->Show();
|
||||
p->m_panel_mixed_filaments->Layout();
|
||||
Layout();
|
||||
|
||||
@@ -1791,6 +1791,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
opt_key == "mixed_filament_advanced_dithering" ||
|
||||
opt_key == "mixed_filament_pointillism_pixel_size" ||
|
||||
opt_key == "mixed_filament_pointillism_line_gap" ||
|
||||
opt_key == "mixed_filament_surface_indentation" ||
|
||||
opt_key == "dithering_z_step_size" ||
|
||||
opt_key == "dithering_local_z_mode" ||
|
||||
opt_key == "dithering_step_painted_zones_only" ||
|
||||
@@ -2522,6 +2523,7 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops"
|
||||
optgroup->append_single_option_line("mixed_filament_advanced_dithering");
|
||||
optgroup->append_single_option_line("mixed_filament_pointillism_pixel_size");
|
||||
optgroup->append_single_option_line("mixed_filament_pointillism_line_gap");
|
||||
optgroup->append_single_option_line("mixed_filament_surface_indentation");
|
||||
optgroup->append_single_option_line("dithering_z_step_size");
|
||||
optgroup->append_single_option_line("dithering_local_z_mode");
|
||||
optgroup->append_single_option_line("dithering_step_painted_zones_only");
|
||||
|
||||
Reference in New Issue
Block a user