Update README and implement dithering features: Change download link in README, add mixed filament management enhancements including filament removal and layer height adjustments for dithering in the MixedFilamentManager. Introduce new configuration options for dithering in the PrintConfig and GUI, and ensure proper handling of mixed filament states during filament deletion.

This commit is contained in:
Rad
2026-02-10 03:10:31 +01:00
parent aad40efc52
commit 49ab2f9ac5
22 changed files with 655 additions and 35 deletions
+4 -1
View File
@@ -27,7 +27,7 @@
# Download
### Stable Release
📥 **[Download the Latest Stable Release](https://github.com/Snapmaker/OrcaSlicer/releases/latest)**
📥 **[Download the Latest Stable Release](https://github.com/ratdoux/OrcaSlicer-FullSpectrum/releases)**
Visit our GitHub Releases page for the latest stable version of Full Spectrum, recommended for most users.
# Features
@@ -147,6 +147,9 @@ Bambu Studio is forked from [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer
Orca Slicer incorporates a lot of features from SuperSlicer by @supermerill
Orca Slicer's logo is designed by community member Justin Levine(@freejstnalxndr)
## Acknowledgements
Special thanks to [u/Aceman11100](https://www.reddit.com/user/Aceman11100/) for the inspiration and idea behind the mixed-color filament feature!
# License
Full Spectrum is licensed under the GNU Affero General Public License, version 3. Full Spectrum is based on Snapmaker Orca.
+87 -9
View File
@@ -17,6 +17,7 @@
#include <cassert>
#include <limits>
#include <algorithm>
#include <cmath>
#include <libslic3r.h>
@@ -24,6 +25,40 @@ namespace Slic3r {
const static bool g_wipe_into_objects = false;
namespace {
unsigned int resolve_mixed_with_layer_heights(const MixedFilamentManager *mixed_mgr,
size_t num_physical,
unsigned int filament_id_1based,
int layer_index,
float layer_height_a,
float layer_height_b,
float base_layer_height)
{
if (!(mixed_mgr && mixed_mgr->is_mixed(filament_id_1based, num_physical)))
return filament_id_1based;
if (layer_height_a > 0.f || layer_height_b > 0.f) {
const float safe_base = std::max<float>(0.01f, base_layer_height);
const int ratio_a = std::max(1, int(std::lround((layer_height_a > 0.f ? layer_height_a : safe_base) / safe_base)));
const int ratio_b = std::max(1, int(std::lround((layer_height_b > 0.f ? layer_height_b : safe_base) / safe_base)));
const int cycle = ratio_a + ratio_b;
if (cycle > 0) {
const size_t idx = static_cast<size_t>(filament_id_1based - num_physical - 1);
const auto &mixed = mixed_mgr->mixed_filaments();
if (idx < mixed.size()) {
const int pos = ((layer_index % cycle) + cycle) % cycle;
return pos < ratio_a ? mixed[idx].component_a : mixed[idx].component_b;
}
}
}
return mixed_mgr->resolve(filament_id_1based, num_physical, layer_index);
}
} // namespace
// Shortest hamilton path problem
static std::vector<unsigned int> solve_extruder_order(const std::vector<std::vector<float>>& wipe_volumes, std::vector<unsigned int> all_extruders, std::optional<unsigned int> start_extruder_id)
@@ -149,9 +184,13 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
// Resolve a 1-based filament ID through the mixed-filament manager for this layer.
unsigned int LayerTools::resolve_mixed_1based(unsigned int filament_id) const
{
if (mixed_mgr && mixed_mgr->is_mixed(filament_id, num_physical))
return mixed_mgr->resolve(filament_id, num_physical, this->layer_index);
return filament_id;
return resolve_mixed_with_layer_heights(mixed_mgr,
num_physical,
filament_id,
this->layer_index,
mixed_layer_height_a,
mixed_layer_height_b,
mixed_base_layer_height);
}
// Return a zero based extruder from the region, or extruder_override if overriden.
@@ -223,6 +262,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
// Mixed filament support.
m_mixed_mgr = &object.print()->mixed_filament_manager();
m_num_physical = object.print()->config().filament_diameter.size();
update_mixed_layer_height_settings();
if (object.layers().empty())
return;
@@ -289,6 +329,7 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
// Mixed filament support.
m_mixed_mgr = &print.mixed_filament_manager();
m_num_physical = print.config().filament_diameter.size();
update_mixed_layer_height_settings();
// Initialize the print layers for all objects and all layers.
coordf_t object_bottom_z = 0.;
@@ -363,6 +404,33 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
this->mark_skirt_layers(print.config(), max_layer_height);
}
void ToolOrdering::update_mixed_layer_height_settings()
{
const PrintConfig *cfg = m_print_config_ptr;
if (cfg == nullptr && m_print_object_ptr != nullptr)
cfg = &m_print_object_ptr->print()->config();
m_mixed_layer_height_a = 0.f;
m_mixed_layer_height_b = 0.f;
if (m_print_full_config != nullptr &&
m_print_full_config->has("mixed_color_layer_height_a") &&
m_print_full_config->has("mixed_color_layer_height_b")) {
m_mixed_layer_height_a = float(m_print_full_config->opt_float("mixed_color_layer_height_a"));
m_mixed_layer_height_b = float(m_print_full_config->opt_float("mixed_color_layer_height_b"));
} else if (cfg != nullptr) {
m_mixed_layer_height_a = cfg->mixed_color_layer_height_a.value;
m_mixed_layer_height_b = cfg->mixed_color_layer_height_b.value;
}
float base_height = 0.2f;
if (m_print_object_ptr != nullptr)
base_height = float(m_print_object_ptr->config().layer_height.value);
else if (m_print_full_config != nullptr && m_print_full_config->has("layer_height"))
base_height = float(m_print_full_config->opt_float("layer_height"));
m_mixed_base_layer_height = std::max<float>(0.01f, base_height);
}
static void apply_first_layer_order(const DynamicPrintConfig* config, std::vector<unsigned int>& tool_order) {
const ConfigOptionInts* first_layer_print_sequence_op = config->option<ConfigOptionInts>("first_layer_print_sequence");
if (first_layer_print_sequence_op) {
@@ -492,6 +560,14 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
// Collect extruders reuqired to print layers.
void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
{
for (LayerTools &layer_tools : m_layer_tools) {
layer_tools.mixed_mgr = m_mixed_mgr;
layer_tools.num_physical = m_num_physical;
layer_tools.mixed_layer_height_a = m_mixed_layer_height_a;
layer_tools.mixed_layer_height_b = m_mixed_layer_height_b;
layer_tools.mixed_base_layer_height = m_mixed_base_layer_height;
}
// Collect the support extruders.
for (auto support_layer : object.support_layers()) {
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
@@ -524,9 +600,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
for (auto layer : object.layers()) {
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
// Store the sequential layer index and mixed-filament context for resolution.
layer_tools.layer_index = layerCount;
layer_tools.mixed_mgr = m_mixed_mgr;
layer_tools.num_physical = m_num_physical;
layer_tools.layer_index = layerCount;
// Override extruder with the next
for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override)
@@ -1441,9 +1515,13 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject
// Resolve a 1-based filament ID through the mixed-filament manager.
unsigned int ToolOrdering::resolve_mixed(unsigned int filament_id_1based, int layer_index) const
{
if (m_mixed_mgr && m_mixed_mgr->is_mixed(filament_id_1based, m_num_physical))
return m_mixed_mgr->resolve(filament_id_1based, m_num_physical, layer_index);
return filament_id_1based;
return resolve_mixed_with_layer_heights(m_mixed_mgr,
m_num_physical,
filament_id_1based,
layer_index,
m_mixed_layer_height_a,
m_mixed_layer_height_b,
m_mixed_base_layer_height);
}
} // namespace Slic3r
+8
View File
@@ -144,6 +144,10 @@ public:
// Mixed-filament resolution context (set by ToolOrdering during collect_extruders).
const MixedFilamentManager *mixed_mgr = nullptr;
size_t num_physical = 0;
// Optional mixed-layer cadence override from print settings.
float mixed_layer_height_a = 0.f;
float mixed_layer_height_b = 0.f;
float mixed_base_layer_height = 0.2f;
private:
// Resolve a 1-based filament ID through the mixed-filament manager for this layer.
@@ -211,6 +215,7 @@ private:
// BBS
std::vector<unsigned int> generate_first_layer_tool_order(const Print& print);
std::vector<unsigned int> generate_first_layer_tool_order(const PrintObject& object);
void update_mixed_layer_height_settings();
// Resolve a 1-based filament ID through the mixed-filament manager.
// Returns the resolved physical extruder (1-based). If the ID is not a
@@ -233,6 +238,9 @@ private:
// number of physical extruders.
const MixedFilamentManager* m_mixed_mgr = nullptr;
size_t m_num_physical = 0;
float m_mixed_layer_height_a = 0.f;
float m_mixed_layer_height_b = 0.f;
float m_mixed_base_layer_height = 0.2f;
};
} // namespace SLic3r
+21
View File
@@ -79,6 +79,27 @@ void MixedFilamentManager::auto_generate(const std::vector<std::string> &filamen
}
}
void MixedFilamentManager::remove_physical_filament(unsigned int deleted_filament_id)
{
if (deleted_filament_id == 0 || m_mixed.empty())
return;
std::vector<MixedFilament> filtered;
filtered.reserve(m_mixed.size());
for (MixedFilament mf : m_mixed) {
if (mf.component_a == deleted_filament_id || mf.component_b == deleted_filament_id)
continue;
if (mf.component_a > deleted_filament_id)
--mf.component_a;
if (mf.component_b > deleted_filament_id)
--mf.component_b;
filtered.emplace_back(std::move(mf));
}
m_mixed = std::move(filtered);
}
unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
size_t num_physical,
int layer_index) const
+5
View File
@@ -61,6 +61,11 @@ public:
// exists.
void auto_generate(const std::vector<std::string> &filament_colours);
// Remove a physical filament (1-based ID) from the mixed list.
// Any mixed filament that contains the removed component is deleted.
// Remaining component IDs are shifted down to stay aligned with physical IDs.
void remove_physical_filament(unsigned int deleted_filament_id);
// ---- Queries --------------------------------------------------------
// True when `filament_id` (1-based) refers to a mixed filament.
+23
View File
@@ -2507,6 +2507,20 @@ void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_cou
}
}
void ModelVolume::remap_extruder_ids(size_t extruder_count, const EnforcerBlockerStateMap &state_map)
{
std::vector<int> used_extruders = get_extruders();
for (int extruder_id : used_extruders) {
if (extruder_id <= 0)
continue;
if (extruder_id >= int(state_map.size()) || extruder_id > int(extruder_count) ||
state_map[size_t(extruder_id)] != EnforcerBlockerType(extruder_id)) {
mmu_segmentation_facets.remap_enforcer_block_types(*this, EnforcerBlockerType(extruder_count), state_map);
break;
}
}
}
void ModelVolume::center_geometry_after_creation(bool update_source_offset)
{
Vec3d shift = this->mesh().bounding_box().center();
@@ -3411,6 +3425,15 @@ void FacetsAnnotation::set_enforcer_block_type_limit(const ModelVolume& mv,
this->set(selector);
}
void FacetsAnnotation::remap_enforcer_block_types(const ModelVolume& mv,
EnforcerBlockerType max_type,
const EnforcerBlockerStateMap &state_map)
{
TriangleSelector selector(mv.mesh());
selector.deserialize(m_data, false, max_type, EnforcerBlockerType::NONE, EnforcerBlockerType::NONE, &state_map);
this->set(selector);
}
indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const
{
TriangleSelector selector(mv.mesh());
+4
View File
@@ -738,6 +738,9 @@ public:
EnforcerBlockerType max_type,
EnforcerBlockerType to_delete_filament = EnforcerBlockerType::NONE,
EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE);
void remap_enforcer_block_types(const ModelVolume& mv,
EnforcerBlockerType max_type,
const EnforcerBlockerStateMap &state_map);
indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const;
bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
bool empty() const { return m_data.triangles_to_split.empty(); }
@@ -919,6 +922,7 @@ public:
std::vector<int> get_extruders() const;
void update_extruder_count(size_t extruder_count);
void update_extruder_count_when_delete_filament(size_t extruder_count, size_t filament_id, int replace_filament_id = -1);
void remap_extruder_ids(size_t extruder_count, const EnforcerBlockerStateMap &state_map);
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
+58
View File
@@ -3231,6 +3231,10 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
#else
size_t num_filaments = this->filament_presets.size();
#endif
const bool deleting_filament = (to_delete_filament_id != size_t(-1));
const size_t old_num_filaments = deleting_filament ? (num_filaments + 1) : num_filaments;
const std::vector<MixedFilament> old_mixed = this->mixed_filaments.mixed_filaments();
m_last_filament_id_remap.clear();
// Now verify if flush_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator):
std::vector<double> old_matrix = this->project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
@@ -3259,6 +3263,12 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
this->project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values = new_matrix;
}
// Keep mixed (virtual) combinations in sync with physical filament deletion.
// Mixed entries containing the deleted physical filament are removed, while
// remaining component IDs are shifted.
if (deleting_filament)
this->mixed_filaments.remove_physical_filament(unsigned(to_delete_filament_id + 1));
// Keep project colours aligned to physical filaments, then regenerate mixed
// (virtual) entries from the physical set only.
{
@@ -3268,6 +3278,54 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
this->mixed_filaments.auto_generate(color_opt->values);
}
}
// Build old->new filament ID remap for painted facet data normalization.
if (deleting_filament) {
const unsigned int deleted_1based = unsigned(to_delete_filament_id + 1);
size_t old_enabled_mixed = 0;
for (const auto &mf : old_mixed)
if (mf.enabled)
++old_enabled_mixed;
const size_t old_total_filaments = old_num_filaments + old_enabled_mixed;
m_last_filament_id_remap.assign(old_total_filaments + 1, 0);
for (unsigned int old_id = 1; old_id <= unsigned(old_num_filaments); ++old_id) {
if (old_id == deleted_1based)
m_last_filament_id_remap[old_id] = 0;
else
m_last_filament_id_remap[old_id] = old_id > deleted_1based ? old_id - 1 : old_id;
}
std::map<std::pair<unsigned int, unsigned int>, unsigned int> new_pair_to_id;
unsigned int next_virtual_id = unsigned(num_filaments + 1);
for (const auto &mf : this->mixed_filaments.mixed_filaments()) {
if (!mf.enabled)
continue;
new_pair_to_id[{mf.component_a, mf.component_b}] = next_virtual_id++;
}
unsigned int old_virtual_id = unsigned(old_num_filaments + 1);
for (const auto &mf : old_mixed) {
if (!mf.enabled)
continue;
unsigned int a = mf.component_a;
unsigned int b = mf.component_b;
if (a == deleted_1based || b == deleted_1based) {
m_last_filament_id_remap[old_virtual_id] = 0;
} else {
if (a > deleted_1based)
--a;
if (b > deleted_1based)
--b;
auto it = new_pair_to_id.find({a, b});
m_last_filament_id_remap[old_virtual_id] = (it == new_pair_to_id.end()) ? 0 : it->second;
}
++old_virtual_id;
}
}
}
void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible)
+4
View File
@@ -253,6 +253,9 @@ public:
// Read out the number of extruders from an active printer preset,
// update size and content of filament_presets.
void update_multi_material_filament_presets(size_t to_delete_filament_id = size_t(-1));
// Mapping generated during the latest filament deletion.
// Index is old 1-based filament ID, value is new 1-based filament ID (0 = removed).
const std::vector<unsigned int>& last_filament_id_remap() const { return m_last_filament_id_remap; }
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
// as compatible with the currently selected printer (and print in case of filament presets).
@@ -324,6 +327,7 @@ private:
bool validation_mode = false;
std::string vendor_to_validate = "";
int m_errors = 0;
std::vector<unsigned int> m_last_filament_id_remap;
};
+3 -1
View File
@@ -245,6 +245,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "filament_shrinkage_compensation_z"
|| opt_key == "resolution"
|| opt_key == "precise_z_height"
|| opt_key == "dithering_z_step_size"
|| opt_key == "dithering_step_painted_zones_only"
// Spiral Vase forces different kind of slicing than the normal model:
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
@@ -1130,7 +1132,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
layer_height_profiles.assign(m_objects.size(), std::vector<coordf_t>());
std::vector<coordf_t> &profile = layer_height_profiles[print_object_idx];
if (profile.empty())
PrintObject::update_layer_height_profile(*print_object.model_object(), print_object.slicing_parameters(), profile);
PrintObject::update_layer_height_profile(*print_object.model_object(), print_object.slicing_parameters(), profile, &print_object);
return profile;
};
+4 -1
View File
@@ -410,7 +410,10 @@ public:
// Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters.
// Returns true, if the layer_height_profile was changed.
static bool update_layer_height_profile(const ModelObject &model_object, const SlicingParameters &slicing_parameters, std::vector<coordf_t> &layer_height_profile);
static bool update_layer_height_profile(const ModelObject &model_object,
const SlicingParameters &slicing_parameters,
std::vector<coordf_t> &layer_height_profile,
const PrintObject *print_object = nullptr);
// Collect the slicing parameters, to be used by variable layer thickness algorithm,
// by the interactive layer height editor and by the printing process itself.
+7
View File
@@ -1092,6 +1092,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
new_full_config.option("print_settings_id", true);
new_full_config.option("filament_settings_id", true);
new_full_config.option("printer_settings_id", true);
// Ensure newly introduced dithering keys are present so in-session updates are detected.
new_full_config.option("dithering_z_step_size", true);
new_full_config.option("dithering_step_painted_zones_only", true);
m_config.option("dithering_z_step_size", true);
m_config.option("dithering_step_painted_zones_only", true);
m_default_object_config.option("dithering_z_step_size", true);
m_default_object_config.option("dithering_step_painted_zones_only", true);
// BBS
int used_filaments = this->extruders(true).size();
+38
View File
@@ -4121,6 +4121,44 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionStrings());
def = this->add("mixed_color_layer_height_a", coFloat);
def->label = L("Dithering cadence height A");
def->category = L("Others");
def->tooltip = L("Layer height contribution of component A for dithering virtual filaments. "
"Set to 0 to use normal 1-layer A / 1-layer B alternation.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("mixed_color_layer_height_b", coFloat);
def->label = L("Dithering cadence height B");
def->category = L("Others");
def->tooltip = L("Layer height contribution of component B for dithering virtual filaments. "
"Set to 0 to use normal 1-layer A / 1-layer B alternation.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("dithering_z_step_size", coFloat);
def->label = L("Dithering Z step size");
def->category = L("Others");
def->tooltip = L("Layer height used in Z zones painted with dithering (mixed virtual filaments). "
"Set to 0 to keep normal layer height in those zones.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("dithering_step_painted_zones_only", coBool);
def->label = L("Use step size in painted zones only");
def->category = L("Others");
def->tooltip = L("When enabled, dithering Z step size is applied only where mixed filament is painted. "
"Unpainted zones keep their original layer height.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("printer_model", coString);
def->label = L("Printer type");
def->tooltip = L("Type of the printer.");
+4
View File
@@ -1351,6 +1351,10 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, ooze_prevention))
((ConfigOptionString, filename_format))
((ConfigOptionStrings, post_process))
((ConfigOptionFloat, mixed_color_layer_height_a))
((ConfigOptionFloat, mixed_color_layer_height_b))
((ConfigOptionFloat, dithering_z_step_size))
((ConfigOptionBool, dithering_step_painted_zones_only))
((ConfigOptionString, printer_model))
((ConfigOptionFloat, resolution))
((ConfigOptionFloats, retraction_minimum_travel))
+258 -3
View File
@@ -946,6 +946,8 @@ bool PrintObject::invalidate_state_by_config_options(
steps.emplace_back(posPerimeters);
} else if (
opt_key == "layer_height"
|| opt_key == "dithering_z_step_size"
|| opt_key == "dithering_step_painted_zones_only"
|| opt_key == "mmu_segmented_region_max_width"
|| opt_key == "mmu_segmented_region_interlocking_depth"
|| opt_key == "raft_layers"
@@ -3331,10 +3333,263 @@ std::vector<unsigned int> PrintObject::object_extruders() const
return extruders;
}
bool PrintObject::update_layer_height_profile(const ModelObject &model_object, const SlicingParameters &slicing_parameters, std::vector<coordf_t> &layer_height_profile)
namespace {
struct LayerHeightRangeOverride {
coordf_t lo { 0.f };
coordf_t hi { 0.f };
coordf_t height { 0.f };
};
static void sort_and_merge_layer_ranges(std::vector<t_layer_height_range> &ranges)
{
if (ranges.empty())
return;
std::sort(ranges.begin(), ranges.end(), [](const t_layer_height_range &a, const t_layer_height_range &b) {
return a.first < b.first || (a.first == b.first && a.second < b.second);
});
std::vector<t_layer_height_range> merged;
merged.reserve(ranges.size());
for (const t_layer_height_range &range : ranges) {
if (range.second <= range.first + EPSILON)
continue;
if (merged.empty() || range.first > merged.back().second + EPSILON) {
merged.emplace_back(range);
} else {
merged.back().second = std::max(merged.back().second, range.second);
}
}
ranges = std::move(merged);
}
static std::vector<t_layer_height_range> collect_mixed_painted_z_ranges(const PrintObject &print_object, coordf_t object_height)
{
std::vector<t_layer_height_range> mixed_ranges;
if (object_height <= EPSILON)
return mixed_ranges;
const Print *print = print_object.print();
if (print == nullptr)
return mixed_ranges;
const size_t num_physical = print->config().filament_colour.size();
const size_t num_total = print->mixed_filament_manager().total_filaments(num_physical);
if (num_total <= num_physical)
return mixed_ranges;
const size_t max_state = std::min(num_total, size_t(EnforcerBlockerType::ExtruderMax));
const Transform3d object_to_print = print_object.trafo_centered();
for (const ModelVolume *mv : print_object.model_object()->volumes) {
if (mv == nullptr || !mv->is_model_part() || mv->mmu_segmentation_facets.empty())
continue;
const auto &used_states = mv->mmu_segmentation_facets.get_data().used_states;
if (used_states.empty())
continue;
const Transform3d volume_to_print = object_to_print * mv->get_matrix();
constexpr coordf_t thin_band = 0.01f;
for (size_t state_idx = num_physical + 1; state_idx <= max_state; ++state_idx) {
if (state_idx >= used_states.size() || !used_states[state_idx])
continue;
const auto facets = mv->mmu_segmentation_facets.get_facets_strict(*mv, static_cast<EnforcerBlockerType>(state_idx));
if (facets.indices.empty() || facets.vertices.empty())
continue;
for (const auto &face : facets.indices) {
double tri_z_min = DBL_MAX;
double tri_z_max = -DBL_MAX;
for (int i = 0; i < 3; ++i) {
const size_t vertex_idx = size_t(face[i]);
if (vertex_idx >= facets.vertices.size())
continue;
const Vec3d p = volume_to_print * facets.vertices[vertex_idx].cast<double>();
tri_z_min = std::min(tri_z_min, p.z());
tri_z_max = std::max(tri_z_max, p.z());
}
if (tri_z_min == DBL_MAX || tri_z_max == -DBL_MAX)
continue;
coordf_t lo = std::max<coordf_t>(0.f, coordf_t(tri_z_min));
coordf_t hi = std::min<coordf_t>(object_height, coordf_t(tri_z_max));
if (hi <= lo + EPSILON) {
const coordf_t center = std::max<coordf_t>(0.f, std::min<coordf_t>(object_height, lo));
lo = std::max<coordf_t>(0.f, center - thin_band * 0.5f);
hi = std::min<coordf_t>(object_height, center + thin_band * 0.5f);
}
if (lo + EPSILON < hi)
mixed_ranges.emplace_back(lo, hi);
}
}
}
sort_and_merge_layer_ranges(mixed_ranges);
return mixed_ranges;
}
static std::vector<LayerHeightRangeOverride> base_layer_height_overrides(const t_layer_config_ranges &ranges, coordf_t object_height)
{
std::vector<LayerHeightRangeOverride> out;
out.reserve(ranges.size());
coordf_t last_hi = 0.f;
for (const auto &[range, config] : ranges) {
coordf_t lo = std::max(range.first, last_hi);
coordf_t hi = std::min(range.second, object_height);
if (lo + EPSILON >= hi)
continue;
const ConfigOption *layer_height_opt = config.option("layer_height");
if (layer_height_opt == nullptr)
continue;
out.push_back({ lo, hi, coordf_t(layer_height_opt->getFloat()) });
last_hi = hi;
}
return out;
}
static bool contains_z(const std::vector<t_layer_height_range> &ranges, coordf_t z)
{
for (const t_layer_height_range &range : ranges) {
if (z + EPSILON < range.first)
break;
if (z + EPSILON >= range.first && z < range.second - EPSILON)
return true;
}
return false;
}
static bool get_override_height(const std::vector<LayerHeightRangeOverride> &ranges, coordf_t z, coordf_t &height_out)
{
for (const LayerHeightRangeOverride &range : ranges) {
if (z + EPSILON < range.lo)
break;
if (z + EPSILON >= range.lo && z < range.hi - EPSILON) {
height_out = range.height;
return true;
}
}
return false;
}
static t_layer_config_ranges layer_ranges_with_dithering(const t_layer_config_ranges &base_ranges_map,
coordf_t object_height,
coordf_t default_layer_height,
const std::vector<t_layer_height_range> &mixed_ranges,
coordf_t dithering_step)
{
if (object_height <= EPSILON || mixed_ranges.empty() || dithering_step <= EPSILON)
return base_ranges_map;
const std::vector<LayerHeightRangeOverride> base_ranges = base_layer_height_overrides(base_ranges_map, object_height);
std::vector<coordf_t> boundaries;
boundaries.reserve(2 + base_ranges.size() * 2 + mixed_ranges.size() * 2);
boundaries.emplace_back(0.f);
boundaries.emplace_back(object_height);
for (const LayerHeightRangeOverride &range : base_ranges) {
boundaries.emplace_back(range.lo);
boundaries.emplace_back(range.hi);
}
for (const t_layer_height_range &range : mixed_ranges) {
boundaries.emplace_back(range.first);
boundaries.emplace_back(range.second);
}
std::sort(boundaries.begin(), boundaries.end());
boundaries.erase(std::unique(boundaries.begin(), boundaries.end(), [](coordf_t a, coordf_t b) {
return std::abs(a - b) <= EPSILON;
}),
boundaries.end());
std::vector<LayerHeightRangeOverride> merged_ranges;
merged_ranges.reserve(boundaries.size());
for (size_t i = 1; i < boundaries.size(); ++i) {
const coordf_t lo = boundaries[i - 1];
const coordf_t hi = boundaries[i];
if (hi <= lo + EPSILON)
continue;
const coordf_t z_mid = 0.5f * (lo + hi);
coordf_t target_height = default_layer_height;
coordf_t base_height = 0.f;
if (contains_z(mixed_ranges, z_mid)) {
target_height = dithering_step;
} else if (get_override_height(base_ranges, z_mid, base_height)) {
target_height = base_height;
}
if (std::abs(target_height - default_layer_height) <= EPSILON)
continue;
if (!merged_ranges.empty() &&
std::abs(merged_ranges.back().height - target_height) <= EPSILON &&
std::abs(merged_ranges.back().hi - lo) <= EPSILON) {
merged_ranges.back().hi = hi;
} else {
merged_ranges.push_back({ lo, hi, target_height });
}
}
t_layer_config_ranges out;
for (const LayerHeightRangeOverride &range : merged_ranges) {
if (range.hi <= range.lo + EPSILON)
continue;
ModelConfig cfg;
cfg.set_key_value("layer_height", new ConfigOptionFloat(range.height));
out.emplace(t_layer_height_range(range.lo, range.hi), std::move(cfg));
}
return out;
}
} // namespace
bool PrintObject::update_layer_height_profile(const ModelObject &model_object,
const SlicingParameters &slicing_parameters,
std::vector<coordf_t> &layer_height_profile,
const PrintObject *print_object)
{
bool updated = false;
const t_layer_config_ranges *ranges_to_use = &model_object.layer_config_ranges;
t_layer_config_ranges dithering_ranges;
if (print_object != nullptr && print_object->print() != nullptr) {
const DynamicPrintConfig &full_cfg = print_object->print()->full_print_config();
coordf_t dithering_step = coordf_t(print_object->print()->config().dithering_z_step_size.value);
bool painted_zones_only = print_object->print()->config().dithering_step_painted_zones_only.value;
if (full_cfg.has("dithering_z_step_size"))
dithering_step = coordf_t(full_cfg.opt_float("dithering_z_step_size"));
if (full_cfg.has("dithering_step_painted_zones_only"))
painted_zones_only = full_cfg.opt_bool("dithering_step_painted_zones_only");
if (dithering_step > EPSILON) {
const coordf_t object_height = slicing_parameters.object_print_z_uncompensated_height();
std::vector<t_layer_height_range> mixed_ranges;
if (painted_zones_only)
mixed_ranges = collect_mixed_painted_z_ranges(*print_object, object_height);
else if (object_height > EPSILON)
mixed_ranges.emplace_back(0.f, object_height);
if (!mixed_ranges.empty()) {
dithering_ranges = layer_ranges_with_dithering(model_object.layer_config_ranges,
object_height,
slicing_parameters.layer_height,
mixed_ranges,
dithering_step);
ranges_to_use = &dithering_ranges;
}
}
}
const bool has_dithering_ranges = (ranges_to_use != &model_object.layer_config_ranges);
if (layer_height_profile.empty()) {
// use the constructor because the assignement is crashing on ASAN OsX
layer_height_profile = std::vector<coordf_t>(model_object.layer_height_profile.get());
@@ -3352,9 +3607,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c
std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_uncompensated_max + slicing_parameters.object_print_z_min) > 1e-3))
layer_height_profile.clear();
if (layer_height_profile.empty() || layer_height_profile[1] != slicing_parameters.first_object_layer_height) {
if (layer_height_profile.empty() || layer_height_profile[1] != slicing_parameters.first_object_layer_height || has_dithering_ranges) {
//layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes);
layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges);
layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, *ranges_to_use);
// The layer height profile is already compressed.
updated = true;
}
+1 -1
View File
@@ -793,7 +793,7 @@ void PrintObject::slice()
//BBS: add flag to reload scene for shell rendering
m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE);
std::vector<coordf_t> layer_height_profile;
this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile);
this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile, this);
m_print->throw_if_canceled();
m_typed_slices = false;
this->clear_layers();
+11 -6
View File
@@ -1724,7 +1724,8 @@ void TriangleSelector::deserialize(const TriangleSplittingData& data,
bool needs_reset,
EnforcerBlockerType max_ebt,
EnforcerBlockerType to_delete_filament,
EnforcerBlockerType replace_filament)
EnforcerBlockerType replace_filament,
const EnforcerBlockerStateMap* state_map)
{
if (needs_reset)
reset(); // dump any current state
@@ -1790,11 +1791,15 @@ void TriangleSelector::deserialize(const TriangleSplittingData& data,
}
}
if (state == to_delete_filament)
state = replace_filament;
else if (to_delete_filament != EnforcerBlockerType::NONE && state != EnforcerBlockerType::NONE) {
state = state > to_delete_filament ? EnforcerBlockerType((int) state - 1) : state;
if (state_map != nullptr && state != EnforcerBlockerType::NONE) {
const size_t state_idx = static_cast<size_t>(state);
state = state_idx < state_map->size() ? (*state_map)[state_idx] : EnforcerBlockerType::NONE;
} else {
if (state == to_delete_filament)
state = replace_filament;
else if (to_delete_filament != EnforcerBlockerType::NONE && state != EnforcerBlockerType::NONE) {
state = state > to_delete_filament ? EnforcerBlockerType((int) state - 1) : state;
}
}
if (state > max_ebt) {
+2 -1
View File
@@ -360,7 +360,8 @@ public:
bool needs_reset = true,
EnforcerBlockerType max_ebt = EnforcerBlockerType::ExtruderMax,
EnforcerBlockerType to_delete_filament = EnforcerBlockerType::NONE,
EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE);
EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE,
const EnforcerBlockerStateMap *state_map = nullptr);
// Extract all used facet states from the given TriangleSplittingData.
static std::vector<EnforcerBlockerType> extract_used_facet_states(const TriangleSplittingData &data);
+10 -2
View File
@@ -108,13 +108,21 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
{
try{
if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) {
const ConfigOptionDef *opt_def = config.def()->get(opt_key);
if (opt_def == nullptr)
throw Slic3r::RuntimeError("Unknown config option key: " + opt_key);
// Some older presets may not carry newly introduced keys. Ensure the
// option exists before mutating it through typed accessors below.
if (!config.has(opt_key))
config.set_key_value(opt_key, opt_def->create_default_option());
if (opt_def->type == coBools && opt_def->nullable) {
ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast<unsigned char>(value) };
config.option<ConfigOptionBoolsNullable>(opt_key)->set_at(vec_new, opt_index, 0);
return;
}
const ConfigOptionDef *opt_def = config.def()->get(opt_key);
switch (opt_def->type) {
case coFloatOrPercent:{
std::string str = boost::any_cast<std::string>(value);
+60 -5
View File
@@ -981,10 +981,29 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
case coPercents:
case coFloats:
case coFloat:{
double val = opt->type == coFloats ?
double val = 0.0;
if (config.has(opt_key)) {
val = opt->type == coFloats ?
config.opt_float(opt_key, idx) :
opt->type == coFloat ? config.opt_float(opt_key) :
config.option<ConfigOptionPercents>(opt_key)->get_at(idx);
} else if (opt->default_value) {
if (opt->type == coFloats) {
const auto *defaults = dynamic_cast<const ConfigOptionFloats*>(opt->default_value.get());
if (defaults != nullptr && !defaults->values.empty()) {
const size_t safe_idx = std::min(idx, defaults->values.size() - 1);
val = defaults->values[safe_idx];
}
} else if (opt->type == coFloat) {
val = opt->default_value->getFloat();
} else {
const auto *defaults = dynamic_cast<const ConfigOptionPercents*>(opt->default_value.get());
if (defaults != nullptr && !defaults->values.empty()) {
const size_t safe_idx = std::min(idx, defaults->values.size() - 1);
val = defaults->values[safe_idx];
}
}
}
ret = double_to_string(val);
}
break;
@@ -1009,10 +1028,29 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
ret = from_u8(config.opt_string(opt_key, static_cast<unsigned int>(idx)));
break;
case coBool:
ret = config.opt_bool(opt_key);
if (config.has(opt_key)) {
ret = config.opt_bool(opt_key);
} else if (opt->default_value) {
const auto *defaults = dynamic_cast<const ConfigOptionBool*>(opt->default_value.get());
ret = defaults != nullptr ? defaults->value : false;
} else {
ret = false;
}
break;
case coBools:
ret = config.opt_bool(opt_key, idx);
if (config.has(opt_key)) {
ret = config.opt_bool(opt_key, idx);
} else if (opt->default_value) {
const auto *defaults = dynamic_cast<const ConfigOptionBools*>(opt->default_value.get());
if (defaults != nullptr && !defaults->values.empty()) {
const size_t safe_idx = std::min(idx, defaults->values.size() - 1);
ret = defaults->values[safe_idx];
} else {
ret = false;
}
} else {
ret = false;
}
break;
case coInt:
ret = config.opt_int(opt_key);
@@ -1139,10 +1177,27 @@ boost::any ConfigOptionsGroup::get_config_value2(const DynamicPrintConfig& confi
ret = config.opt_string(opt_key, static_cast<unsigned int>(idx));
break;
case coBool:
ret = config.opt_bool(opt_key);
if (config.has(opt_key))
ret = config.opt_bool(opt_key);
else if (opt->default_value) {
const auto *defaults = dynamic_cast<const ConfigOptionBool*>(opt->default_value.get());
ret = defaults != nullptr ? defaults->value : false;
} else
ret = false;
break;
case coBools:
ret = static_cast<unsigned char>(config.opt_bool(opt_key, idx));
if (config.has(opt_key))
ret = static_cast<unsigned char>(config.opt_bool(opt_key, idx));
else if (opt->default_value) {
const auto *defaults = dynamic_cast<const ConfigOptionBools*>(opt->default_value.get());
if (defaults != nullptr && !defaults->values.empty()) {
const size_t safe_idx = std::min(idx, defaults->values.size() - 1);
ret = static_cast<unsigned char>(defaults->values[safe_idx]);
} else {
ret = static_cast<unsigned char>(false);
}
} else
ret = static_cast<unsigned char>(false);
break;
case coInt:
ret = config.opt_int(opt_key);
+35 -4
View File
@@ -1436,7 +1436,7 @@ Sidebar::Sidebar(Plater *parent)
p->m_sizer_mixed_filaments = new wxBoxSizer(wxVERTICAL);
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
// Title
auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Mixed Colors"));
auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Dithering"));
mixed_title->SetFont(Label::Head_14);
p->m_sizer_mixed_filaments->Add(mixed_title, 0, wxLEFT | wxRIGHT, FromDIP(16));
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
@@ -2205,7 +2205,7 @@ void Sidebar::update_mixed_filament_panel()
// Recreate header.
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Mixed Colors"));
auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Dithering"));
mixed_title->SetFont(Label::Head_14);
p->m_sizer_mixed_filaments->Add(mixed_title, 0, wxLEFT | wxRIGHT, FromDIP(16));
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
@@ -2355,6 +2355,7 @@ void Sidebar::on_filaments_delete(size_t filament_id)
p->m_panel_filament_title->Refresh();
update_ui_from_settings();
dynamic_filament_list.update();
update_mixed_filament_panel();
}
void Sidebar::edit_filament() {
@@ -14317,10 +14318,40 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r
}*/
// update mmu info
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
size_t total_filaments = num_filaments;
if (preset_bundle != nullptr) {
const size_t current_num_physical = preset_bundle->filament_presets.size();
total_filaments = preset_bundle->mixed_filaments.total_filaments(current_num_physical);
}
EnforcerBlockerStateMap state_map;
for (size_t i = 0; i < state_map.size(); ++i)
state_map[i] = EnforcerBlockerType(i);
const bool can_use_remap = replace_filament_id < 0;
static const std::vector<unsigned int> empty_remap;
const std::vector<unsigned int> &id_remap =
preset_bundle != nullptr ? preset_bundle->last_filament_id_remap() : empty_remap;
if (can_use_remap && !id_remap.empty()) {
for (size_t i = 1; i < state_map.size(); ++i) {
const unsigned int mapped = i < id_remap.size() ? id_remap[i] : 0;
if (mapped == 0 || mapped >= state_map.size() || mapped > total_filaments)
state_map[i] = EnforcerBlockerType::NONE;
else
state_map[i] = EnforcerBlockerType(mapped);
}
}
for (ModelObject* mo : wxGetApp().model().objects) {
for (ModelVolume* mv : mo->volumes) {
mv->update_extruder_count_when_delete_filament(num_filaments, filament_id + 1,
replace_filament_id + 1); // this function is 1 base
if (can_use_remap && !id_remap.empty()) {
mv->remap_extruder_ids(total_filaments, state_map);
} else {
mv->update_extruder_count_when_delete_filament(total_filaments, filament_id + 1,
replace_filament_id + 1); // this function is 1 base
}
}
}
+7
View File
@@ -2488,6 +2488,13 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops"
optgroup->append_single_option_line("timelapse_type", "others_settings_special_mode#timelapse");
// Use default (no icon) here to avoid runtime bitmap load failures.
optgroup = page->new_optgroup(L("Dithering"));
optgroup->append_single_option_line("mixed_color_layer_height_a", "others_settings_mixed_colors#layer-height-a");
optgroup->append_single_option_line("mixed_color_layer_height_b", "others_settings_mixed_colors#layer-height-b");
optgroup->append_single_option_line("dithering_z_step_size", "others_settings_mixed_colors#z-step-size");
optgroup->append_single_option_line("dithering_step_painted_zones_only", "others_settings_mixed_colors#painted-zones-only");
optgroup = page->new_optgroup(L("Fuzzy Skin"), L"fuzzy_skin");
optgroup->append_single_option_line("fuzzy_skin", "others_settings_fuzzy_skin");
optgroup->append_single_option_line("fuzzy_skin_mode", "others_settings_fuzzy_skin#fuzzy-skin-mode");