mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-25 11:52:05 +00:00
temp
This commit is contained in:
@@ -211,6 +211,25 @@ static int clamp_int(int v, int lo, int hi)
|
||||
return std::max(lo, std::min(hi, v));
|
||||
}
|
||||
|
||||
static float clamp_surface_offset(float v)
|
||||
{
|
||||
return std::clamp(v, -2.f, 2.f);
|
||||
}
|
||||
|
||||
static std::string format_surface_offset_token(float value)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << std::fixed << std::setprecision(4) << clamp_surface_offset(value);
|
||||
std::string out = ss.str();
|
||||
while (!out.empty() && out.back() == '0')
|
||||
out.pop_back();
|
||||
if (!out.empty() && out.back() == '.')
|
||||
out.pop_back();
|
||||
if (out == "-0")
|
||||
out = "0";
|
||||
return out.empty() ? std::string("0") : out;
|
||||
}
|
||||
|
||||
static int safe_ratio_from_height(float h, float unit)
|
||||
{
|
||||
if (unit <= 1e-6f)
|
||||
@@ -341,6 +360,8 @@ static bool parse_row_definition(const std::string &row,
|
||||
std::string &manual_pattern,
|
||||
int &distribution_mode,
|
||||
int &local_z_max_sublayers,
|
||||
float &component_a_surface_offset,
|
||||
float &component_b_surface_offset,
|
||||
bool &deleted)
|
||||
{
|
||||
auto trim_copy = [](const std::string &s) {
|
||||
@@ -385,6 +406,22 @@ static bool parse_row_definition(const std::string &row,
|
||||
}
|
||||
};
|
||||
|
||||
auto parse_float_token = [&trim_copy](const std::string &tok, float &out) {
|
||||
const std::string t = trim_copy(tok);
|
||||
if (t.empty())
|
||||
return false;
|
||||
try {
|
||||
size_t consumed = 0;
|
||||
const float v = std::stof(t, &consumed);
|
||||
if (consumed != t.size())
|
||||
return false;
|
||||
out = v;
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
std::stringstream ss(row);
|
||||
std::string token;
|
||||
@@ -425,6 +462,8 @@ static bool parse_row_definition(const std::string &row,
|
||||
manual_pattern.clear();
|
||||
distribution_mode = int(MixedFilament::Simple);
|
||||
local_z_max_sublayers = 0;
|
||||
component_a_surface_offset = 0.f;
|
||||
component_b_surface_offset = 0.f;
|
||||
deleted = false;
|
||||
|
||||
size_t token_idx = 5;
|
||||
@@ -473,6 +512,19 @@ static bool parse_row_definition(const std::string &row,
|
||||
local_z_max_sublayers = std::max(0, parsed_max_sublayers);
|
||||
continue;
|
||||
}
|
||||
if ((tok[0] == 'x' || tok[0] == 'X') && tok.size() >= 3) {
|
||||
const char component = char(std::tolower(static_cast<unsigned char>(tok[1])));
|
||||
if (component == 'a' || component == 'b') {
|
||||
float parsed_offset = component == 'a' ? component_a_surface_offset : component_b_surface_offset;
|
||||
if (parse_float_token(tok.substr(2), parsed_offset)) {
|
||||
if (component == 'a')
|
||||
component_a_surface_offset = clamp_surface_offset(parsed_offset);
|
||||
else
|
||||
component_b_surface_offset = clamp_surface_offset(parsed_offset);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (tok[0] == 'd' || tok[0] == 'D') {
|
||||
int parsed_deleted = deleted ? 1 : 0;
|
||||
if (parse_int_token(tok.substr(1), parsed_deleted))
|
||||
@@ -942,6 +994,8 @@ void MixedFilamentManager::add_custom_filament(unsigned int component_a,
|
||||
mf.pointillism_all_filaments = false;
|
||||
mf.distribution_mode = int(MixedFilament::Simple);
|
||||
mf.local_z_max_sublayers = 0;
|
||||
mf.component_a_surface_offset = 0.f;
|
||||
mf.component_b_surface_offset = 0.f;
|
||||
mf.enabled = true;
|
||||
mf.deleted = false;
|
||||
mf.custom = true;
|
||||
@@ -1030,6 +1084,8 @@ std::string MixedFilamentManager::serialize_custom_entries()
|
||||
<< 'w' << normalized_weights << ','
|
||||
<< 'm' << clamp_int(mf.distribution_mode, int(MixedFilament::LayerCycle), int(MixedFilament::Simple)) << ','
|
||||
<< 'z' << std::max(0, mf.local_z_max_sublayers) << ','
|
||||
<< "xa" << format_surface_offset_token(mf.component_a_surface_offset) << ','
|
||||
<< "xb" << format_surface_offset_token(mf.component_b_surface_offset) << ','
|
||||
<< 'd' << (mf.deleted ? 1 : 0) << ','
|
||||
<< 'o' << (mf.origin_auto ? 1 : 0) << ','
|
||||
<< 'u' << mf.stable_id;
|
||||
@@ -1101,10 +1157,12 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
|
||||
std::string manual_pattern;
|
||||
int distribution_mode = int(MixedFilament::Simple);
|
||||
int local_z_max_sublayers = 0;
|
||||
float component_a_surface_offset = 0.f;
|
||||
float component_b_surface_offset = 0.f;
|
||||
bool deleted = false;
|
||||
if (!parse_row_definition(row, a, b, stable_id, enabled, custom, origin_auto, mix, pointillism_all_filaments,
|
||||
gradient_component_ids, gradient_component_weights, manual_pattern, distribution_mode,
|
||||
local_z_max_sublayers, deleted)) {
|
||||
local_z_max_sublayers, component_a_surface_offset, component_b_surface_offset, deleted)) {
|
||||
++skipped_rows;
|
||||
BOOST_LOG_TRIVIAL(warning) << "MixedFilamentManager::load_custom_entries invalid row format: " << row;
|
||||
continue;
|
||||
@@ -1152,6 +1210,8 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
|
||||
mf.manual_pattern = normalize_manual_pattern(manual_pattern);
|
||||
mf.distribution_mode = clamp_int(distribution_mode, int(MixedFilament::LayerCycle), int(MixedFilament::Simple));
|
||||
mf.local_z_max_sublayers = std::max(0, local_z_max_sublayers);
|
||||
mf.component_a_surface_offset = clamp_surface_offset(component_a_surface_offset);
|
||||
mf.component_b_surface_offset = clamp_surface_offset(component_b_surface_offset);
|
||||
mf.mix_b_percent = mf.manual_pattern.empty() ? mix : mix_percent_from_normalized_pattern(mf.manual_pattern);
|
||||
mf.deleted = deleted;
|
||||
if (mf.deleted)
|
||||
@@ -1179,6 +1239,8 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
|
||||
mf.manual_pattern = normalize_manual_pattern(manual_pattern);
|
||||
mf.distribution_mode = clamp_int(distribution_mode, int(MixedFilament::LayerCycle), int(MixedFilament::Simple));
|
||||
mf.local_z_max_sublayers = std::max(0, local_z_max_sublayers);
|
||||
mf.component_a_surface_offset = clamp_surface_offset(component_a_surface_offset);
|
||||
mf.component_b_surface_offset = clamp_surface_offset(component_b_surface_offset);
|
||||
if (!mf.manual_pattern.empty())
|
||||
mf.mix_b_percent = mix_percent_from_normalized_pattern(mf.manual_pattern);
|
||||
mf.enabled = enabled;
|
||||
@@ -1358,6 +1420,37 @@ unsigned int MixedFilamentManager::effective_painted_region_filament_id(unsigned
|
||||
return resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height);
|
||||
}
|
||||
|
||||
float MixedFilamentManager::component_surface_offset(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
float layer_print_z,
|
||||
float layer_height,
|
||||
bool force_height_weighted) const
|
||||
{
|
||||
const MixedFilament *mixed_row = mixed_filament_from_id(filament_id, num_physical);
|
||||
if (mixed_row == nullptr)
|
||||
return 0.f;
|
||||
|
||||
if (mixed_row->distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
||||
return 0.f;
|
||||
|
||||
const std::string normalized_pattern = normalize_manual_pattern(mixed_row->manual_pattern);
|
||||
if (normalized_pattern.find(',') != std::string::npos)
|
||||
return 0.f;
|
||||
|
||||
const unsigned int resolved = resolve(filament_id,
|
||||
num_physical,
|
||||
layer_index,
|
||||
layer_print_z,
|
||||
layer_height,
|
||||
force_height_weighted);
|
||||
if (resolved == mixed_row->component_a)
|
||||
return clamp_surface_offset(mixed_row->component_a_surface_offset);
|
||||
if (resolved == mixed_row->component_b)
|
||||
return clamp_surface_offset(mixed_row->component_b_surface_offset);
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> MixedFilamentManager::ordered_perimeter_extruders(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
@@ -1498,6 +1591,17 @@ std::string MixedFilamentManager::blend_color(const std::string &color_a,
|
||||
return rgb_to_hex({int(out_r), int(out_g), int(out_b)});
|
||||
}
|
||||
|
||||
int MixedFilamentManager::apparent_mix_b_percent(int mix_b_percent,
|
||||
float component_a_surface_offset,
|
||||
float component_b_surface_offset,
|
||||
float reference_width_mm)
|
||||
{
|
||||
const float safe_reference = std::max(0.05f, std::abs(reference_width_mm));
|
||||
const float shift_pct = 100.f * (clamp_surface_offset(component_a_surface_offset) -
|
||||
clamp_surface_offset(component_b_surface_offset)) / safe_reference;
|
||||
return clamp_int(int(std::lround(float(clamp_int(mix_b_percent, 0, 100)) + shift_pct)), 0, 100);
|
||||
}
|
||||
|
||||
void MixedFilamentManager::refresh_display_colors(const std::vector<std::string> &filament_colours)
|
||||
{
|
||||
for (MixedFilament &mf : m_mixed) {
|
||||
@@ -1535,8 +1639,14 @@ void MixedFilamentManager::refresh_display_colors(const std::vector<std::string>
|
||||
mf.display_color = "#26A69A";
|
||||
continue;
|
||||
}
|
||||
const int ratio_a = std::max(0, 100 - clamp_int(mf.mix_b_percent, 0, 100));
|
||||
const int ratio_b = clamp_int(mf.mix_b_percent, 0, 100);
|
||||
const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
|
||||
const int ratio_b =
|
||||
(mf.distribution_mode != int(MixedFilament::SameLayerPointillisme) &&
|
||||
normalized_pattern.empty() &&
|
||||
gradient_ids.size() < 3) ?
|
||||
apparent_mix_b_percent(mf.mix_b_percent, mf.component_a_surface_offset, mf.component_b_surface_offset) :
|
||||
clamp_int(mf.mix_b_percent, 0, 100);
|
||||
const int ratio_a = std::max(0, 100 - ratio_b);
|
||||
mf.display_color = blend_color(
|
||||
filament_colours[mf.component_a - 1],
|
||||
filament_colours[mf.component_b - 1],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
@@ -63,6 +64,12 @@ struct MixedFilament
|
||||
// Optional Local-Z cap for this mixed row. 0 disables the cap.
|
||||
int local_z_max_sublayers = 0;
|
||||
|
||||
// Additional XY surface offsets, in mm, applied when this mixed row
|
||||
// resolves to component A or B for an entire layer. Positive values
|
||||
// contract inward; negative values expand outward.
|
||||
float component_a_surface_offset = 0.f;
|
||||
float component_b_surface_offset = 0.f;
|
||||
|
||||
// Whether this mixed filament is enabled (available for assignment).
|
||||
bool enabled = true;
|
||||
|
||||
@@ -82,6 +89,7 @@ struct MixedFilament
|
||||
|
||||
bool operator==(const MixedFilament &rhs) const
|
||||
{
|
||||
constexpr float k_surface_offset_epsilon = 1e-6f;
|
||||
return component_a == rhs.component_a &&
|
||||
component_b == rhs.component_b &&
|
||||
stable_id == rhs.stable_id &&
|
||||
@@ -94,6 +102,8 @@ struct MixedFilament
|
||||
pointillism_all_filaments == rhs.pointillism_all_filaments &&
|
||||
distribution_mode == rhs.distribution_mode &&
|
||||
local_z_max_sublayers == rhs.local_z_max_sublayers &&
|
||||
std::abs(component_a_surface_offset - rhs.component_a_surface_offset) <= k_surface_offset_epsilon &&
|
||||
std::abs(component_b_surface_offset - rhs.component_b_surface_offset) <= k_surface_offset_epsilon &&
|
||||
enabled == rhs.enabled &&
|
||||
deleted == rhs.deleted &&
|
||||
custom == rhs.custom &&
|
||||
@@ -191,6 +201,12 @@ public:
|
||||
float layer_height_a = 0.f,
|
||||
float layer_height_b = 0.f,
|
||||
float base_layer_height = 0.2f) const;
|
||||
float component_surface_offset(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
float layer_print_z = 0.f,
|
||||
float layer_height = 0.f,
|
||||
bool force_height_weighted = false) const;
|
||||
std::vector<unsigned int> ordered_perimeter_extruders(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
@@ -213,6 +229,10 @@ public:
|
||||
static std::string blend_color(const std::string &color_a,
|
||||
const std::string &color_b,
|
||||
int ratio_a, int ratio_b);
|
||||
static int apparent_mix_b_percent(int mix_b_percent,
|
||||
float component_a_surface_offset,
|
||||
float component_b_surface_offset,
|
||||
float reference_width_mm = 0.4f);
|
||||
|
||||
// ---- Accessors ------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1031,6 +1031,125 @@ static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vect
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool apply_mixed_component_surface_offsets(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();
|
||||
if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value))
|
||||
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 + 1)
|
||||
return false;
|
||||
|
||||
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
||||
bool has_component_offsets = false;
|
||||
for (const MixedFilament &mf : mixed_mgr.mixed_filaments()) {
|
||||
if (!mf.enabled || mf.deleted)
|
||||
continue;
|
||||
if (std::abs(mf.component_a_surface_offset) > EPSILON || std::abs(mf.component_b_surface_offset) > EPSILON) {
|
||||
has_component_offsets = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_component_offsets)
|
||||
return false;
|
||||
|
||||
size_t changed_layers = 0;
|
||||
size_t changed_states = 0;
|
||||
size_t emptied_states = 0;
|
||||
size_t expanded_states = 0;
|
||||
size_t contracted_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;
|
||||
|
||||
const Layer *layer = layer_id < size_t(print_object.layer_count()) ? print_object.get_layer(int(layer_id)) : nullptr;
|
||||
const float layer_print_z = layer ? float(layer->print_z) : 0.f;
|
||||
const float layer_height = layer ? float(layer->height) : 0.f;
|
||||
bool layer_changed = false;
|
||||
|
||||
for (size_t channel_idx = 1; channel_idx < num_channels; ++channel_idx) {
|
||||
ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
||||
if (state_masks.empty())
|
||||
continue;
|
||||
|
||||
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
||||
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
||||
continue;
|
||||
|
||||
const coordf_t offset_mm = coordf_t(mixed_mgr.component_surface_offset(state_id,
|
||||
num_physical,
|
||||
int(layer_id),
|
||||
layer_print_z,
|
||||
layer_height));
|
||||
if (std::abs(offset_mm) <= EPSILON)
|
||||
continue;
|
||||
|
||||
const float delta_scaled = float(scale_(std::abs(double(offset_mm))));
|
||||
if (delta_scaled <= float(EPSILON))
|
||||
continue;
|
||||
|
||||
ExPolygons adjusted = offset_mm > 0 ? offset_ex(state_masks, -delta_scaled) : offset_ex(state_masks, delta_scaled);
|
||||
if (!adjusted.empty() && adjusted.size() > 1)
|
||||
adjusted = union_ex(adjusted);
|
||||
|
||||
if (offset_mm < 0 && !adjusted.empty()) {
|
||||
ExPolygons occupied_other;
|
||||
for (size_t other_idx = 0; other_idx < num_channels; ++other_idx) {
|
||||
if (other_idx == channel_idx)
|
||||
continue;
|
||||
if (!segmentation[layer_id][other_idx].empty())
|
||||
append(occupied_other, segmentation[layer_id][other_idx]);
|
||||
}
|
||||
if (occupied_other.size() > 1)
|
||||
occupied_other = union_ex(occupied_other);
|
||||
if (!occupied_other.empty()) {
|
||||
ExPolygons clipped = diff_ex(adjusted, occupied_other, 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;
|
||||
if (offset_mm < 0)
|
||||
++expanded_states;
|
||||
else
|
||||
++contracted_states;
|
||||
++changed_states;
|
||||
layer_changed = true;
|
||||
}
|
||||
|
||||
if (layer_changed)
|
||||
++changed_layers;
|
||||
}
|
||||
|
||||
if (changed_states == 0)
|
||||
return false;
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Mixed component surface offsets applied"
|
||||
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
||||
<< " changed_layers=" << changed_layers
|
||||
<< " changed_states=" << changed_states
|
||||
<< " contracted_states=" << contracted_states
|
||||
<< " expanded_states=" << expanded_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)
|
||||
@@ -2182,6 +2301,134 @@ static ExPolygons collect_layer_region_slices(const Layer &layer)
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool apply_mixed_region_surface_offsets(PrintObject &print_object)
|
||||
{
|
||||
const Print *print = print_object.print();
|
||||
if (print == nullptr || print_object.layer_count() == 0)
|
||||
return false;
|
||||
|
||||
const PrintConfig &print_cfg = print->config();
|
||||
const DynamicPrintConfig &full_cfg = print->full_print_config();
|
||||
if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value))
|
||||
return false;
|
||||
|
||||
const size_t num_physical = print_cfg.filament_diameter.size();
|
||||
if (num_physical == 0)
|
||||
return false;
|
||||
|
||||
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
||||
bool has_component_offsets = false;
|
||||
for (const MixedFilament &mf : mixed_mgr.mixed_filaments()) {
|
||||
if (!mf.enabled || mf.deleted)
|
||||
continue;
|
||||
if (std::abs(mf.component_a_surface_offset) > EPSILON || std::abs(mf.component_b_surface_offset) > EPSILON) {
|
||||
has_component_offsets = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_component_offsets)
|
||||
return false;
|
||||
|
||||
size_t changed_layers = 0;
|
||||
size_t changed_regions = 0;
|
||||
size_t contracted_regions = 0;
|
||||
size_t expanded_regions = 0;
|
||||
size_t stolen_regions = 0;
|
||||
|
||||
struct PendingRegionOffset {
|
||||
int region_id { -1 };
|
||||
coordf_t offset_mm { 0.f };
|
||||
ExPolygons adjusted;
|
||||
};
|
||||
|
||||
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
|
||||
Layer &layer = *print_object.get_layer(int(layer_id));
|
||||
std::vector<PendingRegionOffset> pending;
|
||||
pending.reserve(size_t(layer.region_count()));
|
||||
|
||||
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
||||
LayerRegion *layerm = layer.get_region(region_id);
|
||||
if (layerm == nullptr || layerm->slices.empty())
|
||||
continue;
|
||||
|
||||
const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().wall_filament.value));
|
||||
if (!mixed_mgr.is_mixed(filament_id, num_physical))
|
||||
continue;
|
||||
|
||||
const coordf_t offset_mm = coordf_t(mixed_mgr.component_surface_offset(filament_id,
|
||||
num_physical,
|
||||
int(layer_id),
|
||||
float(layer.print_z),
|
||||
float(layer.height)));
|
||||
if (std::abs(offset_mm) <= EPSILON)
|
||||
continue;
|
||||
|
||||
const float delta_scaled = float(scale_(std::abs(double(offset_mm))));
|
||||
if (delta_scaled <= float(EPSILON))
|
||||
continue;
|
||||
|
||||
ExPolygons adjusted = offset_ex(to_expolygons(layerm->slices.surfaces), offset_mm > 0 ? -delta_scaled : delta_scaled);
|
||||
if (!adjusted.empty() && adjusted.size() > 1)
|
||||
adjusted = union_ex(adjusted);
|
||||
|
||||
pending.push_back({ region_id, offset_mm, std::move(adjusted) });
|
||||
}
|
||||
|
||||
if (pending.empty())
|
||||
continue;
|
||||
|
||||
bool layer_changed = false;
|
||||
for (const PendingRegionOffset &entry : pending) {
|
||||
LayerRegion *layerm = layer.get_region(entry.region_id);
|
||||
if (layerm == nullptr)
|
||||
continue;
|
||||
|
||||
if (entry.offset_mm < 0 && !entry.adjusted.empty()) {
|
||||
for (int other_region_id = 0; other_region_id < layer.region_count(); ++other_region_id) {
|
||||
if (other_region_id == entry.region_id)
|
||||
continue;
|
||||
|
||||
LayerRegion *other = layer.get_region(other_region_id);
|
||||
if (other == nullptr || other->slices.empty())
|
||||
continue;
|
||||
|
||||
ExPolygons stolen = intersection_ex(other->slices.surfaces, entry.adjusted);
|
||||
if (stolen.empty())
|
||||
continue;
|
||||
|
||||
Polygons remaining = diff(to_polygons(other->slices.surfaces), entry.adjusted);
|
||||
other->slices.set(union_ex(remaining), stInternal);
|
||||
++stolen_regions;
|
||||
layer_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
layerm->slices.set(entry.adjusted, stInternal);
|
||||
++changed_regions;
|
||||
if (entry.offset_mm > 0)
|
||||
++contracted_regions;
|
||||
else
|
||||
++expanded_regions;
|
||||
layer_changed = true;
|
||||
}
|
||||
|
||||
if (layer_changed)
|
||||
++changed_layers;
|
||||
}
|
||||
|
||||
if (changed_regions == 0)
|
||||
return false;
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Mixed region surface offsets applied"
|
||||
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
||||
<< " changed_layers=" << changed_layers
|
||||
<< " changed_regions=" << changed_regions
|
||||
<< " contracted_regions=" << contracted_regions
|
||||
<< " expanded_regions=" << expanded_regions
|
||||
<< " stolen_regions=" << stolen_regions;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void export_local_z_plan_debug(const PrintObject &print_object, coordf_t lower_bound, coordf_t upper_bound)
|
||||
{
|
||||
const std::vector<LocalZInterval> &intervals = print_object.local_z_intervals();
|
||||
@@ -3935,6 +4182,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);
|
||||
apply_mixed_component_surface_offsets(*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";
|
||||
@@ -3942,6 +4190,8 @@ void PrintObject::slice_volumes()
|
||||
apply_mm_segmentation(*this, std::move(mm_segmentation), [print]() { print->throw_if_canceled(); });
|
||||
}
|
||||
|
||||
apply_mixed_region_surface_offsets(*this);
|
||||
|
||||
// Is any ModelVolume fuzzy skin painted?
|
||||
if (this->model_object()->is_fuzzy_skin_painted()) {
|
||||
// If XY Size compensation is also enabled, notify the user that XY Size compensation
|
||||
|
||||
@@ -4580,6 +4580,7 @@ public:
|
||||
const MixedFilament &mf,
|
||||
size_t num_physical,
|
||||
const std::vector<std::string> &physical_colors,
|
||||
const std::vector<double> &nozzle_diameters,
|
||||
const std::vector<wxColour> &palette,
|
||||
const MixedFilamentPreviewSettings &preview_settings,
|
||||
OnChangeFn on_change = {});
|
||||
@@ -4600,6 +4601,7 @@ private:
|
||||
MixedFilament m_mf;
|
||||
size_t m_num_physical;
|
||||
std::vector<std::string> m_physical_colors;
|
||||
std::vector<double> m_nozzle_diameters;
|
||||
std::vector<wxColour> m_palette;
|
||||
MixedFilamentPreviewSettings m_preview_settings;
|
||||
bool m_has_changes = false;
|
||||
@@ -4620,11 +4622,14 @@ private:
|
||||
wxStaticText *m_picker_b_label = nullptr;
|
||||
wxStaticText *m_picker_c_label = nullptr;
|
||||
wxStaticText *m_picker_d_label = nullptr;
|
||||
wxPanel *m_surface_offset_target_swatch = nullptr;
|
||||
wxChoice *m_surface_offset_target_choice = nullptr;
|
||||
MixedGradientSelector *m_blend_selector = nullptr;
|
||||
wxStaticText *m_blend_label = nullptr;
|
||||
wxTextCtrl *m_pattern_ctrl = nullptr;
|
||||
wxCheckBox *m_local_z_limit_checkbox = nullptr;
|
||||
wxSpinCtrl *m_local_z_limit_spin = nullptr;
|
||||
wxSpinCtrlDouble *m_surface_offset_spin = nullptr;
|
||||
std::vector<wxButton*> m_pattern_quick_buttons;
|
||||
MixedMixPreview *m_mix_preview = nullptr;
|
||||
wxStaticText *m_breakdown_label = nullptr;
|
||||
@@ -4675,6 +4680,7 @@ public:
|
||||
void set_data(const std::vector<wxColour> &palette,
|
||||
const std::vector<unsigned int> &sequence,
|
||||
bool same_layer_mode,
|
||||
const std::vector<double> &surface_offsets_mm,
|
||||
const wxColour &fallback,
|
||||
const wxString &left_overlay,
|
||||
const wxString &right_overlay)
|
||||
@@ -4682,6 +4688,7 @@ public:
|
||||
m_palette = palette;
|
||||
m_sequence = sequence;
|
||||
m_same_layer = same_layer_mode;
|
||||
m_surface_offsets_mm = surface_offsets_mm;
|
||||
m_fallback = fallback;
|
||||
m_left_overlay = left_overlay;
|
||||
m_right_overlay = right_overlay;
|
||||
@@ -4704,6 +4711,19 @@ private:
|
||||
return m_fallback;
|
||||
}
|
||||
|
||||
int vertical_inset_for_extruder(unsigned int extruder_id, int rect_height) const
|
||||
{
|
||||
if (extruder_id == 0 || extruder_id >= m_surface_offsets_mm.size() || rect_height <= 2)
|
||||
return 0;
|
||||
|
||||
const double offset_mm = m_surface_offsets_mm[extruder_id];
|
||||
if (offset_mm <= 0.0)
|
||||
return 0;
|
||||
|
||||
const double normalized = std::clamp(offset_mm / 2.0, 0.0, 1.0);
|
||||
return std::clamp(int(std::round(normalized * rect_height * 0.30)), 0, std::max(0, rect_height / 3));
|
||||
}
|
||||
|
||||
void on_paint(wxPaintEvent &)
|
||||
{
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
@@ -4723,10 +4743,12 @@ private:
|
||||
const size_t seq_len = m_sequence.size();
|
||||
for (int s = 0; s < stripes; ++s) {
|
||||
const size_t idx = size_t(s % int(seq_len));
|
||||
const unsigned int extruder_id = m_sequence[idx];
|
||||
dc.SetBrush(wxBrush(color_for_extruder(m_sequence[idx])));
|
||||
const int x = rect.GetLeft() + s * stripe_w;
|
||||
const int w = (s == stripes - 1) ? (rect.GetRight() - x + 1) : stripe_w;
|
||||
dc.DrawRectangle(x, rect.GetTop(), std::max(1, w), rect.GetHeight());
|
||||
const int inset_y = vertical_inset_for_extruder(extruder_id, rect.GetHeight());
|
||||
dc.DrawRectangle(x, rect.GetTop() + inset_y, std::max(1, w), std::max(1, rect.GetHeight() - inset_y * 2));
|
||||
}
|
||||
} else {
|
||||
const int bars = 24;
|
||||
@@ -4741,7 +4763,8 @@ private:
|
||||
dc.SetBrush(wxBrush(color_for_extruder(extruder_id)));
|
||||
const int x = rect.GetLeft() + i * bar_w;
|
||||
const int w = (i == bars - 1) ? (rect.GetRight() - x + 1) : bar_w;
|
||||
dc.DrawRectangle(x, rect.GetTop(), std::max(1, w), rect.GetHeight());
|
||||
const int inset_y = vertical_inset_for_extruder(extruder_id, rect.GetHeight());
|
||||
dc.DrawRectangle(x, rect.GetTop() + inset_y, std::max(1, w), std::max(1, rect.GetHeight() - inset_y * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4782,6 +4805,7 @@ private:
|
||||
private:
|
||||
std::vector<wxColour> m_palette;
|
||||
std::vector<unsigned int> m_sequence;
|
||||
std::vector<double> m_surface_offsets_mm;
|
||||
bool m_same_layer { false };
|
||||
wxColour m_fallback { wxColour(38, 166, 154) };
|
||||
wxString m_left_overlay;
|
||||
@@ -5557,6 +5581,98 @@ int MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(const Mixe
|
||||
return std::clamp(int(std::lround(100.0 * total_b / total)), 0, 100);
|
||||
}
|
||||
|
||||
static bool mixed_filament_supports_bias_apparent_color(const MixedFilament &mf,
|
||||
const MixedFilamentPreviewSettings &preview_settings)
|
||||
{
|
||||
if (preview_settings.local_z_mode)
|
||||
return false;
|
||||
if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
||||
return false;
|
||||
if (!MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern).empty())
|
||||
return false;
|
||||
if (mf.gradient_component_ids.size() >= 3)
|
||||
return false;
|
||||
return mf.component_a >= 1 && mf.component_b >= 1 && mf.component_a != mf.component_b;
|
||||
}
|
||||
|
||||
static std::pair<int, float> mixed_filament_single_surface_offset_ui_state(const MixedFilament &mf)
|
||||
{
|
||||
const float offset_a = mf.component_a_surface_offset;
|
||||
const float offset_b = mf.component_b_surface_offset;
|
||||
const bool has_a = std::abs(offset_a) > 1e-6f;
|
||||
const bool has_b = std::abs(offset_b) > 1e-6f;
|
||||
|
||||
if (has_b && (!has_a || std::abs(offset_b) > std::abs(offset_a)))
|
||||
return { 1, offset_b };
|
||||
return { 0, has_a ? offset_a : 0.f };
|
||||
}
|
||||
|
||||
static std::pair<float, float> mixed_filament_single_surface_offset_pair(int target_index, float value)
|
||||
{
|
||||
const float clamped = std::clamp(value, -2.f, 2.f);
|
||||
return target_index == 1 ? std::make_pair(0.f, clamped) : std::make_pair(clamped, 0.f);
|
||||
}
|
||||
|
||||
static double mixed_filament_reference_nozzle_mm(unsigned int component_a,
|
||||
unsigned int component_b,
|
||||
const std::vector<double> &nozzle_diameters)
|
||||
{
|
||||
std::vector<double> samples;
|
||||
samples.reserve(2);
|
||||
|
||||
auto append_if_valid = [&samples, &nozzle_diameters](unsigned int component_id) {
|
||||
if (component_id >= 1 && component_id <= nozzle_diameters.size())
|
||||
samples.emplace_back(std::max(0.05, nozzle_diameters[size_t(component_id - 1)]));
|
||||
};
|
||||
|
||||
append_if_valid(component_a);
|
||||
append_if_valid(component_b);
|
||||
|
||||
if (samples.empty())
|
||||
return 0.4;
|
||||
return std::accumulate(samples.begin(), samples.end(), 0.0) / double(samples.size());
|
||||
}
|
||||
|
||||
static std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedFilament &mf,
|
||||
const MixedFilamentPreviewSettings &preview_settings,
|
||||
const std::vector<double> &nozzle_diameters)
|
||||
{
|
||||
const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings);
|
||||
if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings))
|
||||
return { 100 - base_b, base_b };
|
||||
|
||||
const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters);
|
||||
const int apparent_b = MixedFilamentManager::apparent_mix_b_percent(base_b,
|
||||
mf.component_a_surface_offset,
|
||||
mf.component_b_surface_offset,
|
||||
float(reference_nozzle_mm));
|
||||
return { 100 - apparent_b, apparent_b };
|
||||
}
|
||||
|
||||
static std::string mixed_filament_apparent_pair_summary(const MixedFilament &mf,
|
||||
const MixedFilamentPreviewSettings &preview_settings,
|
||||
const std::vector<double> &nozzle_diameters)
|
||||
{
|
||||
if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings))
|
||||
return {};
|
||||
|
||||
const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings);
|
||||
const int base_a = 100 - base_b;
|
||||
const auto [apparent_a, apparent_b] =
|
||||
mixed_filament_apparent_pair_percentages(mf, preview_settings, nozzle_diameters);
|
||||
|
||||
if (std::abs(mf.component_a_surface_offset - mf.component_b_surface_offset) > 1e-4f &&
|
||||
(apparent_a != base_a || apparent_b != base_b)) {
|
||||
std::ostringstream ss;
|
||||
ss << '~' << apparent_a << '/' << apparent_b;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << apparent_a << "%/" << apparent_b << '%';
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string MixedFilamentConfigPanel::summarize_sequence(const std::vector<unsigned int> &seq)
|
||||
{
|
||||
if (seq.empty()) return "";
|
||||
@@ -5764,6 +5880,7 @@ MixedFilamentConfigPanel::MixedFilamentConfigPanel(wxWindow *parent,
|
||||
const MixedFilament &mf,
|
||||
size_t num_physical,
|
||||
const std::vector<std::string> &physical_colors,
|
||||
const std::vector<double> &nozzle_diameters,
|
||||
const std::vector<wxColour> &palette,
|
||||
const MixedFilamentPreviewSettings &preview_settings,
|
||||
OnChangeFn on_change)
|
||||
@@ -5772,6 +5889,7 @@ MixedFilamentConfigPanel::MixedFilamentConfigPanel(wxWindow *parent,
|
||||
, m_mf(mf)
|
||||
, m_num_physical(num_physical)
|
||||
, m_physical_colors(physical_colors)
|
||||
, m_nozzle_diameters(nozzle_diameters)
|
||||
, m_palette(palette)
|
||||
, m_preview_settings(preview_settings)
|
||||
, m_selected_weight_state(std::make_shared<std::vector<int>>())
|
||||
@@ -5969,9 +6087,62 @@ void MixedFilamentConfigPanel::build_ui()
|
||||
auto *preview_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_mix_preview = new MixedMixPreview(this);
|
||||
m_mix_preview->SetBackgroundColour(panel_bg);
|
||||
preview_row->Add(m_mix_preview, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
preview_row->Add(m_mix_preview, 3, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
|
||||
|
||||
auto *bias_controls = new wxBoxSizer(wxHORIZONTAL);
|
||||
const auto initial_surface_offset_state = mixed_filament_single_surface_offset_ui_state(m_mf);
|
||||
|
||||
auto *surface_offset_label = new wxStaticText(this, wxID_ANY, _L("XY-Bias"));
|
||||
surface_offset_label->SetForegroundColour(is_dark ? wxColour(236, 236, 236) : wxColour(20, 20, 20));
|
||||
surface_offset_label->SetToolTip(
|
||||
_L("Additional XY offset applied only on layers where this mixed filament resolves to component A or B.\n\n"
|
||||
"Positive values contract inward. Negative values expand outward.\n\n"
|
||||
"Currently this is intended for ordinary layer/height cadence rows. Grouped wall patterns, same-layer pointillisme, and Local-Z dithering ignore it."));
|
||||
bias_controls->Add(surface_offset_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(4), gap / 2));
|
||||
|
||||
auto *target_group = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_surface_offset_target_swatch = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(10), FromDIP(10)), wxBORDER_SIMPLE);
|
||||
m_surface_offset_target_swatch->SetMinSize(wxSize(FromDIP(10), FromDIP(10)));
|
||||
m_surface_offset_target_swatch->SetMaxSize(wxSize(FromDIP(10), FromDIP(10)));
|
||||
m_surface_offset_target_swatch->SetToolTip(_L("Choose which filament in this pair receives the XY bias."));
|
||||
target_group->Add(m_surface_offset_target_swatch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(3));
|
||||
|
||||
wxArrayString surface_offset_targets;
|
||||
surface_offset_targets.Add(wxString::Format("F%d", int(m_mf.component_a)));
|
||||
surface_offset_targets.Add(wxString::Format("F%d", int(m_mf.component_b)));
|
||||
m_surface_offset_target_choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(62), -1), surface_offset_targets);
|
||||
m_surface_offset_target_choice->SetSelection(std::clamp(initial_surface_offset_state.first, 0, 1));
|
||||
m_surface_offset_target_choice->SetToolTip(_L("Choose which filament in this pair receives the XY bias."));
|
||||
target_group->Add(m_surface_offset_target_choice, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
bias_controls->Add(target_group, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
|
||||
|
||||
m_surface_offset_spin = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(64), -1),
|
||||
wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER,
|
||||
-2.0, 2.0, std::clamp(double(initial_surface_offset_state.second), -2.0, 2.0), 0.01);
|
||||
m_surface_offset_spin->SetDigits(3);
|
||||
m_surface_offset_spin->SetToolTip(
|
||||
_L("XY bias for the selected filament on layers where this row resolves to it. Positive values contract inward. Negative values expand outward."));
|
||||
bias_controls->Add(m_surface_offset_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
|
||||
auto *surface_offset_units = new wxStaticText(this, wxID_ANY, _L("mm"));
|
||||
surface_offset_units->SetForegroundColour(is_dark ? wxColour(210, 210, 210) : wxColour(72, 72, 72));
|
||||
bias_controls->Add(surface_offset_units, 0, wxALIGN_CENTER_VERTICAL);
|
||||
preview_row->Add(bias_controls, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
root->Add(preview_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
|
||||
|
||||
const auto initial_surface_offset_pair =
|
||||
mixed_filament_single_surface_offset_pair(initial_surface_offset_state.first, initial_surface_offset_state.second);
|
||||
m_mf.component_a_surface_offset = initial_surface_offset_pair.first;
|
||||
m_mf.component_b_surface_offset = initial_surface_offset_pair.second;
|
||||
|
||||
const bool initial_component_surface_offsets_supported = !pattern_row_mode &&
|
||||
row_distribution_mode != int(MixedFilament::SameLayerPointillisme) &&
|
||||
!m_preview_settings.local_z_mode;
|
||||
if (m_surface_offset_target_choice)
|
||||
m_surface_offset_target_choice->Enable(initial_component_surface_offsets_supported);
|
||||
if (m_surface_offset_spin)
|
||||
m_surface_offset_spin->Enable(initial_component_surface_offsets_supported);
|
||||
|
||||
const bool local_z_limit_supported = multi_gradient_row &&
|
||||
row_distribution_mode != int(MixedFilament::SameLayerPointillisme);
|
||||
if (local_z_limit_supported) {
|
||||
@@ -6023,6 +6194,14 @@ void MixedFilamentConfigPanel::build_ui()
|
||||
const bool preserve_same_layer_mode = m_mf.distribution_mode == int(MixedFilament::SameLayerPointillisme);
|
||||
m_mf.component_a = unsigned(a);
|
||||
m_mf.component_b = unsigned(b);
|
||||
const int surface_offset_target_index =
|
||||
m_surface_offset_target_choice ? std::clamp(m_surface_offset_target_choice->GetSelection(), 0, 1) : 0;
|
||||
const float surface_offset_value =
|
||||
m_surface_offset_spin ? std::clamp(float(m_surface_offset_spin->GetValue()), -2.f, 2.f) : 0.f;
|
||||
const auto surface_offset_pair =
|
||||
mixed_filament_single_surface_offset_pair(surface_offset_target_index, surface_offset_value);
|
||||
m_mf.component_a_surface_offset = surface_offset_pair.first;
|
||||
m_mf.component_b_surface_offset = surface_offset_pair.second;
|
||||
m_mf.local_z_max_sublayers =
|
||||
(m_local_z_limit_checkbox != nullptr && m_local_z_limit_checkbox->GetValue() && m_local_z_limit_spin != nullptr) ?
|
||||
std::max(2, m_local_z_limit_spin->GetValue()) :
|
||||
@@ -6111,6 +6290,13 @@ void MixedFilamentConfigPanel::build_ui()
|
||||
m_mf.custom = true;
|
||||
|
||||
const std::vector<unsigned int> selected_gradient_ids = decode_gradient_ids(m_mf.gradient_component_ids);
|
||||
const bool component_surface_offsets_supported = (m_pattern_ctrl == nullptr) &&
|
||||
!same_layer_mode &&
|
||||
!m_preview_settings.local_z_mode;
|
||||
if (m_surface_offset_target_choice)
|
||||
m_surface_offset_target_choice->Enable(component_surface_offsets_supported);
|
||||
if (m_surface_offset_spin)
|
||||
m_surface_offset_spin->Enable(component_surface_offsets_supported);
|
||||
if (preview_sequence.empty())
|
||||
preview_sequence = build_weighted_pair_sequence(m_mf.component_a, m_mf.component_b, preview_mix_b_percent, same_layer_mode);
|
||||
|
||||
@@ -6125,7 +6311,17 @@ void MixedFilamentConfigPanel::build_ui()
|
||||
m_blend_selector->set_multi_preview(corner_colors, *m_selected_weight_state);
|
||||
}
|
||||
|
||||
if (selected_gradient_ids.size() >= 3 || !preview_sequence.empty()) {
|
||||
if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings) &&
|
||||
m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
|
||||
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
|
||||
const auto [apparent_pct_a, apparent_pct_b] =
|
||||
mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters);
|
||||
m_mf.display_color = MixedFilamentManager::blend_color(
|
||||
m_physical_colors[size_t(m_mf.component_a - 1)],
|
||||
m_physical_colors[size_t(m_mf.component_b - 1)],
|
||||
apparent_pct_a,
|
||||
apparent_pct_b);
|
||||
} else if (selected_gradient_ids.size() >= 3 || !preview_sequence.empty()) {
|
||||
m_mf.display_color = blend_from_sequence(m_physical_colors, preview_sequence, "#26A69A");
|
||||
if (m_blend_label) {
|
||||
if (selected_gradient_ids.size() >= 3) {
|
||||
@@ -6148,8 +6344,14 @@ void MixedFilamentConfigPanel::build_ui()
|
||||
}
|
||||
|
||||
if (m_mix_preview) {
|
||||
const std::string summary = summarize_sequence(preview_sequence);
|
||||
m_mix_preview->set_data(m_palette, preview_sequence, same_layer_mode, wxColour(m_mf.display_color),
|
||||
const std::string bias_summary = mixed_filament_apparent_pair_summary(m_mf, m_preview_settings, m_nozzle_diameters);
|
||||
const std::string summary = bias_summary.empty() ? summarize_sequence(preview_sequence) : bias_summary;
|
||||
std::vector<double> preview_surface_offsets(m_palette.size() + 1, 0.0);
|
||||
if (m_mf.component_a >= 1 && m_mf.component_a < preview_surface_offsets.size())
|
||||
preview_surface_offsets[m_mf.component_a] = std::max(0.0, double(m_mf.component_a_surface_offset));
|
||||
if (m_mf.component_b >= 1 && m_mf.component_b < preview_surface_offsets.size())
|
||||
preview_surface_offsets[m_mf.component_b] = std::max(0.0, double(m_mf.component_b_surface_offset));
|
||||
m_mix_preview->set_data(m_palette, preview_sequence, same_layer_mode, preview_surface_offsets, wxColour(m_mf.display_color),
|
||||
_L("Preview"), summary.empty() ? wxString() : from_u8(summary));
|
||||
}
|
||||
update_local_z_breakdown();
|
||||
@@ -6246,6 +6448,16 @@ void MixedFilamentConfigPanel::build_ui()
|
||||
evt.Skip();
|
||||
});
|
||||
}
|
||||
if (m_surface_offset_target_choice)
|
||||
m_surface_offset_target_choice->Bind(wxEVT_CHOICE, [apply_changes](wxCommandEvent &) { apply_changes(); });
|
||||
if (m_surface_offset_spin) {
|
||||
m_surface_offset_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_changes](wxSpinDoubleEvent &) { apply_changes(); });
|
||||
m_surface_offset_spin->Bind(wxEVT_TEXT_ENTER, [apply_changes](wxCommandEvent &) { apply_changes(); });
|
||||
m_surface_offset_spin->Bind(wxEVT_KILL_FOCUS, [apply_changes](wxFocusEvent &evt) {
|
||||
apply_changes();
|
||||
evt.Skip();
|
||||
});
|
||||
}
|
||||
|
||||
if (m_blend_selector) {
|
||||
m_blend_selector->Bind(wxEVT_BUTTON, [this, apply_changes](wxCommandEvent&) {
|
||||
@@ -6344,6 +6556,24 @@ void MixedFilamentConfigPanel::update_component_picker_visuals()
|
||||
update_one(m_choice_b, m_picker_b_container, m_picker_b_swatch, m_picker_b_label);
|
||||
update_one(m_choice_c, m_picker_c_container, m_picker_c_swatch, m_picker_c_label);
|
||||
update_one(m_choice_d, m_picker_d_container, m_picker_d_swatch, m_picker_d_label);
|
||||
|
||||
if (m_surface_offset_target_choice) {
|
||||
const int a_filament = std::clamp(m_choice_a ? (m_choice_a->GetSelection() + 1) : int(m_mf.component_a), 1, int(std::max<size_t>(1, m_num_physical)));
|
||||
const int b_filament = std::clamp(m_choice_b ? (m_choice_b->GetSelection() + 1) : int(m_mf.component_b), 1, int(std::max<size_t>(1, m_num_physical)));
|
||||
if (m_surface_offset_target_choice->GetCount() >= 2) {
|
||||
m_surface_offset_target_choice->SetString(0, wxString::Format("F%d", a_filament));
|
||||
m_surface_offset_target_choice->SetString(1, wxString::Format("F%d", b_filament));
|
||||
}
|
||||
|
||||
const int selected_target = std::clamp(m_surface_offset_target_choice->GetSelection(), 0, 1);
|
||||
const int color_idx = (selected_target == 1 ? b_filament : a_filament) - 1;
|
||||
const wxColour color = (color_idx >= 0 && size_t(color_idx) < m_palette.size()) ? m_palette[size_t(color_idx)] : wxColour("#26A69A");
|
||||
if (m_surface_offset_target_swatch) {
|
||||
m_surface_offset_target_swatch->SetBackgroundColour(color);
|
||||
m_surface_offset_target_swatch->Refresh();
|
||||
}
|
||||
m_surface_offset_target_choice->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void MixedFilamentConfigPanel::update_preview()
|
||||
@@ -6383,8 +6613,26 @@ void MixedFilamentConfigPanel::update_preview()
|
||||
}
|
||||
|
||||
if (m_mix_preview) {
|
||||
const std::string summary = summarize_sequence(initial_sequence);
|
||||
m_mix_preview->set_data(m_palette, initial_sequence, same_layer_mode, wxColour(m_mf.display_color),
|
||||
if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings) &&
|
||||
m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
|
||||
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
|
||||
const auto [apparent_pct_a, apparent_pct_b] =
|
||||
mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters);
|
||||
m_mf.display_color = MixedFilamentManager::blend_color(
|
||||
m_physical_colors[size_t(m_mf.component_a - 1)],
|
||||
m_physical_colors[size_t(m_mf.component_b - 1)],
|
||||
apparent_pct_a,
|
||||
apparent_pct_b);
|
||||
}
|
||||
|
||||
const std::string bias_summary = mixed_filament_apparent_pair_summary(m_mf, m_preview_settings, m_nozzle_diameters);
|
||||
const std::string summary = bias_summary.empty() ? summarize_sequence(initial_sequence) : bias_summary;
|
||||
std::vector<double> preview_surface_offsets(m_palette.size() + 1, 0.0);
|
||||
if (m_mf.component_a >= 1 && m_mf.component_a < preview_surface_offsets.size())
|
||||
preview_surface_offsets[m_mf.component_a] = std::max(0.0, double(m_mf.component_a_surface_offset));
|
||||
if (m_mf.component_b >= 1 && m_mf.component_b < preview_surface_offsets.size())
|
||||
preview_surface_offsets[m_mf.component_b] = std::max(0.0, double(m_mf.component_b_surface_offset));
|
||||
m_mix_preview->set_data(m_palette, initial_sequence, same_layer_mode, preview_surface_offsets, wxColour(m_mf.display_color),
|
||||
_L("Preview"), summary.empty() ? wxString() : from_u8(summary));
|
||||
}
|
||||
update_local_z_breakdown();
|
||||
@@ -6543,6 +6791,14 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
|
||||
ConfigOptionStrings *color_opt = preset_bundle->project_config.option<ConfigOptionStrings>("filament_colour");
|
||||
std::vector<std::string> physical_colors = color_opt ? color_opt->values : std::vector<std::string>();
|
||||
physical_colors.resize(num_physical, "#26A69A");
|
||||
std::vector<double> nozzle_diameters(num_physical, 0.4);
|
||||
if (const ConfigOptionFloats *opt = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")) {
|
||||
const size_t opt_count = opt->values.size();
|
||||
if (opt_count > 0) {
|
||||
for (size_t i = 0; i < num_physical; ++i)
|
||||
nozzle_diameters[i] = std::max(0.05, opt->get_at(unsigned(std::min(i, opt_count - 1))));
|
||||
}
|
||||
}
|
||||
|
||||
auto get_mixed_bool = [preset_bundle, print_cfg](const std::string &key, bool fallback) {
|
||||
if (const ConfigOptionBool *opt = preset_bundle->project_config.option<ConfigOptionBool>(key))
|
||||
@@ -6929,7 +7185,20 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
|
||||
const bool same_layer_mode = entry.distribution_mode == int(MixedFilament::SameLayerPointillisme);
|
||||
return build_effective_pair_preview_sequence(entry.component_a, entry.component_b, effective_mix_b, same_layer_mode);
|
||||
};
|
||||
auto compute_entry_display_color = [num_physical, &physical_colors, blend_from_sequence, build_entry_preview_sequence](const MixedFilament &entry) {
|
||||
auto compute_entry_display_color = [num_physical, &physical_colors, &nozzle_diameters, blend_from_sequence, build_entry_preview_sequence, preview_settings](const MixedFilament &entry) {
|
||||
if (mixed_filament_supports_bias_apparent_color(entry, preview_settings) &&
|
||||
entry.component_a >= 1 && entry.component_b >= 1 &&
|
||||
entry.component_a <= num_physical && entry.component_b <= num_physical &&
|
||||
entry.component_a <= physical_colors.size() && entry.component_b <= physical_colors.size()) {
|
||||
const auto [apparent_pct_a, apparent_pct_b] =
|
||||
mixed_filament_apparent_pair_percentages(entry, preview_settings, nozzle_diameters);
|
||||
return MixedFilamentManager::blend_color(
|
||||
physical_colors[entry.component_a - 1],
|
||||
physical_colors[entry.component_b - 1],
|
||||
apparent_pct_a,
|
||||
apparent_pct_b);
|
||||
}
|
||||
|
||||
const std::vector<unsigned int> sequence = build_entry_preview_sequence(entry);
|
||||
if (!sequence.empty())
|
||||
return blend_from_sequence(physical_colors, sequence, "#26A69A");
|
||||
@@ -7371,7 +7640,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
|
||||
return row->GetClientRect().Contains(local);
|
||||
};
|
||||
|
||||
auto ensure_editor = [this, mixed_id, num_physical, physical_colors, palette, preview_settings, preset_bundle,
|
||||
auto ensure_editor = [this, mixed_id, num_physical, physical_colors, nozzle_diameters, palette, preview_settings, preset_bundle,
|
||||
editor_host, editor_sizer, swatch, summary_label, header_panel, row,
|
||||
rows_scroller, mixed_summary_text, apply_mixed_entry_changes]() {
|
||||
if (!preset_bundle || !editor_sizer || editor_sizer->GetItemCount() > 0)
|
||||
@@ -7382,7 +7651,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
|
||||
if (mixed_id >= mfs.size())
|
||||
return;
|
||||
|
||||
auto *editor = new MixedFilamentConfigPanel(editor_host, mixed_id, mfs[mixed_id], num_physical, physical_colors, palette, preview_settings,
|
||||
auto *editor = new MixedFilamentConfigPanel(editor_host, mixed_id, mfs[mixed_id], num_physical, physical_colors, nozzle_diameters, palette, preview_settings,
|
||||
[this, mixed_id, swatch, summary_label, header_panel, row, rows_scroller, mixed_summary_text, apply_mixed_entry_changes](const MixedFilament &updated_mf) {
|
||||
apply_mixed_entry_changes(mixed_id, updated_mf, true);
|
||||
|
||||
@@ -19337,6 +19606,38 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
|
||||
// if physical_printer is selected, send gcode for this printer
|
||||
// DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
|
||||
|
||||
auto prepare_upload_filename_for_dialog = [this, use_3mf](fs::path output_file) {
|
||||
output_file = fs::path(Slic3r::fold_utf8_to_ascii(output_file.string()));
|
||||
if (use_3mf)
|
||||
output_file.replace_extension("3mf");
|
||||
|
||||
PartPlate *current_plate = this->get_partplate_list().get_curr_plate();
|
||||
if (current_plate != nullptr) {
|
||||
const Print *current_print = current_plate->fff_print();
|
||||
if (current_print != nullptr && !current_print->print_statistics().estimated_normal_print_time.empty())
|
||||
return fs::path(current_print->print_statistics().finalize_output_path(output_file.string()));
|
||||
}
|
||||
|
||||
if (current_plate != nullptr && current_plate->is_slice_result_valid() && current_plate->get_slice_result() != nullptr) {
|
||||
const auto &estimated_stats = current_plate->get_slice_result()->print_statistics;
|
||||
const float normal_time = estimated_stats.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time;
|
||||
if (normal_time > 0.0f) {
|
||||
std::string filename = output_file.string();
|
||||
const std::string normal_time_str = short_time(get_time_dhms(normal_time));
|
||||
boost::replace_all(filename, "{print_time}", normal_time_str);
|
||||
boost::replace_all(filename, "{normal_print_time}", normal_time_str);
|
||||
|
||||
const float silent_time = estimated_stats.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].time;
|
||||
if (silent_time > 0.0f)
|
||||
boost::replace_all(filename, "{silent_print_time}", short_time(get_time_dhms(silent_time)));
|
||||
|
||||
output_file = fs::path(filename);
|
||||
}
|
||||
}
|
||||
|
||||
return output_file;
|
||||
};
|
||||
|
||||
// 校验机型
|
||||
auto devices = wxGetApp().app_config->get_devices();
|
||||
std::string connect_preset = "";
|
||||
@@ -19403,10 +19704,7 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
|
||||
show_error(this, ex.what(), false);
|
||||
return;
|
||||
}
|
||||
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
|
||||
if (use_3mf) {
|
||||
default_output_file.replace_extension("3mf");
|
||||
}
|
||||
default_output_file = prepare_upload_filename_for_dialog(std::move(default_output_file));
|
||||
|
||||
// 获取文件路径
|
||||
auto file_path = get_partplate_list().get_curr_plate()->get_tmp_gcode_path();
|
||||
@@ -19478,10 +19776,7 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
|
||||
show_error(this, ex.what(), false);
|
||||
return;
|
||||
}
|
||||
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
|
||||
if (use_3mf) {
|
||||
default_output_file.replace_extension("3mf");
|
||||
}
|
||||
default_output_file = prepare_upload_filename_for_dialog(std::move(default_output_file));
|
||||
|
||||
// Repetier specific: Query the server for the list of file groups.
|
||||
wxArrayString groups;
|
||||
|
||||
Reference in New Issue
Block a user