mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-17 18:42:24 +00:00
Update from FS at b3c41fda4.
Slicing
- align merge_segmented_layers shape with FS apply_mm_segmentation
(size = num_facets_states, loop from 0, no -1 shift); painted mixed
regions were previously attributed to filament_id-1 of intent.
apply_fuzzy_skin_segmentation reads channel 1;
apply_mixed_surface_indentation uses segmentation_channel_filament_id
- port apply_mixed_surface_indentation, apply_mixed_component_surface_offsets,
apply_mixed_region_surface_offsets, apply_surface_emboss_mixed_region_override,
plus surface_emboss_mixed_* debug subsystem
- refactor apply_mm_segmentation (by-value MM, bias_mode, surface-type-
preserving intersection, region normalization, post-MM dump); hoist MM
segmentation into slice_volumes so mixed apply_* flow can mutate it
- restore clear_local_z_plan() invalidation hooks
(PrintObject.cpp:805/1264/1286)
GCode
- add LayerTools::preserve_extruder_order, honored by collect_extruders,
both reorder_extruders overloads, and
reorder_filaments_for_minimum_flush_volume; helpers
append_unique_preserve_order / remove_duplicates_preserve_order
- wire MixedFilamentManager::ordered_perimeter_extruders for grouped
manual-pattern walls; set preserve_extruder_order when >= 2
- mixed-aware support: layer_height set for support-only layers,
ExtrusionRole-based has_support/has_interface with erMixed short-
circuit, support_filament / support_interface_filament routed through
resolve_mixed
Print
- materialize mixed_filament_pointillism_{pixel_size,line_gap} in
PrintApply's option-tracking block so in-session edits diff correctly
GUI
- Tab::on_value_change: dithering_local_z_mode cascading clears, 17-key
project_config sync, update_mixed_filament_panel(false) on
mixed_filament_component_bias_enabled change
- GUI_Factories: physical_filaments_count, ui_ordered_filament_ids,
filament_menu_item_name; filaments_count includes enabled mixed
virtuals; right-click 'Change filament' submenus iterate UI-ordered IDs
Tests
- sentinel asserting MultiMaterialSegmentation uses FS-aligned shape
(39 -> 40)
This commit is contained in:
@@ -90,6 +90,12 @@ bool has_grouped_manual_pattern(const MixedFilamentManager *mixed_mgr,
|
|||||||
return normalized.find(',') != std::string::npos;
|
return normalized.find(',') != std::string::npos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void append_unique_preserve_order(std::vector<unsigned int> &dst, unsigned int value)
|
||||||
|
{
|
||||||
|
if (std::find(dst.begin(), dst.end(), value) == dst.end())
|
||||||
|
dst.emplace_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
bool internal_solid_infill_uses_sparse_filament(const PrintRegion ®ion, ExtrusionRole role)
|
bool internal_solid_infill_uses_sparse_filament(const PrintRegion ®ion, ExtrusionRole role)
|
||||||
{
|
{
|
||||||
return role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON;
|
return role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON;
|
||||||
@@ -141,6 +147,15 @@ unsigned int grouped_manual_pattern_infill_filament_1based(const LayerTools& la
|
|||||||
float(layer_tools.layer_height));
|
float(layer_tools.layer_height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void remove_duplicates_preserve_order(std::vector<unsigned int> &values)
|
||||||
|
{
|
||||||
|
std::vector<unsigned int> ordered;
|
||||||
|
ordered.reserve(values.size());
|
||||||
|
for (unsigned int value : values)
|
||||||
|
append_unique_preserve_order(ordered, value);
|
||||||
|
values = std::move(ordered);
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -323,27 +338,29 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& too
|
|||||||
// Reorder the extruders of first layer
|
// Reorder the extruders of first layer
|
||||||
{
|
{
|
||||||
LayerTools& lt = m_layer_tools[0];
|
LayerTools& lt = m_layer_tools[0];
|
||||||
std::vector<unsigned int> layer0_extruders = lt.extruders;
|
if (!lt.preserve_extruder_order) {
|
||||||
lt.extruders.clear();
|
std::vector<unsigned int> layer0_extruders = lt.extruders;
|
||||||
for (unsigned int extruder_id : tool_order_layer0) {
|
lt.extruders.clear();
|
||||||
auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id);
|
for (unsigned int extruder_id : tool_order_layer0) {
|
||||||
if (iter != layer0_extruders.end()) {
|
auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id);
|
||||||
lt.extruders.push_back(extruder_id);
|
if (iter != layer0_extruders.end()) {
|
||||||
*iter = (unsigned int)-1;
|
lt.extruders.push_back(extruder_id);
|
||||||
|
*iter = (unsigned int)-1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned int extruder_id : layer0_extruders) {
|
for (unsigned int extruder_id : layer0_extruders) {
|
||||||
if (extruder_id == 0)
|
if (extruder_id == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (extruder_id != (unsigned int)-1)
|
if (extruder_id != (unsigned int)-1)
|
||||||
lt.extruders.push_back(extruder_id);
|
lt.extruders.push_back(extruder_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// all extruders are zero
|
// all extruders are zero
|
||||||
if (lt.extruders.empty()) {
|
if (lt.extruders.empty()) {
|
||||||
lt.extruders.push_back(tool_order_layer0[0]);
|
lt.extruders.push_back(tool_order_layer0[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,6 +376,10 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& too
|
|||||||
if (lt.extruders.front() == 0)
|
if (lt.extruders.front() == 0)
|
||||||
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
||||||
lt.extruders.erase(lt.extruders.begin());
|
lt.extruders.erase(lt.extruders.begin());
|
||||||
|
if (lt.preserve_extruder_order) {
|
||||||
|
last_extruder_id = lt.extruders.back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Reorder the extruders to start with the last one.
|
// Reorder the extruders to start with the last one.
|
||||||
for (size_t i = 1; i < lt.extruders.size(); ++i)
|
for (size_t i = 1; i < lt.extruders.size(); ++i)
|
||||||
if (lt.extruders[i] == last_extruder_id) {
|
if (lt.extruders[i] == last_extruder_id) {
|
||||||
@@ -413,6 +434,10 @@ void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id)
|
|||||||
if (lt.extruders.front() == 0)
|
if (lt.extruders.front() == 0)
|
||||||
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
||||||
lt.extruders.erase(lt.extruders.begin());
|
lt.extruders.erase(lt.extruders.begin());
|
||||||
|
if (lt.preserve_extruder_order) {
|
||||||
|
last_extruder_id = lt.extruders.back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Reorder the extruders to start with the last one.
|
// Reorder the extruders to start with the last one.
|
||||||
for (size_t i = 1; i < lt.extruders.size(); ++ i)
|
for (size_t i = 1; i < lt.extruders.size(); ++ i)
|
||||||
if (lt.extruders[i] == last_extruder_id) {
|
if (lt.extruders[i] == last_extruder_id) {
|
||||||
@@ -874,19 +899,40 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (something_nonoverriddable){
|
if (something_nonoverriddable){
|
||||||
if (extruder_override == 0) {
|
const unsigned int configured_wall = (extruder_override == 0)
|
||||||
layer_tools.extruders.emplace_back(layer_tools.wall_filament(region) + 1);
|
? region.config().wall_filament.value
|
||||||
if (layerCount == 0) {
|
: extruder_override;
|
||||||
firstLayerExtruders.emplace_back(layer_tools.wall_filament(region) + 1);
|
unsigned int wall_ext = resolve_mixed(configured_wall,
|
||||||
|
layerCount,
|
||||||
|
float(layer->print_z),
|
||||||
|
float(layer->height));
|
||||||
|
const unsigned int grouped_id =
|
||||||
|
grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall);
|
||||||
|
if (grouped_id != 0) {
|
||||||
|
const std::vector<unsigned int> ordered =
|
||||||
|
m_mixed_mgr->ordered_perimeter_extruders(grouped_id,
|
||||||
|
m_num_physical,
|
||||||
|
layerCount,
|
||||||
|
float(layer->print_z),
|
||||||
|
float(layer->height));
|
||||||
|
if (!ordered.empty()) {
|
||||||
|
if (ordered.size() >= 2)
|
||||||
|
layer_tools.preserve_extruder_order = true;
|
||||||
|
for (unsigned int extruder_id : ordered) {
|
||||||
|
layer_tools.extruders.emplace_back(extruder_id);
|
||||||
|
if (layerCount == 0 &&
|
||||||
|
std::find(firstLayerExtruders.begin(), firstLayerExtruders.end(), int(extruder_id)) == firstLayerExtruders.end())
|
||||||
|
firstLayerExtruders.emplace_back(int(extruder_id));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
layer_tools.extruders.emplace_back(wall_ext);
|
||||||
|
if (layerCount == 0)
|
||||||
|
firstLayerExtruders.emplace_back(wall_ext);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const unsigned int resolved = resolve_mixed(extruder_override,
|
layer_tools.extruders.emplace_back(wall_ext);
|
||||||
layerCount,
|
|
||||||
float(layer->print_z),
|
|
||||||
float(layer->height));
|
|
||||||
layer_tools.extruders.emplace_back(resolved);
|
|
||||||
if (layerCount == 0)
|
if (layerCount == 0)
|
||||||
firstLayerExtruders.emplace_back(resolved);
|
firstLayerExtruders.emplace_back(wall_ext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,19 +981,24 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
|||||||
|
|
||||||
// Collect the support extruders.
|
// Collect the support extruders.
|
||||||
for (auto support_layer : object.support_layers()) {
|
for (auto support_layer : object.support_layers()) {
|
||||||
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
|
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
|
||||||
ExtrusionRole role = support_layer->support_fills.role();
|
layer_tools.layer_height = support_layer->height;
|
||||||
bool has_support = false;
|
ExtrusionRole role = support_layer->support_fills.role();
|
||||||
bool has_interface = false;
|
bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition;
|
||||||
for (const ExtrusionEntity *ee : support_layer->support_fills.entities) {
|
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
|
||||||
ExtrusionRole er = ee->role();
|
|
||||||
if (er == erSupportMaterial || er == erSupportTransition) has_support = true;
|
unsigned int extruder_support = resolve_mixed(object.config().support_filament.value,
|
||||||
if (er == erSupportMaterialInterface) has_interface = true;
|
layer_tools.layer_index,
|
||||||
if (has_support && has_interface) break;
|
float(support_layer->print_z),
|
||||||
}
|
float(support_layer->height));
|
||||||
unsigned int extruder_support = object.config().support_filament.value;
|
unsigned int extruder_interface = resolve_mixed(object.config().support_interface_filament.value,
|
||||||
unsigned int extruder_interface = object.config().support_interface_filament.value;
|
layer_tools.layer_index,
|
||||||
|
float(support_layer->print_z),
|
||||||
|
float(support_layer->height));
|
||||||
|
|
||||||
if (has_support) {
|
if (has_support) {
|
||||||
|
// BP-only fallback: when support_filament is unset and an interface
|
||||||
|
// exists, pick the lowest-flush non-soluble body extruder.
|
||||||
if (extruder_support > 0 || !has_interface || extruder_interface == 0 || layer_tools.has_object)
|
if (extruder_support > 0 || !has_interface || extruder_interface == 0 || layer_tools.has_object)
|
||||||
layer_tools.extruders.push_back(extruder_support);
|
layer_tools.extruders.push_back(extruder_support);
|
||||||
else {
|
else {
|
||||||
@@ -956,7 +1007,6 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
|||||||
std::vector<float> flush_matrix(
|
std::vector<float> flush_matrix(
|
||||||
cast<float>(get_flush_volumes_matrix(object.print()->config().flush_volumes_matrix.values, 0, object.print()->config().nozzle_diameter.values.size())));
|
cast<float>(get_flush_volumes_matrix(object.print()->config().flush_volumes_matrix.values, 0, object.print()->config().nozzle_diameter.values.size())));
|
||||||
const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON);
|
const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON);
|
||||||
// Extract purging volumes for each extruder pair:
|
|
||||||
std::vector<std::vector<float>> wipe_volumes;
|
std::vector<std::vector<float>> wipe_volumes;
|
||||||
for (unsigned int i = 0; i < number_of_extruders; ++i)
|
for (unsigned int i = 0; i < number_of_extruders; ++i)
|
||||||
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders));
|
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders));
|
||||||
@@ -983,8 +1033,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto& layer : m_layer_tools) {
|
for (auto& layer : m_layer_tools) {
|
||||||
// Sort and remove duplicates
|
if (layer.preserve_extruder_order)
|
||||||
sort_remove_duplicates(layer.extruders);
|
remove_duplicates_preserve_order(layer.extruders);
|
||||||
|
else
|
||||||
|
sort_remove_duplicates(layer.extruders);
|
||||||
|
|
||||||
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
|
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
|
||||||
if (layer.extruders.empty() && layer.has_object)
|
if (layer.extruders.empty() && layer.has_object)
|
||||||
@@ -1539,6 +1591,9 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
|||||||
&filament_sequences
|
&filament_sequences
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO(fs-port): for layers with preserve_extruder_order=true the stats
|
||||||
|
// here reflect the optimized sequence; the guarded writeback below keeps
|
||||||
|
// the original ordering. UI-only divergence — see line ~1610 below.
|
||||||
auto curr_flush_info = calc_filament_change_info_by_toolorder(print_config, filament_maps, nozzle_flush_mtx, filament_sequences);
|
auto curr_flush_info = calc_filament_change_info_by_toolorder(print_config, filament_maps, nozzle_flush_mtx, filament_sequences);
|
||||||
if (nozzle_nums <= 1)
|
if (nozzle_nums <= 1)
|
||||||
m_stats_by_single_extruder = curr_flush_info;
|
m_stats_by_single_extruder = curr_flush_info;
|
||||||
@@ -1580,8 +1635,19 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < filament_sequences.size(); ++i)
|
for (size_t i = 0; i < filament_sequences.size(); ++i) {
|
||||||
|
// FS preserve_extruder_order guard: keep the layer's existing extruder
|
||||||
|
// ordering by skipping writeback of the optimized sequence.
|
||||||
|
//
|
||||||
|
// Note: BP runs the optimizer (and updates stats) BEFORE this writeback
|
||||||
|
// because the optimizer is a free function in ToolOrderUtils.cpp without
|
||||||
|
// LayerTools access. Stats may diverge from g-code reality on
|
||||||
|
// preserve-order layers. See FS ToolOrdering.cpp:1156-1159 for the
|
||||||
|
// upstream behavior that guards earlier in the call chain.
|
||||||
|
if (m_layer_tools[i].preserve_extruder_order)
|
||||||
|
continue;
|
||||||
m_layer_tools[i].extruders = std::move(filament_sequences[i]);
|
m_layer_tools[i].extruders = std::move(filament_sequences[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
||||||
void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height)
|
void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height)
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ public:
|
|||||||
bool has_support = false;
|
bool has_support = false;
|
||||||
// Zero based extruder IDs, ordered to minimize tool switches.
|
// Zero based extruder IDs, ordered to minimize tool switches.
|
||||||
std::vector<unsigned int> extruders;
|
std::vector<unsigned int> extruders;
|
||||||
|
// When set, downstream reorder passes leave this layer's extruder
|
||||||
|
// sequence in place (used by grouped manual mixed-filament patterns).
|
||||||
|
bool preserve_extruder_order = false;
|
||||||
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
|
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
|
||||||
// If not overriden, it is set to 0.
|
// If not overriden, it is set to 0.
|
||||||
unsigned int extruder_override = 0;
|
unsigned int extruder_override = 0;
|
||||||
|
|||||||
@@ -1829,15 +1829,14 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
|
|||||||
{
|
{
|
||||||
const size_t num_layers = segmented_regions.size();
|
const size_t num_layers = segmented_regions.size();
|
||||||
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
|
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
|
||||||
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_facets_states - 1));
|
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_facets_states));
|
||||||
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
|
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
|
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
||||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||||
assert(segmented_regions[layer_idx].size() == num_facets_states);
|
assert(segmented_regions[layer_idx].size() == num_facets_states);
|
||||||
// Zero is skipped because it is the default color of the volume
|
for (size_t extruder_id = 0; extruder_id < num_facets_states; ++extruder_id) {
|
||||||
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
|
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
||||||
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
|
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
|
||||||
@@ -1849,16 +1848,16 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
|
segmented_regions_merged[layer_idx][extruder_id] = std::move(segmented_regions_trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
||||||
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
|
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id].empty();
|
||||||
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
|
append(segmented_regions_merged[layer_idx][extruder_id], top_and_bottom_layers[extruder_id][layer_idx]);
|
||||||
|
|
||||||
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
|
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
|
||||||
if (!was_top_and_bottom_empty)
|
if (!was_top_and_bottom_empty)
|
||||||
segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
segmented_regions_merged[layer_idx][extruder_id] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1174,6 +1174,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
new_full_config.option("mixed_filament_height_lower_bound", true);
|
new_full_config.option("mixed_filament_height_lower_bound", true);
|
||||||
new_full_config.option("mixed_filament_height_upper_bound", true);
|
new_full_config.option("mixed_filament_height_upper_bound", true);
|
||||||
new_full_config.option("mixed_filament_advanced_dithering", true);
|
new_full_config.option("mixed_filament_advanced_dithering", true);
|
||||||
|
new_full_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||||
|
new_full_config.option("mixed_filament_pointillism_line_gap", true);
|
||||||
new_full_config.option("mixed_filament_component_bias_enabled", true);
|
new_full_config.option("mixed_filament_component_bias_enabled", true);
|
||||||
new_full_config.option("mixed_filament_surface_indentation", true);
|
new_full_config.option("mixed_filament_surface_indentation", true);
|
||||||
new_full_config.option("mixed_filament_region_collapse", true);
|
new_full_config.option("mixed_filament_region_collapse", true);
|
||||||
@@ -1190,6 +1192,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
m_config.option("mixed_filament_height_lower_bound", true);
|
m_config.option("mixed_filament_height_lower_bound", true);
|
||||||
m_config.option("mixed_filament_height_upper_bound", true);
|
m_config.option("mixed_filament_height_upper_bound", true);
|
||||||
m_config.option("mixed_filament_advanced_dithering", true);
|
m_config.option("mixed_filament_advanced_dithering", true);
|
||||||
|
m_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||||
|
m_config.option("mixed_filament_pointillism_line_gap", true);
|
||||||
m_config.option("mixed_filament_component_bias_enabled", true);
|
m_config.option("mixed_filament_component_bias_enabled", true);
|
||||||
m_config.option("mixed_filament_surface_indentation", true);
|
m_config.option("mixed_filament_surface_indentation", true);
|
||||||
m_config.option("mixed_filament_region_collapse", true);
|
m_config.option("mixed_filament_region_collapse", true);
|
||||||
@@ -1206,6 +1210,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
m_default_object_config.option("mixed_filament_height_lower_bound", true);
|
m_default_object_config.option("mixed_filament_height_lower_bound", true);
|
||||||
m_default_object_config.option("mixed_filament_height_upper_bound", true);
|
m_default_object_config.option("mixed_filament_height_upper_bound", true);
|
||||||
m_default_object_config.option("mixed_filament_advanced_dithering", true);
|
m_default_object_config.option("mixed_filament_advanced_dithering", true);
|
||||||
|
m_default_object_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||||
|
m_default_object_config.option("mixed_filament_pointillism_line_gap", true);
|
||||||
m_default_object_config.option("mixed_filament_component_bias_enabled", true);
|
m_default_object_config.option("mixed_filament_component_bias_enabled", true);
|
||||||
m_default_object_config.option("mixed_filament_surface_indentation", true);
|
m_default_object_config.option("mixed_filament_surface_indentation", true);
|
||||||
m_default_object_config.option("mixed_filament_region_collapse", true);
|
m_default_object_config.option("mixed_filament_region_collapse", true);
|
||||||
@@ -1374,6 +1380,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
float mixed_height_lower = 0.04f;
|
float mixed_height_lower = 0.04f;
|
||||||
float mixed_height_upper = 0.16f;
|
float mixed_height_upper = 0.16f;
|
||||||
bool mixed_advanced_dither = false;
|
bool mixed_advanced_dither = false;
|
||||||
|
float mixed_pointillism_pixel_size = 0.f;
|
||||||
|
float mixed_pointillism_line_gap = 0.f;
|
||||||
float mixed_surface_indentation = 0.f;
|
float mixed_surface_indentation = 0.f;
|
||||||
std::string mixed_custom_definitions;
|
std::string mixed_custom_definitions;
|
||||||
|
|
||||||
@@ -1393,6 +1401,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
else
|
else
|
||||||
mixed_advanced_dither = (new_full_config.opt_int("mixed_filament_advanced_dithering") != 0);
|
mixed_advanced_dither = (new_full_config.opt_int("mixed_filament_advanced_dithering") != 0);
|
||||||
}
|
}
|
||||||
|
if (new_full_config.has("mixed_filament_pointillism_pixel_size"))
|
||||||
|
mixed_pointillism_pixel_size = float(new_full_config.opt_float("mixed_filament_pointillism_pixel_size"));
|
||||||
|
if (new_full_config.has("mixed_filament_pointillism_line_gap"))
|
||||||
|
mixed_pointillism_line_gap = float(new_full_config.opt_float("mixed_filament_pointillism_line_gap"));
|
||||||
if (new_full_config.has("mixed_filament_surface_indentation"))
|
if (new_full_config.has("mixed_filament_surface_indentation"))
|
||||||
mixed_surface_indentation = float(new_full_config.opt_float("mixed_filament_surface_indentation"));
|
mixed_surface_indentation = float(new_full_config.opt_float("mixed_filament_surface_indentation"));
|
||||||
if (new_full_config.has("mixed_filament_definitions"))
|
if (new_full_config.has("mixed_filament_definitions"))
|
||||||
@@ -1401,6 +1413,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
mixed_gradient_mode = std::clamp(mixed_gradient_mode, 0, 1);
|
mixed_gradient_mode = std::clamp(mixed_gradient_mode, 0, 1);
|
||||||
mixed_height_lower = std::max(0.01f, mixed_height_lower);
|
mixed_height_lower = std::max(0.01f, mixed_height_lower);
|
||||||
mixed_height_upper = std::max(mixed_height_lower, mixed_height_upper);
|
mixed_height_upper = std::max(mixed_height_lower, mixed_height_upper);
|
||||||
|
mixed_pointillism_pixel_size = std::max(0.f, mixed_pointillism_pixel_size);
|
||||||
|
mixed_pointillism_line_gap = std::max(0.f, mixed_pointillism_line_gap);
|
||||||
mixed_surface_indentation = std::clamp(mixed_surface_indentation, -2.f, 2.f);
|
mixed_surface_indentation = std::clamp(mixed_surface_indentation, -2.f, 2.f);
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Print::apply mixed settings"
|
BOOST_LOG_TRIVIAL(info) << "Print::apply mixed settings"
|
||||||
@@ -1408,6 +1422,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
<< ", lower=" << mixed_height_lower
|
<< ", lower=" << mixed_height_lower
|
||||||
<< ", upper=" << mixed_height_upper
|
<< ", upper=" << mixed_height_upper
|
||||||
<< ", advanced_dither=" << (mixed_advanced_dither ? 1 : 0)
|
<< ", advanced_dither=" << (mixed_advanced_dither ? 1 : 0)
|
||||||
|
<< ", pointillism_pixel_size=" << mixed_pointillism_pixel_size
|
||||||
|
<< ", pointillism_line_gap=" << mixed_pointillism_line_gap
|
||||||
<< ", surface_indentation=" << mixed_surface_indentation
|
<< ", surface_indentation=" << mixed_surface_indentation
|
||||||
<< ", custom_definitions_len=" << mixed_custom_definitions.size()
|
<< ", custom_definitions_len=" << mixed_custom_definitions.size()
|
||||||
<< ", physical_extruders=" << num_extruders;
|
<< ", physical_extruders=" << num_extruders;
|
||||||
|
|||||||
@@ -992,6 +992,7 @@ FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
|
|||||||
|
|
||||||
void PrintObject::clear_layers()
|
void PrintObject::clear_layers()
|
||||||
{
|
{
|
||||||
|
this->clear_local_z_plan();
|
||||||
if (!m_shared_object) {
|
if (!m_shared_object) {
|
||||||
for (Layer *l : m_layers)
|
for (Layer *l : m_layers)
|
||||||
delete l;
|
delete l;
|
||||||
@@ -1467,6 +1468,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
|||||||
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifyInfill });
|
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifyInfill });
|
||||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||||
m_slicing_params.valid = false;
|
m_slicing_params.valid = false;
|
||||||
|
this->clear_local_z_plan();
|
||||||
} else if (step == posSupportMaterial) {
|
} else if (step == posSupportMaterial) {
|
||||||
invalidated |= this->invalidate_steps({ posSimplifySupportPath });
|
invalidated |= this->invalidate_steps({ posSimplifySupportPath });
|
||||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||||
@@ -1488,6 +1490,7 @@ bool PrintObject::invalidate_all_steps()
|
|||||||
bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
|
bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
|
||||||
// Then reset some of the depending values.
|
// Then reset some of the depending values.
|
||||||
m_slicing_params.valid = false;
|
m_slicing_params.valid = false;
|
||||||
|
this->clear_local_z_plan();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -35,9 +35,48 @@ static PrinterTechnology printer_technology()
|
|||||||
return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
|
return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int physical_filaments_count()
|
||||||
|
{
|
||||||
|
return std::max(wxGetApp().filaments_cnt(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
static int filaments_count()
|
static int filaments_count()
|
||||||
{
|
{
|
||||||
return wxGetApp().filaments_cnt();
|
if (wxGetApp().preset_bundle == nullptr)
|
||||||
|
return 0;
|
||||||
|
const int physical = physical_filaments_count();
|
||||||
|
const auto &mixed_mgr = wxGetApp().preset_bundle->mixed_filaments;
|
||||||
|
return static_cast<int>(mixed_mgr.total_filaments(size_t(physical)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<unsigned int> ui_ordered_filament_ids()
|
||||||
|
{
|
||||||
|
if (wxGetApp().plater() == nullptr)
|
||||||
|
return {};
|
||||||
|
return wxGetApp().plater()->sidebar().get_ui_ordered_filament_ids();
|
||||||
|
}
|
||||||
|
|
||||||
|
static wxString filament_menu_item_name(const int filament_id_1based, const int display_filament_id_1based)
|
||||||
|
{
|
||||||
|
if (filament_id_1based <= 0)
|
||||||
|
return _L("Default");
|
||||||
|
|
||||||
|
if (wxGetApp().preset_bundle == nullptr)
|
||||||
|
return wxString::Format(_L("Filament %d"), filament_id_1based);
|
||||||
|
|
||||||
|
const int physical = physical_filaments_count();
|
||||||
|
if (filament_id_1based <= physical) {
|
||||||
|
const size_t preset_idx = size_t(filament_id_1based - 1);
|
||||||
|
const auto &filament_presets = wxGetApp().preset_bundle->filament_presets;
|
||||||
|
if (preset_idx < filament_presets.size()) {
|
||||||
|
auto preset = wxGetApp().preset_bundle->filaments.find_preset(filament_presets[preset_idx]);
|
||||||
|
if (preset != nullptr)
|
||||||
|
return from_u8(preset->label(false));
|
||||||
|
}
|
||||||
|
return wxString::Format(_L("Filament %d"), filament_id_1based);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wxString::Format(_L("Mixed Filament %d"), display_filament_id_1based);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_improper_category(const std::string& category, const int filaments_cnt, const bool is_object_settings = true)
|
static bool is_improper_category(const std::string& category, const int filaments_cnt, const bool is_object_settings = true)
|
||||||
@@ -1022,8 +1061,9 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
|
|||||||
menu->Destroy(item_id);
|
menu->Destroy(item_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int filaments_cnt = filaments_count();
|
// Use UI-ordered filament IDs (physical first, then enabled mixed in UI order)
|
||||||
if (filaments_cnt <= 1)
|
const std::vector<unsigned int> ordered_filament_ids = ui_ordered_filament_ids();
|
||||||
|
if (ordered_filament_ids.size() <= 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wxDataViewItemArray sels;
|
wxDataViewItemArray sels;
|
||||||
@@ -1039,24 +1079,16 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
|
|||||||
if (sels.Count() == 1) {
|
if (sels.Count() == 1) {
|
||||||
const ModelConfig& config = obj_list()->get_item_config(sels[0]);
|
const ModelConfig& config = obj_list()->get_item_config(sels[0]);
|
||||||
// BBS: set default extruder to 1
|
// BBS: set default extruder to 1
|
||||||
initial_extruder = config.has("extruder") ? config.extruder() : 1;
|
initial_extruder = config.has("extruder") ? config.extruder() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i <= filaments_cnt; i++)
|
for (size_t display_idx = 0; display_idx <= ordered_filament_ids.size(); ++display_idx)
|
||||||
{
|
{
|
||||||
bool is_active_extruder = i == initial_extruder;
|
const int actual_filament_id = display_idx == 0 ? 0 : int(ordered_filament_ids[display_idx - 1]);
|
||||||
int icon_idx = i == 0 ? 0 : i - 1;
|
const bool is_active_extruder = actual_filament_id == initial_extruder;
|
||||||
|
const int icon_idx = actual_filament_id == 0 ? 0 : actual_filament_id - 1;
|
||||||
|
|
||||||
wxString item_name = _L("Default");
|
wxString item_name = filament_menu_item_name(actual_filament_id, int(display_idx));
|
||||||
|
|
||||||
if (i > 0) {
|
|
||||||
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i - 1]);
|
|
||||||
if (preset == nullptr) {
|
|
||||||
item_name = wxString::Format(_L("Filament %d"), i);
|
|
||||||
} else {
|
|
||||||
item_name = from_u8(preset->label(false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_active_extruder) {
|
if (is_active_extruder) {
|
||||||
item_name << " (" + _L("current") + ")";
|
item_name << " (" + _L("current") + ")";
|
||||||
@@ -1064,11 +1096,13 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
|
|||||||
|
|
||||||
if (icon_idx >= 0 && icon_idx < icons.size()) {
|
if (icon_idx >= 0 && icon_idx < icons.size()) {
|
||||||
append_menu_item(
|
append_menu_item(
|
||||||
extruder_selection_menu, wxID_ANY, item_name, "", [i](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(i); }, *icons[icon_idx], menu,
|
extruder_selection_menu, wxID_ANY, item_name, "",
|
||||||
|
[actual_filament_id](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(actual_filament_id); }, *icons[icon_idx], menu,
|
||||||
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
|
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
|
||||||
} else {
|
} else {
|
||||||
append_menu_item(
|
append_menu_item(
|
||||||
extruder_selection_menu, wxID_ANY, item_name, "", [i](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(i); }, "", menu,
|
extruder_selection_menu, wxID_ANY, item_name, "",
|
||||||
|
[actual_filament_id](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(actual_filament_id); }, "", menu,
|
||||||
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
|
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2190,8 +2224,8 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu)
|
|||||||
menu->Destroy(item_id);
|
menu->Destroy(item_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int filaments_cnt = filaments_count();
|
const std::vector<unsigned int> ordered_filament_ids = ui_ordered_filament_ids();
|
||||||
if (filaments_cnt <= 1)
|
if (ordered_filament_ids.size() <= 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wxDataViewItemArray sels;
|
wxDataViewItemArray sels;
|
||||||
@@ -2206,12 +2240,10 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
|
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
|
||||||
if (icons.size() < filaments_cnt) {
|
if (icons.size() < ordered_filament_ids.size()) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << boost::format("Warning: icons size %1%, filaments_cnt=%2%")%icons.size()%filaments_cnt;
|
BOOST_LOG_TRIVIAL(warning) << boost::format("Warning: icons size %1%, filaments_cnt=%2%") % icons.size() % ordered_filament_ids.size();
|
||||||
if (icons.size() <= 1)
|
if (icons.size() <= 1)
|
||||||
return;
|
return;
|
||||||
else
|
|
||||||
filaments_cnt = icons.size();
|
|
||||||
}
|
}
|
||||||
wxMenu* extruder_selection_menu = new wxMenu();
|
wxMenu* extruder_selection_menu = new wxMenu();
|
||||||
const wxString& name = sels.Count() == 1 ? names[0] : names[1];
|
const wxString& name = sels.Count() == 1 ? names[0] : names[1];
|
||||||
@@ -2219,47 +2251,27 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu)
|
|||||||
int initial_extruder = -1; // negative value for multiple object/part selection
|
int initial_extruder = -1; // negative value for multiple object/part selection
|
||||||
if (sels.Count() == 1) {
|
if (sels.Count() == 1) {
|
||||||
const ModelConfig& config = obj_list()->get_item_config(sels[0]);
|
const ModelConfig& config = obj_list()->get_item_config(sels[0]);
|
||||||
// BBS
|
|
||||||
const auto sel_vol = obj_list()->get_selected_model_volume();
|
const auto sel_vol = obj_list()->get_selected_model_volume();
|
||||||
if (sel_vol && sel_vol->type() == ModelVolumeType::PARAMETER_MODIFIER)
|
if (sel_vol && sel_vol->type() == ModelVolumeType::PARAMETER_MODIFIER)
|
||||||
initial_extruder = config.has("extruder") ? config.extruder() : 0;
|
initial_extruder = config.has("extruder") ? config.extruder() : 0;
|
||||||
else
|
else
|
||||||
initial_extruder = config.has("extruder") ? config.extruder() : 1;
|
initial_extruder = config.has("extruder") ? config.extruder() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BBS
|
for (size_t display_idx = 0; display_idx <= ordered_filament_ids.size(); ++display_idx)
|
||||||
bool has_modifier = false;
|
|
||||||
for (auto sel : sels) {
|
|
||||||
if (obj_list()->GetModel()->GetVolumeType(sel) == ModelVolumeType::PARAMETER_MODIFIER) {
|
|
||||||
has_modifier = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = has_modifier ? 0 : 1; i <= filaments_cnt; i++)
|
|
||||||
{
|
{
|
||||||
// BBS
|
const int actual_filament_id = display_idx == 0 ? 0 : int(ordered_filament_ids[display_idx - 1]);
|
||||||
//bool is_active_extruder = i == initial_extruder;
|
|
||||||
bool is_active_extruder = false;
|
bool is_active_extruder = false;
|
||||||
|
|
||||||
wxString item_name = _L("Default");
|
wxString item_name = filament_menu_item_name(actual_filament_id, int(display_idx));
|
||||||
|
|
||||||
if (i > 0) {
|
|
||||||
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i - 1]);
|
|
||||||
if (preset == nullptr) {
|
|
||||||
item_name = wxString::Format(_L("Filament %d"), i);
|
|
||||||
} else {
|
|
||||||
item_name = from_u8(preset->label(false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_active_extruder) {
|
if (is_active_extruder) {
|
||||||
item_name << " (" + _L("current") + ")";
|
item_name << " (" + _L("current") + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
wxBitmap bm = (i == 0 || size_t(i - 1) >= icons.size()) ? wxNullBitmap : *icons[i - 1];
|
|
||||||
append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "",
|
append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "",
|
||||||
[i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, bm, menu,
|
[actual_filament_id](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(actual_filament_id); },
|
||||||
|
actual_filament_id == 0 || size_t(actual_filament_id - 1) >= icons.size() ? wxNullBitmap : *icons[size_t(actual_filament_id - 1)], menu,
|
||||||
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
|
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
|
||||||
}
|
}
|
||||||
menu->Append(wxID_ANY, name, extruder_selection_menu, _L("Change Filament"));
|
menu->Append(wxID_ANY, name, extruder_selection_menu, _L("Change Filament"));
|
||||||
|
|||||||
@@ -11429,6 +11429,12 @@ void Plater::priv::on_filament_color_changed(wxCommandEvent &event)
|
|||||||
if (wxGetApp().app_config->get("auto_calculate_flush") != "disabled") {
|
if (wxGetApp().app_config->get("auto_calculate_flush") != "disabled") {
|
||||||
sidebar->auto_calc_flushing_volumes(modify_id);
|
sidebar->auto_calc_flushing_volumes(modify_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regenerate mixed filaments and refresh the mixed panel only. Color
|
||||||
|
// changes do not alter filament IDs, so the full on_filaments_change()
|
||||||
|
// path is unnecessary and can re-enter UI rebuilds mid-update.
|
||||||
|
wxGetApp().preset_bundle->update_multi_material_filament_presets();
|
||||||
|
sidebar->update_mixed_filament_panel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::install_network_plugin(wxCommandEvent &event)
|
void Plater::priv::install_network_plugin(wxCommandEvent &event)
|
||||||
|
|||||||
@@ -1615,6 +1615,49 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opt_key == "dithering_local_z_mode") {
|
||||||
|
const bool local_z_enabled = boost::any_cast<bool>(value);
|
||||||
|
if (local_z_enabled &&
|
||||||
|
(!m_config->has("mixed_filament_region_collapse") ||
|
||||||
|
m_config->option("mixed_filament_region_collapse") == nullptr ||
|
||||||
|
m_config->opt_bool("mixed_filament_region_collapse"))) {
|
||||||
|
change_opt_value(*m_config, "mixed_filament_region_collapse", boost::any(false));
|
||||||
|
if (m_type == Preset::TYPE_PRINT) {
|
||||||
|
DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config;
|
||||||
|
project_cfg.set_key_value("mixed_filament_region_collapse", new ConfigOptionBool(false));
|
||||||
|
}
|
||||||
|
if (Field *field = this->get_field("mixed_filament_region_collapse"))
|
||||||
|
field->set_value(boost::any(false), false);
|
||||||
|
update_dirty();
|
||||||
|
}
|
||||||
|
if (!local_z_enabled &&
|
||||||
|
m_config->has("dithering_local_z_whole_objects") &&
|
||||||
|
m_config->option("dithering_local_z_whole_objects") != nullptr &&
|
||||||
|
m_config->opt_bool("dithering_local_z_whole_objects")) {
|
||||||
|
change_opt_value(*m_config, "dithering_local_z_whole_objects", boost::any(false));
|
||||||
|
if (m_type == Preset::TYPE_PRINT) {
|
||||||
|
DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config;
|
||||||
|
project_cfg.set_key_value("dithering_local_z_whole_objects", new ConfigOptionBool(false));
|
||||||
|
}
|
||||||
|
if (Field *field = this->get_field("dithering_local_z_whole_objects"))
|
||||||
|
field->set_value(boost::any(false), false);
|
||||||
|
update_dirty();
|
||||||
|
}
|
||||||
|
if (!local_z_enabled &&
|
||||||
|
m_config->has("dithering_local_z_direct_multicolor") &&
|
||||||
|
m_config->option("dithering_local_z_direct_multicolor") != nullptr &&
|
||||||
|
m_config->opt_bool("dithering_local_z_direct_multicolor")) {
|
||||||
|
change_opt_value(*m_config, "dithering_local_z_direct_multicolor", boost::any(false));
|
||||||
|
if (m_type == Preset::TYPE_PRINT) {
|
||||||
|
DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config;
|
||||||
|
project_cfg.set_key_value("dithering_local_z_direct_multicolor", new ConfigOptionBool(false));
|
||||||
|
}
|
||||||
|
if (Field *field = this->get_field("dithering_local_z_direct_multicolor"))
|
||||||
|
field->set_value(boost::any(false), false);
|
||||||
|
update_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reload scene to update timelapse wipe tower
|
// reload scene to update timelapse wipe tower
|
||||||
if (opt_key == "timelapse_type") {
|
if (opt_key == "timelapse_type") {
|
||||||
bool wipe_tower_enabled = m_config->option<ConfigOptionBool>("enable_prime_tower")->value;
|
bool wipe_tower_enabled = m_config->option<ConfigOptionBool>("enable_prime_tower")->value;
|
||||||
@@ -1907,10 +1950,42 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool refresh_mixed_filament_panel =
|
||||||
|
m_type == Preset::TYPE_PRINT && opt_key == "mixed_filament_component_bias_enabled";
|
||||||
|
|
||||||
|
// Keep Mixed Filaments global settings in sync with project_config. In
|
||||||
|
// full_fff_config(), project_config is applied last and would otherwise
|
||||||
|
// override the edited print preset value from the Others panel.
|
||||||
|
if (m_type == Preset::TYPE_PRINT &&
|
||||||
|
(opt_key == "mixed_filament_gradient_mode" ||
|
||||||
|
opt_key == "mixed_filament_height_lower_bound" ||
|
||||||
|
opt_key == "mixed_filament_height_upper_bound" ||
|
||||||
|
opt_key == "mixed_color_layer_height_a" ||
|
||||||
|
opt_key == "mixed_color_layer_height_b" ||
|
||||||
|
opt_key == "mixed_filament_advanced_dithering" ||
|
||||||
|
opt_key == "mixed_filament_pointillism_pixel_size" ||
|
||||||
|
opt_key == "mixed_filament_pointillism_line_gap" ||
|
||||||
|
opt_key == "mixed_filament_component_bias_enabled" ||
|
||||||
|
opt_key == "mixed_filament_surface_indentation" ||
|
||||||
|
opt_key == "mixed_filament_region_collapse" ||
|
||||||
|
opt_key == "dithering_z_step_size" ||
|
||||||
|
opt_key == "dithering_local_z_mode" ||
|
||||||
|
opt_key == "dithering_local_z_whole_objects" ||
|
||||||
|
opt_key == "dithering_local_z_direct_multicolor" ||
|
||||||
|
opt_key == "dithering_step_painted_zones_only" ||
|
||||||
|
opt_key == "mixed_filament_definitions")) {
|
||||||
|
DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config;
|
||||||
|
if (const ConfigOption *opt = m_config->option(opt_key))
|
||||||
|
project_cfg.set_key_value(opt_key, opt->clone());
|
||||||
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
if(m_active_page)
|
if(m_active_page)
|
||||||
m_active_page->update_visibility(m_mode, true);
|
m_active_page->update_visibility(m_mode, true);
|
||||||
m_page_view->GetParent()->Layout();
|
m_page_view->GetParent()->Layout();
|
||||||
|
|
||||||
|
if (refresh_mixed_filament_panel && wxGetApp().plater() != nullptr)
|
||||||
|
wxGetApp().sidebar().update_mixed_filament_panel(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tab::show_timelapse_warning_dialog() {
|
void Tab::show_timelapse_warning_dialog() {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/Slicing.hpp"
|
#include "libslic3r/Slicing.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -130,3 +133,57 @@ TEST_CASE("[review-fixes] mixed/dithering option keys exist in PrintConfig",
|
|||||||
REQUIRE(print_config_def.get(k) != nullptr);
|
REQUIRE(print_config_def.get(k) != nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[review-fixes] clear_local_z_plan called from clear_layers", "[review-fixes][mixed-filament]")
|
||||||
|
{
|
||||||
|
// Sentinel: ensure the source contains the 3 invalidation hooks the
|
||||||
|
// FullSpectrum verification report flagged as missing. This is a
|
||||||
|
// smoke-test against the source so we can detect future regressions
|
||||||
|
// without depending on a full slicing-pipeline harness.
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
const fs::path repo = fs::path(__FILE__).parent_path().parent_path().parent_path();
|
||||||
|
const fs::path src = repo / "src" / "libslic3r" / "PrintObject.cpp";
|
||||||
|
REQUIRE(fs::exists(src));
|
||||||
|
|
||||||
|
std::ifstream in(src.string());
|
||||||
|
std::stringstream buf; buf << in.rdbuf();
|
||||||
|
const std::string body = buf.str();
|
||||||
|
|
||||||
|
const auto count_substr = [&](const std::string &needle) {
|
||||||
|
size_t n = 0, pos = 0;
|
||||||
|
while ((pos = body.find(needle, pos)) != std::string::npos) { ++n; ++pos; }
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Three FS-mandated call sites: clear_layers, invalidate_step(posSlice),
|
||||||
|
// invalidate_all_steps. Plus the one already-present site inside
|
||||||
|
// build_local_z_plan(). PrintObject.cpp itself has 3 (4 once the
|
||||||
|
// backport is complete).
|
||||||
|
REQUIRE(count_substr("this->clear_local_z_plan()") >= 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[review-fixes] merge_segmented_layers preserves channel 0", "[review-fixes][mixed-filament]")
|
||||||
|
{
|
||||||
|
// Sentinel: ensure merge_segmented_layers uses the FS shape
|
||||||
|
// (output sized num_facets_states, channel 0 = default), not the
|
||||||
|
// pre-FS-a11b70e3a shape (output sized num_facets_states - 1,
|
||||||
|
// channel 0 dropped). The FS-verbatim apply_mm_segmentation in
|
||||||
|
// PrintObjectSlice.cpp expects channel 0 to be present; mismatching
|
||||||
|
// shapes silently shifts every painted region's filament_id by -1
|
||||||
|
// (e.g. paint with mixed slot 4 -> applied as physical filament 3).
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
const fs::path repo = fs::path(__FILE__).parent_path().parent_path().parent_path();
|
||||||
|
const fs::path src = repo / "src" / "libslic3r" / "MultiMaterialSegmentation.cpp";
|
||||||
|
REQUIRE(fs::exists(src));
|
||||||
|
|
||||||
|
std::ifstream in(src.string());
|
||||||
|
std::stringstream buf; buf << in.rdbuf();
|
||||||
|
const std::string body = buf.str();
|
||||||
|
|
||||||
|
// Producer must NOT subtract one from num_facets_states when sizing the output.
|
||||||
|
REQUIRE(body.find("num_facets_states - 1") == std::string::npos);
|
||||||
|
// Producer must NOT shift indexing back by one when writing into the output.
|
||||||
|
REQUIRE(body.find("[extruder_id - 1]") == std::string::npos);
|
||||||
|
// Loop must include channel 0 (extruder_id starts at 0, not 1).
|
||||||
|
REQUIRE(body.find("for (size_t extruder_id = 0; extruder_id < num_facets_states") != std::string::npos);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user