Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new z-dither branch which implements feature described in issue #9388 #10391

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libslic3r/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ set(SLIC3R_SOURCES
TriangleSelectorWrapper.cpp
TriangleSelectorWrapper.hpp
MTUtils.hpp
ZDither.hpp
ZDither.cpp
Zipper.hpp
Zipper.cpp
MinAreaBoundingBox.hpp
Expand Down
23 changes: 13 additions & 10 deletions src/libslic3r/GCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ std::vector<std::pair<coordf_t, GCodeGenerator::ObjectsLayerToPrint>> GCodeGener
coordf_t print_z;
size_t object_idx;
size_t layer_idx;
coordf_t layer_height;
};

std::vector<ObjectsLayerToPrint> per_object(print.objects().size(), ObjectsLayerToPrint());
Expand All @@ -409,11 +410,14 @@ std::vector<std::pair<coordf_t, GCodeGenerator::ObjectsLayerToPrint>> GCodeGener
for (const ObjectLayerToPrint &ltp : per_object[i]) {
ordering_item.print_z = ltp.print_z();
ordering_item.layer_idx = &ltp - &front;
ordering_item.layer_height = ltp.layer()->height;
ordering.emplace_back(ordering_item);
}
}

std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) {
return fabs(oi1.print_z - oi2.print_z) > EPSILON ? oi1.print_z < oi2.print_z : oi1.layer_height < oi2.layer_height;
});

std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print;

