#include #include "libslic3r.h" #include "Slicing.hpp" #include "SlicingAdaptive.hpp" #include "PrintConfig.hpp" #include "Model.hpp" // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #undef NDEBUG #define DEBUG #define _DEBUG #include "SVG.hpp" #undef assert #include #endif namespace Slic3r { static const coordf_t MIN_LAYER_HEIGHT = 0.01; static const coordf_t MIN_LAYER_HEIGHT_DEFAULT = 0.07; // Minimum layer height for the variable layer height algorithm. inline coordf_t min_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle) { coordf_t min_layer_height = print_config.min_layer_height.get_at(idx_nozzle - 1); return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height); } // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, // it should not be smaller than the minimum layer height. inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle) { coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); coordf_t max_layer_height = print_config.max_layer_height.get_at(idx_nozzle - 1); coordf_t nozzle_dmr = print_config.nozzle_diameter.get_at(idx_nozzle - 1); return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); } // Minimum layer height for the variable layer height algorithm. coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) { coordf_t min_layer_height = print_config.opt_float("min_layer_height", idx_nozzle - 1); return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height); } // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, // it should not be smaller than the minimum layer height. coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) { coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); coordf_t max_layer_height = print_config.opt_float("max_layer_height", idx_nozzle - 1); coordf_t nozzle_dmr = print_config.opt_float("nozzle_diameter", idx_nozzle - 1); return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); } SlicingParameters SlicingParameters::create_from_config( const PrintConfig &print_config, const PrintObjectConfig &object_config, coordf_t object_height, const std::vector &object_extruders) { assert(! print_config.first_layer_height.percent); coordf_t first_layer_height = (print_config.first_layer_height.value <= 0) ? object_config.layer_height.value : print_config.first_layer_height.value; // If object_config.support_material_extruder == 0 resp. object_config.support_material_interface_extruder == 0, // print_config.nozzle_diameter.get_at(size_t(-1)) returns the 0th nozzle diameter, // which is consistent with the requirement that if support_material_extruder == 0 resp. support_material_interface_extruder == 0, // support will not trigger tool change, but it will use the current nozzle instead. // In that case all the nozzles have to be of the same diameter. coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_extruder.value - 1); coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_interface_extruder.value - 1); bool soluble_interface = object_config.support_material_contact_distance.value == 0.; SlicingParameters params; params.layer_height = object_config.layer_height.value; params.first_print_layer_height = first_layer_height; params.first_object_layer_height = first_layer_height; params.object_print_z_min = 0.; params.object_print_z_max = object_height; params.base_raft_layers = object_config.raft_layers.value; params.soluble_interface = soluble_interface; // Miniumum/maximum of the minimum layer height over all extruders. params.min_layer_height = MIN_LAYER_HEIGHT; params.max_layer_height = std::numeric_limits::max(); if (object_config.support_material.value || params.base_raft_layers > 0 || object_config.support_material_enforce_layers > 0) { // Has some form of support. Add the support layers to the minimum / maximum layer height limits. params.min_layer_height = std::max( min_layer_height_from_nozzle(print_config, object_config.support_material_extruder), min_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder)); params.max_layer_height = std::min( max_layer_height_from_nozzle(print_config, object_config.support_material_extruder), max_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder)); params.max_suport_layer_height = params.max_layer_height; } if (object_extruders.empty()) { params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, 0)); params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, 0)); } else { for (unsigned int extruder_id : object_extruders) { params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, extruder_id)); params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, extruder_id)); } } params.min_layer_height = std::min(params.min_layer_height, params.layer_height); params.max_layer_height = std::max(params.max_layer_height, params.layer_height); if (! soluble_interface) { params.gap_raft_object = object_config.raft_contact_distance.value; params.gap_object_support = object_config.support_material_bottom_contact_distance.value; params.gap_support_object = object_config.support_material_contact_distance.value; if (params.gap_object_support <= 0) params.gap_object_support = params.gap_support_object; } if (params.base_raft_layers > 0) { params.interface_raft_layers = (params.base_raft_layers + 1) / 2; params.base_raft_layers -= params.interface_raft_layers; // Use as large as possible layer height for the intermediate raft layers. params.base_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_extruder_dmr); params.interface_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr); params.first_object_layer_bridging = false; params.contact_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr); params.first_object_layer_height = params.layer_height; } if (params.has_raft()) { // Raise first object layer Z by the thickness of the raft itself plus the extra distance required by the support material logic. //FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case. if (params.raft_layers() == 1) { // There is only the contact layer. params.contact_raft_layer_height = first_layer_height; params.raft_contact_top_z = first_layer_height; } else { assert(params.base_raft_layers > 0); assert(params.interface_raft_layers > 0); // Number of the base raft layers is decreased by the first layer. params.raft_base_top_z = first_layer_height + coordf_t(params.base_raft_layers - 1) * params.base_raft_layer_height; // Number of the interface raft layers is decreased by the contact layer. params.raft_interface_top_z = params.raft_base_top_z + coordf_t(params.interface_raft_layers - 1) * params.interface_raft_layer_height; params.raft_contact_top_z = params.raft_interface_top_z + params.contact_raft_layer_height; } coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object; params.object_print_z_min = print_z; params.object_print_z_max += print_z; } params.valid = true; return params; } // Convert layer_config_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, const t_layer_config_ranges &layer_config_ranges) { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector> ranges_non_overlapping; ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair( t_layer_height_range(0., slicing_params.first_object_layer_height), slicing_params.first_object_layer_height)); // The height ranges are sorted lexicographically by low / high layer boundaries. for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); it_range != layer_config_ranges.end(); ++ it_range) { coordf_t lo = it_range->first.first; coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); coordf_t height = it_range->second.option("layer_height")->getFloat(); if (! ranges_non_overlapping.empty()) // Trim current low with the last high. lo = std::max(lo, ranges_non_overlapping.back().first.second); if (lo + EPSILON < hi) // Ignore too narrow ranges. ranges_non_overlapping.push_back(std::pair(t_layer_height_range(lo, hi), height)); } // 2) Convert the trimmed ranges to a height profile, fill in the undefined intervals between z=0 and z=slicing_params.object_print_z_max() // with slicing_params.layer_height std::vector layer_height_profile; for (std::vector>::const_iterator it_range = ranges_non_overlapping.begin(); it_range != ranges_non_overlapping.end(); ++ it_range) { coordf_t lo = it_range->first.first; coordf_t hi = it_range->first.second; coordf_t height = it_range->second; coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; if (lo > last_z + EPSILON) { // Insert a step of normal layer height. layer_height_profile.push_back(last_z); layer_height_profile.push_back(slicing_params.layer_height); layer_height_profile.push_back(lo); layer_height_profile.push_back(slicing_params.layer_height); } // Insert a step of the overriden layer height. layer_height_profile.push_back(lo); layer_height_profile.push_back(height); layer_height_profile.push_back(hi); layer_height_profile.push_back(height); } coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; if (last_z < slicing_params.object_print_z_height()) { // Insert a step of normal layer height up to the object top. layer_height_profile.push_back(last_z); layer_height_profile.push_back(slicing_params.layer_height); layer_height_profile.push_back(slicing_params.object_print_z_height()); layer_height_profile.push_back(slicing_params.layer_height); } return layer_height_profile; } // Based on the work of @platsch // Fill layer_height_profile by heights ensuring a prescribed maximum cusp height. std::vector layer_height_profile_adaptive(const SlicingParameters& slicing_params, const ModelObject& object, float quality_factor) { // 1) Initialize the SlicingAdaptive class with the object meshes. SlicingAdaptive as; as.set_slicing_parameters(slicing_params); as.prepare(object); // 2) Generate layers using the algorithm of @platsch std::vector layer_height_profile; layer_height_profile.push_back(0.0); layer_height_profile.push_back(slicing_params.first_object_layer_height); if (slicing_params.first_object_layer_height_fixed()) { layer_height_profile.push_back(slicing_params.first_object_layer_height); layer_height_profile.push_back(slicing_params.first_object_layer_height); } double print_z = slicing_params.first_object_layer_height; // last facet visited by the as.next_layer_height() function, where the facets are sorted by their increasing Z span. size_t current_facet = 0; // loop until we have at least one layer and the max slice_z reaches the object height while (print_z + EPSILON < slicing_params.object_print_z_height()) { float height = slicing_params.max_layer_height; // Slic3r::debugf "\n Slice layer: %d\n", $id; // determine next layer height float cusp_height = as.next_layer_height(float(print_z), quality_factor, current_facet); #if 0 // check for horizontal features and object size if (this->config.match_horizontal_surfaces.value) { coordf_t horizontal_dist = as.horizontal_facet_distance(print_z + height, min_layer_height); if ((horizontal_dist < min_layer_height) && (horizontal_dist > 0)) { #ifdef SLIC3R_DEBUG std::cout << "Horizontal feature ahead, distance: " << horizontal_dist << std::endl; #endif // can we shrink the current layer a bit? if (height-(min_layer_height - horizontal_dist) > min_layer_height) { // yes we can height -= (min_layer_height - horizontal_dist); #ifdef SLIC3R_DEBUG std::cout << "Shrink layer height to " << height << std::endl; #endif } else { // no, current layer would become too thin height += horizontal_dist; #ifdef SLIC3R_DEBUG std::cout << "Widen layer height to " << height << std::endl; #endif } } } #endif height = std::min(cusp_height, height); // apply z-gradation /* my $gradation = $self->config->get_value('adaptive_slicing_z_gradation'); if($gradation > 0) { $height = $height - unscale((scale($height)) % (scale($gradation))); } */ // look for an applicable custom range /* if (my $range = first { $_->[0] <= $print_z && $_->[1] > $print_z } @{$self->layer_height_ranges}) { $height = $range->[2]; # if user set custom height to zero we should just skip the range and resume slicing over it if ($height == 0) { $print_z += $range->[1] - $range->[0]; next; } } */ layer_height_profile.push_back(print_z); layer_height_profile.push_back(height); print_z += height; } double z_gap = slicing_params.object_print_z_height() - layer_height_profile[layer_height_profile.size() - 2]; if (z_gap > 0.0) { layer_height_profile.push_back(slicing_params.object_print_z_height()); layer_height_profile.push_back(std::clamp(z_gap, slicing_params.min_layer_height, slicing_params.max_layer_height)); } return layer_height_profile; } std::vector smooth_height_profile(const std::vector& profile, const SlicingParameters& slicing_params, const HeightProfileSmoothingParams& smoothing_params) { auto gauss_blur = [&slicing_params](const std::vector& profile, const HeightProfileSmoothingParams& smoothing_params) -> std::vector { auto gauss_kernel = [] (unsigned int radius) -> std::vector { unsigned int size = 2 * radius + 1; std::vector ret; ret.reserve(size); // Reworked from static inline int getGaussianKernelSize(float sigma) taken from opencv-4.1.2\modules\features2d\src\kaze\AKAZEFeatures.cpp double sigma = 0.3 * (double)(radius - 1) + 0.8; double two_sq_sigma = 2.0 * sigma * sigma; double inv_root_two_pi_sq_sigma = 1.0 / ::sqrt(M_PI * two_sq_sigma); for (unsigned int i = 0; i < size; ++i) { double x = (double)i - (double)radius; ret.push_back(inv_root_two_pi_sq_sigma * ::exp(-x * x / two_sq_sigma)); } return ret; }; // skip first layer ? size_t skip_count = slicing_params.first_object_layer_height_fixed() ? 4 : 0; // not enough data to smmoth if ((int)profile.size() - (int)skip_count < 6) return profile; unsigned int radius = std::max(smoothing_params.radius, (unsigned int)1); std::vector kernel = gauss_kernel(radius); int two_radius = 2 * (int)radius; std::vector ret; size_t size = profile.size(); ret.reserve(size); // leave first layer untouched for (size_t i = 0; i < skip_count; ++i) { ret.push_back(profile[i]); } // smooth the rest of the profile by biasing a gaussian blur // the bias moves the smoothed profile closer to the min_layer_height double delta_h = slicing_params.max_layer_height - slicing_params.min_layer_height; double inv_delta_h = (delta_h != 0.0) ? 1.0 / delta_h : 1.0; double max_dz_band = (double)radius * slicing_params.layer_height; for (size_t i = skip_count; i < size; i += 2) { double zi = profile[i]; double hi = profile[i + 1]; ret.push_back(zi); ret.push_back(0.0); double& height = ret.back(); int begin = std::max((int)i - two_radius, (int)skip_count); int end = std::min((int)i + two_radius, (int)size - 2); double weight_total = 0.0; for (int j = begin; j <= end; j += 2) { int kernel_id = radius + (j - (int)i) / 2; double dz = std::abs(zi - profile[j]); if (dz * slicing_params.layer_height <= max_dz_band) { double dh = std::abs(slicing_params.max_layer_height - profile[j + 1]); double weight = kernel[kernel_id] * sqrt(dh * inv_delta_h); height += weight * profile[j + 1]; weight_total += weight; } } height = std::clamp(weight_total == 0 ? hi : height / weight_total, slicing_params.min_layer_height, slicing_params.max_layer_height); if (smoothing_params.keep_min) height = std::min(height, hi); } return ret; }; return gauss_blur(profile, smoothing_params); } void adjust_layer_height_profile( const SlicingParameters &slicing_params, std::vector &layer_height_profile, coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, LayerHeightEditActionType action) { // Constrain the profile variability by the 1st layer height. std::pair z_span_variable = std::pair( slicing_params.first_object_layer_height_fixed() ? slicing_params.first_object_layer_height : 0., slicing_params.object_print_z_height()); if (z < z_span_variable.first || z > z_span_variable.second) return; assert(layer_height_profile.size() >= 2); assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); // 1) Get the current layer thickness at z. coordf_t current_layer_height = slicing_params.layer_height; for (size_t i = 0; i < layer_height_profile.size(); i += 2) { if (i + 2 == layer_height_profile.size()) { current_layer_height = layer_height_profile[i + 1]; break; } else if (layer_height_profile[i + 2] > z) { coordf_t z1 = layer_height_profile[i]; coordf_t h1 = layer_height_profile[i + 1]; coordf_t z2 = layer_height_profile[i + 2]; coordf_t h2 = layer_height_profile[i + 3]; current_layer_height = lerp(h1, h2, (z - z1) / (z2 - z1)); break; } } // 2) Is it possible to apply the delta? switch (action) { case LAYER_HEIGHT_EDIT_ACTION_DECREASE: layer_thickness_delta = - layer_thickness_delta; // fallthrough case LAYER_HEIGHT_EDIT_ACTION_INCREASE: if (layer_thickness_delta > 0) { if (current_layer_height >= slicing_params.max_layer_height - EPSILON) return; layer_thickness_delta = std::min(layer_thickness_delta, slicing_params.max_layer_height - current_layer_height); } else { if (current_layer_height <= slicing_params.min_layer_height + EPSILON) return; layer_thickness_delta = std::max(layer_thickness_delta, slicing_params.min_layer_height - current_layer_height); } break; case LAYER_HEIGHT_EDIT_ACTION_REDUCE: case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: layer_thickness_delta = std::abs(layer_thickness_delta); layer_thickness_delta = std::min(layer_thickness_delta, std::abs(slicing_params.layer_height - current_layer_height)); if (layer_thickness_delta < EPSILON) return; break; default: assert(false); break; } // 3) Densify the profile inside z +- band_width/2, remove duplicate Zs from the height profile inside the band. coordf_t lo = std::max(z_span_variable.first, z - 0.5 * band_width); // Do not limit the upper side of the band, so that the modifications to the top point of the profile will be allowed. coordf_t hi = z + 0.5 * band_width; coordf_t z_step = 0.1; size_t idx = 0; while (idx < layer_height_profile.size() && layer_height_profile[idx] < lo) idx += 2; idx -= 2; std::vector profile_new; profile_new.reserve(layer_height_profile.size()); assert(idx >= 0 && idx + 1 < layer_height_profile.size()); profile_new.insert(profile_new.end(), layer_height_profile.begin(), layer_height_profile.begin() + idx + 2); coordf_t zz = lo; size_t i_resampled_start = profile_new.size(); while (zz < hi) { size_t next = idx + 2; coordf_t z1 = layer_height_profile[idx]; coordf_t h1 = layer_height_profile[idx + 1]; coordf_t height = h1; if (next < layer_height_profile.size()) { coordf_t z2 = layer_height_profile[next]; coordf_t h2 = layer_height_profile[next + 1]; height = lerp(h1, h2, (zz - z1) / (z2 - z1)); } // Adjust height by layer_thickness_delta. coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; switch (action) { case LAYER_HEIGHT_EDIT_ACTION_INCREASE: case LAYER_HEIGHT_EDIT_ACTION_DECREASE: height += weight * layer_thickness_delta; break; case LAYER_HEIGHT_EDIT_ACTION_REDUCE: { coordf_t delta = height - slicing_params.layer_height; coordf_t step = weight * layer_thickness_delta; step = (std::abs(delta) > step) ? (delta > 0) ? -step : step : -delta; height += step; break; } case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: { // Don't modify the profile during resampling process, do it at the next step. break; } default: assert(false); break; } height = std::clamp(height, slicing_params.min_layer_height, slicing_params.max_layer_height); if (zz == z_span_variable.second) { // This is the last point of the profile. if (profile_new[profile_new.size() - 2] + EPSILON > zz) { profile_new.pop_back(); profile_new.pop_back(); } profile_new.push_back(zz); profile_new.push_back(height); idx = layer_height_profile.size(); break; } // Avoid entering a too short segment. if (profile_new[profile_new.size() - 2] + EPSILON < zz) { profile_new.push_back(zz); profile_new.push_back(height); } // Limit zz to the object height, so the next iteration the last profile point will be set. zz = std::min(zz + z_step, z_span_variable.second); idx = next; while (idx < layer_height_profile.size() && layer_height_profile[idx] < zz) idx += 2; idx -= 2; } idx += 2; assert(idx > 0); size_t i_resampled_end = profile_new.size(); if (idx < layer_height_profile.size()) { assert(zz >= layer_height_profile[idx - 2]); assert(zz <= layer_height_profile[idx]); profile_new.insert(profile_new.end(), layer_height_profile.begin() + idx, layer_height_profile.end()); } else if (profile_new[profile_new.size() - 2] + 0.5 * EPSILON < z_span_variable.second) { profile_new.insert(profile_new.end(), layer_height_profile.end() - 2, layer_height_profile.end()); } layer_height_profile = std::move(profile_new); if (action == LAYER_HEIGHT_EDIT_ACTION_SMOOTH) { if (i_resampled_start == 0) ++ i_resampled_start; if (i_resampled_end == layer_height_profile.size()) i_resampled_end -= 2; size_t n_rounds = 6; for (size_t i_round = 0; i_round < n_rounds; ++ i_round) { profile_new = layer_height_profile; for (size_t i = i_resampled_start; i < i_resampled_end; i += 2) { coordf_t zz = profile_new[i]; coordf_t t = std::abs(zz - z) < 0.5 * band_width ? (0.25 + 0.25 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; assert(t >= 0. && t <= 0.5000001); if (i == 0) layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i + 3]; else if (i + 1 == profile_new.size()) layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i - 1]; else layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + 0.5 * t * (profile_new[i - 1] + profile_new[i + 3]); } } } assert(layer_height_profile.size() > 2); assert(layer_height_profile.size() % 2 == 0); assert(layer_height_profile[0] == 0.); assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); #ifdef _DEBUG for (size_t i = 2; i < layer_height_profile.size(); i += 2) assert(layer_height_profile[i - 2] <= layer_height_profile[i]); for (size_t i = 1; i < layer_height_profile.size(); i += 2) { assert(layer_height_profile[i] > slicing_params.min_layer_height - EPSILON); assert(layer_height_profile[i] < slicing_params.max_layer_height + EPSILON); } #endif /* _DEBUG */ } // Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. std::vector generate_object_layers( const SlicingParameters &slicing_params, const std::vector &layer_height_profile) { assert(! layer_height_profile.empty()); coordf_t print_z = 0; coordf_t height = 0; std::vector out; if (slicing_params.first_object_layer_height_fixed()) { out.push_back(0); print_z = slicing_params.first_object_layer_height; out.push_back(print_z); } size_t idx_layer_height_profile = 0; // loop until we have at least one layer and the max slice_z reaches the object height coordf_t slice_z = print_z + 0.5 * slicing_params.min_layer_height; while (slice_z < slicing_params.object_print_z_height()) { height = slicing_params.min_layer_height; if (idx_layer_height_profile < layer_height_profile.size()) { size_t next = idx_layer_height_profile + 2; for (;;) { if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next]) break; idx_layer_height_profile = next; next += 2; } coordf_t z1 = layer_height_profile[idx_layer_height_profile]; coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1]; height = h1; if (next < layer_height_profile.size()) { coordf_t z2 = layer_height_profile[next]; coordf_t h2 = layer_height_profile[next + 1]; height = lerp(h1, h2, (slice_z - z1) / (z2 - z1)); assert(height >= slicing_params.min_layer_height - EPSILON && height <= slicing_params.max_layer_height + EPSILON); } } slice_z = print_z + 0.5 * height; if (slice_z >= slicing_params.object_print_z_height()) break; assert(height > slicing_params.min_layer_height - EPSILON); assert(height < slicing_params.max_layer_height + EPSILON); out.push_back(print_z); print_z += height; slice_z = print_z + 0.5 * slicing_params.min_layer_height; out.push_back(print_z); } //FIXME Adjust the last layer to align with the top object layer exactly? return out; } int generate_layer_height_texture( const SlicingParameters &slicing_params, const std::vector &layers, void *data, int rows, int cols, bool level_of_detail_2nd_level) { // https://github.com/aschn/gnuplot-colorbrewer std::vector palette_raw; palette_raw.push_back(Vec3crd(0x01A, 0x098, 0x050)); palette_raw.push_back(Vec3crd(0x066, 0x0BD, 0x063)); palette_raw.push_back(Vec3crd(0x0A6, 0x0D9, 0x06A)); palette_raw.push_back(Vec3crd(0x0D9, 0x0F1, 0x0EB)); palette_raw.push_back(Vec3crd(0x0FE, 0x0E6, 0x0EB)); palette_raw.push_back(Vec3crd(0x0FD, 0x0AE, 0x061)); palette_raw.push_back(Vec3crd(0x0F4, 0x06D, 0x043)); palette_raw.push_back(Vec3crd(0x0D7, 0x030, 0x027)); // Clear the main texture and the 2nd LOD level. // memset(data, 0, rows * cols * (level_of_detail_2nd_level ? 5 : 4)); // 2nd LOD level data start unsigned char *data1 = reinterpret_cast(data) + rows * cols * 4; int ncells = std::min((cols-1) * rows, int(ceil(16. * (slicing_params.object_print_z_height() / slicing_params.min_layer_height)))); int ncells1 = ncells / 2; int cols1 = cols / 2; coordf_t z_to_cell = coordf_t(ncells-1) / slicing_params.object_print_z_height(); coordf_t cell_to_z = slicing_params.object_print_z_height() / coordf_t(ncells-1); coordf_t z_to_cell1 = coordf_t(ncells1-1) / slicing_params.object_print_z_height(); // for color scaling coordf_t hscale = 2.f * std::max(slicing_params.max_layer_height - slicing_params.layer_height, slicing_params.layer_height - slicing_params.min_layer_height); if (hscale == 0) // All layers have the same height. Provide some height scale to avoid division by zero. hscale = slicing_params.layer_height; for (size_t idx_layer = 0; idx_layer < layers.size(); idx_layer += 2) { coordf_t lo = layers[idx_layer]; coordf_t hi = layers[idx_layer + 1]; coordf_t mid = 0.5f * (lo + hi); assert(mid <= slicing_params.object_print_z_height()); coordf_t h = hi - lo; hi = std::min(hi, slicing_params.object_print_z_height()); int cell_first = std::clamp(int(ceil(lo * z_to_cell)), 0, ncells-1); int cell_last = std::clamp(int(floor(hi * z_to_cell)), 0, ncells-1); for (int cell = cell_first; cell <= cell_last; ++ cell) { coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale; int idx1 = std::clamp(int(floor(idxf)), 0, int(palette_raw.size() - 1)); int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); coordf_t t = idxf - coordf_t(idx1); const Vec3crd &color1 = palette_raw[idx1]; const Vec3crd &color2 = palette_raw[idx2]; coordf_t z = cell_to_z * coordf_t(cell); assert(lo - EPSILON <= z && z <= hi + EPSILON); // Intensity profile to visualize the layers. coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h); // Color mapping from layer height to RGB. Vec3d color( intensity * lerp(coordf_t(color1(0)), coordf_t(color2(0)), t), intensity * lerp(coordf_t(color1(1)), coordf_t(color2(1)), t), intensity * lerp(coordf_t(color1(2)), coordf_t(color2(2)), t)); int row = cell / (cols - 1); int col = cell - row * (cols - 1); assert(row >= 0 && row < rows); assert(col >= 0 && col < cols); unsigned char *ptr = (unsigned char*)data + (row * cols + col) * 4; ptr[0] = (unsigned char)std::clamp(int(floor(color(0) + 0.5)), 0, 255); ptr[1] = (unsigned char)std::clamp(int(floor(color(1) + 0.5)), 0, 255); ptr[2] = (unsigned char)std::clamp(int(floor(color(2) + 0.5)), 0, 255); ptr[3] = 255; if (col == 0 && row > 0) { // Duplicate the first value in a row as a last value of the preceding row. ptr[-4] = ptr[0]; ptr[-3] = ptr[1]; ptr[-2] = ptr[2]; ptr[-1] = ptr[3]; } } if (level_of_detail_2nd_level) { cell_first = std::clamp(int(ceil(lo * z_to_cell1)), 0, ncells1-1); cell_last = std::clamp(int(floor(hi * z_to_cell1)), 0, ncells1-1); for (int cell = cell_first; cell <= cell_last; ++ cell) { coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale; int idx1 = std::clamp(int(floor(idxf)), 0, int(palette_raw.size() - 1)); int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); coordf_t t = idxf - coordf_t(idx1); const Vec3crd &color1 = palette_raw[idx1]; const Vec3crd &color2 = palette_raw[idx2]; // Color mapping from layer height to RGB. Vec3d color( lerp(coordf_t(color1(0)), coordf_t(color2(0)), t), lerp(coordf_t(color1(1)), coordf_t(color2(1)), t), lerp(coordf_t(color1(2)), coordf_t(color2(2)), t)); int row = cell / (cols1 - 1); int col = cell - row * (cols1 - 1); assert(row >= 0 && row < rows/2); assert(col >= 0 && col < cols/2); unsigned char *ptr = data1 + (row * cols1 + col) * 4; ptr[0] = (unsigned char)std::clamp(int(floor(color(0) + 0.5)), 0, 255); ptr[1] = (unsigned char)std::clamp(int(floor(color(1) + 0.5)), 0, 255); ptr[2] = (unsigned char)std::clamp(int(floor(color(2) + 0.5)), 0, 255); ptr[3] = 255; if (col == 0 && row > 0) { // Duplicate the first value in a row as a last value of the preceding row. ptr[-4] = ptr[0]; ptr[-3] = ptr[1]; ptr[-2] = ptr[2]; ptr[-1] = ptr[3]; } } } } // Returns number of cells of the 0th LOD level. return ncells; } }; // namespace Slic3r