This commit is contained in:
Rad
2026-04-12 21:46:48 +02:00
parent 5bd47c6031
commit 1da14997b3
5 changed files with 738 additions and 22 deletions

View File

@@ -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],

View File

@@ -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 ------------------------------------------------------

View File

@@ -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

View File

@@ -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;