Expand All @@ -422,16 +426,16 @@ std::vector<std::pair<coordf_t, GCodeGenerator::ObjectsLayerToPrint>> GCodeGener
// Find the last layer with roughly the same print_z.
size_t j = i + 1;
coordf_t zmax = ordering[i].print_z + EPSILON;
for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
coordf_t hmax = ordering[i].layer_height + EPSILON; // z-dithering may make layers of different height but same print_z
for (; j < ordering.size() && ordering[j].print_z <= zmax && ordering[j].layer_height < hmax; ++j);
// Merge into layers_to_print.
std::pair<coordf_t, ObjectsLayerToPrint> merged;
// Assign an average print_z to the set of layers with nearly equal print_z.
merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
merged.second.assign(print.objects().size(), ObjectLayerToPrint());
// z-dithering may result in 2 layers from the same object in merged
for (; i < j; ++i) {
const OrderingItem& oi = ordering[i];
assert(merged.second[oi.object_idx].layer() == nullptr);
merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]);
merged.second.emplace_back(std::move(per_object[oi.object_idx][oi.layer_idx]));
}
layers_to_print.emplace_back(std::move(merged));
}
Expand Down Expand Up @@ -2064,14 +2068,13 @@ namespace Skirt {
// not at the print_z of the interlaced support material layers.
std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
if (print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt &&
// Bevare of several layers with same print_z but different heights which could happen
// in case of dithered overhanging layers with support
fabs(skirt_done.back() - layer_tools.print_z) > EPSILON &&
// Not enough skirt layers printed yet.
//FIXME infinite or high skirt does not make sense for sequential print!
(skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) {
(layer_tools.print_z < print.skirt_print_z() + EPSILON || print.has_infinite_skirt())) {
bool valid = ! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON;
assert(valid);
// This print_z has not been extruded yet (sequential print)
// FIXME: The skirt_done should not be empty at this point. The check is a workaround
// of https://github.com/prusa3d/PrusaSlicer/issues/5652, but it deserves a real fix.
if (valid) {
#if 0
// Prime just the first printing extruder. This is original Slic3r's implementation.
Expand Down
8 changes: 6 additions & 2 deletions src/libslic3r/Layer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ class Layer
coordf_t print_z; // Z used for printing in unscaled coordinates
coordf_t height; // layer height in unscaled coordinates
coordf_t bottom_z() const { return this->print_z - this->height; }
bool dithered = false; // is this is a layer produced by z-dithering.

//Extrusions estimated to be seriously malformed, estimated during "Estimating curled extrusions" step. These lines should be avoided during fast travels.
CurledLines curled_lines;
Expand Down Expand Up @@ -401,6 +402,8 @@ class Layer
friend class PrintObject;
friend std::vector<Layer*> new_layers(PrintObject*, const std::vector<coordf_t>&);
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
// Create dithering layer. bottom & top >= 0 and <= 1
friend Layer *make_dithered_layer(Layer *refLayer, double bottom, double top);

Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
upper_layer(nullptr), lower_layer(nullptr),
Expand Down Expand Up @@ -465,12 +468,13 @@ class SupportLayer : public Layer
};

template<typename LayerContainer>
inline std::vector<float> zs_from_layers(const LayerContainer &layers)
inline std::vector<float> zs_from_layers(const LayerContainer &layers, bool exclude_dithered = true)
{
std::vector<float> zs;
zs.reserve(layers.size());
for (const Layer *l : layers)
zs.emplace_back((float)l->slice_z);
if (!(l->dithered && exclude_dithered))
zs.emplace_back((float) l->slice_z);
return zs;
}

Expand Down
15 changes: 11 additions & 4 deletions src/libslic3r/LayerRegion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,15 @@ void LayerRegion::make_perimeters(
auto perimeters_begin = uint32_t(m_perimeters.size());
auto gap_fills_begin = uint32_t(m_thin_fills.size());
auto fill_expolygons_begin = uint32_t(fill_expolygons.size());
if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase)
PerimeterGenerator::process_arachne(
bool use_arachne = this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase;
bool dithered_layer = this->layer()->dithered;
// arachne width averaging is not so good for thin areas of dithered layers becuase it may generate open contours
// which make routh attachment/seams between dithered layers and adjacent non-dithered (regular) layers.
// On the other hand, for very narrow regions, classic algothim sometimes makes no perimeters at all.
// In this case we better fall back on arachne algorithm (even if user selected classic).
// Thus the logic of trying classic first for dithered layers and if it makes no perimeters then trying arachne.
if (!use_arachne || dithered_layer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better not to switch Arachne/Classic calls order, neither reformat parameter list, as it causes problem with further merges.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While changing the formatting of parameters could be done and is not a big deal, the alternative to switching Arachne/Classic calls order was considered and turned out to be much worse.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even restore format will be good, as I had problem in merging another PR over dithering. But what is problem with call order? Aren't two calls mutually exclusive? I guess this

if (dithered_layer && m_perimeters.empty() || !dithered_layer && use_arachne)
       PerimeterGenerator::process_arachne(
.........
else
       PerimeterGenerator::process_classic(

should work, or do I miss something?

Copy link
Author

@LRaiz LRaiz Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For dithered layers, Classic paths connect to adjacent non-dithers layers much better than Arachne. This is because Classic tends to create closed perimeter loops, while for narrow regions, Arachne may produce open contours. On the other hand, at times, Classic produces nothing, while Arachne is still capable of making open perimeters. My code for dithered layers attempts to use Classic first, then if Classic fails, use Arachne. Observe m_perimeters.empty() as a check for Classic failure and two if statements as opposed to if/else.

The change in formatting was made by Visual Studio while complying with .clang-format. I will restore the original formatting and attempt to improve the comment describing the logic of the changed code.

PerimeterGenerator::process_classic(
// input:
params,
surface,
Expand All @@ -129,8 +136,8 @@ void LayerRegion::make_perimeters(
m_perimeters,
m_thin_fills,
fill_expolygons);
else
PerimeterGenerator::process_classic(
if (dithered_layer && m_perimeters.empty() || !dithered_layer && use_arachne)
PerimeterGenerator::process_arachne(
// input:
params,
surface,
Expand Down
2 changes: 1 addition & 1 deletion src/libslic3r/Preset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ static std::vector<std::string> s_Preset_print_options {
"wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"perimeter_generator", "z_dither", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"wall_distribution_count", "min_feature_size", "min_bead_width"
};

Expand Down
32 changes: 19 additions & 13 deletions src/libslic3r/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,13 @@ std::string Print::validate(std::vector<std::string>* warnings) const
return _u8L("Variable layer height is not supported with Organic supports.");
}

if (m_default_object_config.z_dither) {
if (m_default_region_config.infill_every_layers > 1)
return _u8L("Z-dither slicing option is not compatible with option to combine infills of multiple layers.");
if (extruders.size() > 1) // Is there a better way to check for a possibility of multimaterial printing?
return _u8L("Z-dither slicing option is currently not supported for printers with multiple extruders.");
}

if (this->has_wipe_tower() && ! m_objects.empty()) {
// Make sure all extruders use same diameter filament and have the same nozzle diameter
// EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments
Expand Down Expand Up @@ -1070,18 +1077,17 @@ void Print::_make_skirt()
// The skirt_height option from config is expressed in layers, but our
// object might have different layer heights, so we need to find the print_z
// of the highest layer involved.
// Note that unless has_infinite_skirt() == true
// the actual skirt might not reach this $skirt_height_z value since the print
// order of objects on each layer is not guaranteed and will not generally
// include the thickest object first. It is just guaranteed that a skirt is
// prepended to the first 'n' layers (with 'n' = skirt_height).
// $skirt_height_z in this case is the highest possible skirt height for safety.
coordf_t skirt_height_z = 0.;
m_skirt_height_z = 0.;
for (const PrintObject *object : m_objects) {
size_t skirt_layers = this->has_infinite_skirt() ?
object->layer_count() :
std::min(size_t(m_config.skirt_height.value), object->layer_count());
skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z);
size_t skirt_layer = this->has_infinite_skirt() ? object->layer_count() - 1: 0;
if (!this->has_infinite_skirt()) {
for (size_t i = 0, non_dithered = 0; i < object->layer_count() && non_dithered < size_t(m_config.skirt_height.value); i++)
if (!object->m_layers[i]->dithered) { // Skip dithered layers
skirt_layer = i;
non_dithered += 1;
}
}
m_skirt_height_z = std::max(m_skirt_height_z, object->m_layers[skirt_layer]->print_z);
}

// Collect points from all layers contained in skirt height.
Expand All @@ -1090,15 +1096,15 @@ void Print::_make_skirt()
Points object_points;
// Get object layers up to skirt_height_z.
for (const Layer *layer : object->m_layers) {
if (layer->print_z > skirt_height_z)
if (layer->print_z > m_skirt_height_z)
break;
for (const ExPolygon &expoly : layer->lslices)
// Collect the outer contour points only, ignore holes for the calculation of the convex hull.
append(object_points, expoly.contour.points);
}
// Get support layers up to skirt_height_z.
for (const SupportLayer *layer : object->support_layers()) {
if (layer->print_z > skirt_height_z)
if (layer->print_z > m_skirt_height_z)
break;
layer->support_fills.collect_points(object_points);
}
Expand Down
4 changes: 4 additions & 0 deletions src/libslic3r/Print.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ class PrintObject : public PrintObjectBaseWithState<Print, PrintObjectStep, posC

void slice_volumes();
// Has any support (not counting the raft).
// find the next layer below or above idx; In case of z-dithering it may be different from incrementing/decrementing idx
int next_layer_index(size_t idx, bool lower) const; // returns int to allow -1
void detect_surfaces_type();
void process_external_surfaces();
void discover_vertical_shells();
Expand Down Expand Up @@ -604,6 +606,7 @@ class Print : public PrintBaseWithState<PrintStep, psCount>
double skirt_first_layer_height() const;
Flow brim_flow() const;
Flow skirt_flow() const;
coordf_t skirt_print_z() const { return m_skirt_height_z; };

std::vector<unsigned int> object_extruders() const;
std::vector<unsigned int> support_material_extruders() const;
Expand Down Expand Up @@ -704,6 +707,7 @@ class Print : public PrintBaseWithState<PrintStep, psCount>
// It does NOT encompass MMU/MMU2 starting (wipe) areas.
Polygon m_first_layer_convex_hull;
Points m_skirt_convex_hull;
coordf_t m_skirt_height_z;

// Following section will be consumed by the GCodeGenerator.
ToolOrdering m_tool_ordering;
Expand Down
10 changes: 8 additions & 2 deletions src/libslic3r/PrintConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2508,6 +2508,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(1));

def = this->add("z_dither", coBool);
def->label = L("Z-dither");
def->category = L("Layers and Perimeters");
def->tooltip = L("This experimental setting is used to halve stairstep effect on low slope surfaces. "
"Near slice periphery it introduces additional layers that are 25% and 50% of nominal layer height.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));

def = this->add("slowdown_below_layer_time", coInts);
def->label = L("Slow down if layer print time is below");
def->tooltip = L("If layer print time is estimated below this number of seconds, print moves "
Expand Down Expand Up @@ -4030,8 +4038,6 @@ void PrintConfigDef::init_sla_params()
def->multiline = true;
def->full_width = true;
def->height = 13;
// TODO currently notes are the only way to pass data
// for non-PrusaResearch printers. We therefore need to always show them
def->mode = comSimple;
def->set_default_value(new ConfigOptionString(""));

Expand Down
1 change: 1 addition & 0 deletions src/libslic3r/PrintConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ PRINT_CONFIG_CLASS_DEFINE(
// The rest
((ConfigOptionBool, thick_bridges))
((ConfigOptionFloat, xy_size_compensation))
((ConfigOptionBool, z_dither))
((ConfigOptionBool, wipe_into_objects))
)

Expand Down
Loading