From 3cfd8c656b7a0102a4a3a9928249365465d9d4bd Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Sun, 24 May 2026 14:17:12 +0100 Subject: [PATCH] Create top-surface images with variable line widths (experimental) --- src/libslic3r/ExtrusionEntityCollection.cpp | 16 + src/libslic3r/ExtrusionEntityCollection.hpp | 43 +- src/libslic3r/Fill/Fill.cpp | 715 +++++++++++++++++++- src/libslic3r/GCode/ToolOrdering.cpp | 35 +- src/libslic3r/GCode/ToolOrdering.hpp | 1 + src/libslic3r/TextureMapping.cpp | 66 ++ src/libslic3r/TextureMapping.hpp | 34 + src/libslic3r/TextureMappingOffset.cpp | 22 +- src/libslic3r/TextureMappingOffset.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 216 +++++- 10 files changed, 1111 insertions(+), 41 deletions(-) diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 11c6e9c94d5..cb4a5bdf7c0 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -20,6 +20,12 @@ void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) : no_sort(false) + , texture_mapping_extruder_override(-1) + , texture_mapping_top_surface_image(false) + , texture_mapping_top_surface_zone_id(0) + , texture_mapping_top_surface_desired_component_id(0) + , texture_mapping_top_surface_stack_depth(-1) + , texture_mapping_top_surface_fixed_coloring(true) { this->append(paths); } @@ -32,6 +38,11 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE this->entities[i] = this->entities[i]->clone(); this->no_sort = other.no_sort; this->texture_mapping_extruder_override = other.texture_mapping_extruder_override; + this->texture_mapping_top_surface_image = other.texture_mapping_top_surface_image; + this->texture_mapping_top_surface_zone_id = other.texture_mapping_top_surface_zone_id; + this->texture_mapping_top_surface_desired_component_id = other.texture_mapping_top_surface_desired_component_id; + this->texture_mapping_top_surface_stack_depth = other.texture_mapping_top_surface_stack_depth; + this->texture_mapping_top_surface_fixed_coloring = other.texture_mapping_top_surface_fixed_coloring; return *this; } @@ -40,6 +51,11 @@ void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c) std::swap(this->entities, c.entities); std::swap(this->no_sort, c.no_sort); std::swap(this->texture_mapping_extruder_override, c.texture_mapping_extruder_override); + std::swap(this->texture_mapping_top_surface_image, c.texture_mapping_top_surface_image); + std::swap(this->texture_mapping_top_surface_zone_id, c.texture_mapping_top_surface_zone_id); + std::swap(this->texture_mapping_top_surface_desired_component_id, c.texture_mapping_top_surface_desired_component_id); + std::swap(this->texture_mapping_top_surface_stack_depth, c.texture_mapping_top_surface_stack_depth); + std::swap(this->texture_mapping_top_surface_fixed_coloring, c.texture_mapping_top_surface_fixed_coloring); } void ExtrusionEntityCollection::clear() diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index ebcafebf824..1b71b80b77a 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -32,9 +32,41 @@ public: ExtrusionEntitiesPtr entities; // we own these entities bool no_sort; int texture_mapping_extruder_override; - ExtrusionEntityCollection(): no_sort(false), texture_mapping_extruder_override(-1) {} - ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), texture_mapping_extruder_override(other.texture_mapping_extruder_override), is_reverse(other.is_reverse) { this->append(other.entities); } - ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort), texture_mapping_extruder_override(other.texture_mapping_extruder_override), is_reverse(other.is_reverse) {} + bool texture_mapping_top_surface_image; + unsigned int texture_mapping_top_surface_zone_id; + unsigned int texture_mapping_top_surface_desired_component_id; + int texture_mapping_top_surface_stack_depth; + bool texture_mapping_top_surface_fixed_coloring; + ExtrusionEntityCollection() + : no_sort(false) + , texture_mapping_extruder_override(-1) + , texture_mapping_top_surface_image(false) + , texture_mapping_top_surface_zone_id(0) + , texture_mapping_top_surface_desired_component_id(0) + , texture_mapping_top_surface_stack_depth(-1) + , texture_mapping_top_surface_fixed_coloring(true) + {} + ExtrusionEntityCollection(const ExtrusionEntityCollection &other) + : no_sort(other.no_sort) + , texture_mapping_extruder_override(other.texture_mapping_extruder_override) + , texture_mapping_top_surface_image(other.texture_mapping_top_surface_image) + , texture_mapping_top_surface_zone_id(other.texture_mapping_top_surface_zone_id) + , texture_mapping_top_surface_desired_component_id(other.texture_mapping_top_surface_desired_component_id) + , texture_mapping_top_surface_stack_depth(other.texture_mapping_top_surface_stack_depth) + , texture_mapping_top_surface_fixed_coloring(other.texture_mapping_top_surface_fixed_coloring) + , is_reverse(other.is_reverse) + { this->append(other.entities); } + ExtrusionEntityCollection(ExtrusionEntityCollection &&other) + : entities(std::move(other.entities)) + , no_sort(other.no_sort) + , texture_mapping_extruder_override(other.texture_mapping_extruder_override) + , texture_mapping_top_surface_image(other.texture_mapping_top_surface_image) + , texture_mapping_top_surface_zone_id(other.texture_mapping_top_surface_zone_id) + , texture_mapping_top_surface_desired_component_id(other.texture_mapping_top_surface_desired_component_id) + , texture_mapping_top_surface_stack_depth(other.texture_mapping_top_surface_stack_depth) + , texture_mapping_top_surface_fixed_coloring(other.texture_mapping_top_surface_fixed_coloring) + , is_reverse(other.is_reverse) + {} explicit ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other); ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) @@ -42,6 +74,11 @@ public: this->entities = std::move(other.entities); this->no_sort = other.no_sort; this->texture_mapping_extruder_override = other.texture_mapping_extruder_override; + this->texture_mapping_top_surface_image = other.texture_mapping_top_surface_image; + this->texture_mapping_top_surface_zone_id = other.texture_mapping_top_surface_zone_id; + this->texture_mapping_top_surface_desired_component_id = other.texture_mapping_top_surface_desired_component_id; + this->texture_mapping_top_surface_stack_depth = other.texture_mapping_top_surface_stack_depth; + this->texture_mapping_top_surface_fixed_coloring = other.texture_mapping_top_surface_fixed_coloring; is_reverse = other.is_reverse; return *this; } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index fb297852751..1915cba6183 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -3,11 +3,15 @@ #include #include "../ClipperUtils.hpp" +#include "../Color.hpp" +#include "../ColorSolver.hpp" #include "../Geometry.hpp" #include "../Layer.hpp" #include "../Print.hpp" #include "../PrintConfig.hpp" #include "../Surface.hpp" +#include "../TextureMapping.hpp" +#include "../TextureMappingOffset.hpp" #include "AABBTreeLines.hpp" #include "ExtrusionEntity.hpp" @@ -20,6 +24,10 @@ #include "FillConcentric.hpp" #include "libslic3r.h" +#include +#include +#include + namespace Slic3r { // Calculate infill rotation angle (in radians) for a given layer from a rotation template. @@ -271,6 +279,17 @@ struct SurfaceFillParams // Params for Lateral honeycomb float infill_overhang_angle = 60.f; + bool texture_mapping_top_surface_image = false; + unsigned int texture_mapping_top_surface_zone_id = 0; + unsigned int texture_mapping_top_surface_component_id = 0; + int texture_mapping_top_surface_stack_depth = -1; + bool texture_mapping_top_surface_fixed_coloring = true; + float texture_mapping_top_surface_min_width_mm = TextureMappingZone::DefaultTopSurfaceImageMinLineWidthMm; + float texture_mapping_top_surface_max_width_mm = TextureMappingZone::DefaultTopSurfaceImageMaxLineWidthMm; + bool texture_mapping_top_surface_same_layer_partition = false; + int texture_mapping_top_surface_component_index = 0; + int texture_mapping_top_surface_component_count = 0; + bool operator<(const SurfaceFillParams &rhs) const { #define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false; #define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false; @@ -303,6 +322,16 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis); RETURN_COMPARE_NON_EQUAL(infill_lock_depth); RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_image); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_zone_id); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_component_id); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_stack_depth); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_fixed_coloring); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_min_width_mm); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_max_width_mm); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_same_layer_partition); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_component_index); + RETURN_COMPARE_NON_EQUAL(texture_mapping_top_surface_component_count); return false; } @@ -330,7 +359,17 @@ struct SurfaceFillParams this->lateral_lattice_angle_2 == rhs.lateral_lattice_angle_2 && this->infill_lock_depth == rhs.infill_lock_depth && this->skin_infill_depth == rhs.skin_infill_depth && - this->infill_overhang_angle == rhs.infill_overhang_angle; + this->infill_overhang_angle == rhs.infill_overhang_angle && + this->texture_mapping_top_surface_image == rhs.texture_mapping_top_surface_image && + this->texture_mapping_top_surface_zone_id == rhs.texture_mapping_top_surface_zone_id && + this->texture_mapping_top_surface_component_id == rhs.texture_mapping_top_surface_component_id && + this->texture_mapping_top_surface_stack_depth == rhs.texture_mapping_top_surface_stack_depth && + this->texture_mapping_top_surface_fixed_coloring == rhs.texture_mapping_top_surface_fixed_coloring && + this->texture_mapping_top_surface_min_width_mm == rhs.texture_mapping_top_surface_min_width_mm && + this->texture_mapping_top_surface_max_width_mm == rhs.texture_mapping_top_surface_max_width_mm && + this->texture_mapping_top_surface_same_layer_partition == rhs.texture_mapping_top_surface_same_layer_partition && + this->texture_mapping_top_surface_component_index == rhs.texture_mapping_top_surface_component_index && + this->texture_mapping_top_surface_component_count == rhs.texture_mapping_top_surface_component_count; } }; @@ -346,6 +385,543 @@ struct SurfaceFill { ExPolygons no_overlap_expolygons; }; +struct TopSurfaceImageStackSlice { + unsigned int component_id = 0; + int depth = 0; + size_t component_index = 0; + size_t component_count = 0; + bool same_layer_partition = false; + float angle_rad = float(PI / 4.0); + ExPolygons area; +}; + +struct TopSurfaceImageRegionPlan { + const TextureMappingZone *zone = nullptr; + unsigned int zone_id = 0; + std::vector components_bottom_to_top; + std::vector slices; + float min_width_mm = TextureMappingZone::DefaultTopSurfaceImageMinLineWidthMm; + float max_width_mm = TextureMappingZone::DefaultTopSurfaceImageMaxLineWidthMm; + bool fixed_coloring = true; + bool same_layer_partition = false; + int colored_top_layers = TextureMappingZone::DefaultTopSurfaceImageColoredTopLayers; +}; + +static float top_surface_image_filament_luminance(const PrintConfig &config, unsigned int component_id) +{ + ColorRGB color; + if (component_id == 0 || component_id > config.filament_colour.values.size() || + !decode_color(config.filament_colour.get_at(size_t(component_id - 1)), color)) + return std::numeric_limits::max(); + return 0.2126f * color.r() + 0.7152f * color.g() + 0.0722f * color.b(); +} + +static std::vector top_surface_image_components_bottom_to_top(const TextureMappingZone &zone, + const PrintConfig &config, + std::vector components) +{ + components.erase(std::remove_if(components.begin(), components.end(), [](unsigned int id) { return id == 0; }), components.end()); + components.erase(std::unique(components.begin(), components.end()), components.end()); + if (components.empty()) + return components; + + bool has_complete_td = true; + for (unsigned int component_id : components) { + const size_t idx = size_t(component_id - 1); + const float td = idx < zone.filament_transmission_distances_mm.size() ? + zone.filament_transmission_distances_mm[idx] : + 0.f; + if (!std::isfinite(td) || td <= 0.f) { + has_complete_td = false; + break; + } + } + if (has_complete_td) { + std::stable_sort(components.begin(), components.end(), [&zone](unsigned int lhs, unsigned int rhs) { + return zone.filament_transmission_distances_mm[size_t(lhs - 1)] < + zone.filament_transmission_distances_mm[size_t(rhs - 1)]; + }); + return components; + } + + auto black_it = std::min_element(components.begin(), components.end(), [&config](unsigned int lhs, unsigned int rhs) { + return top_surface_image_filament_luminance(config, lhs) < top_surface_image_filament_luminance(config, rhs); + }); + if (black_it != components.end() && black_it != components.begin()) + std::rotate(components.begin(), black_it, std::next(black_it)); + return components; +} + +static std::optional> top_surface_image_equal_blend_background(const PrintConfig &config, + const std::vector &component_ids) +{ + std::vector> colors; + colors.reserve(component_ids.size()); + for (unsigned int component_id : component_ids) { + if (component_id == 0 || component_id > config.filament_colour.values.size()) + return std::nullopt; + ColorRGB color; + if (!decode_color(config.filament_colour.get_at(size_t(component_id - 1)), color)) + return std::nullopt; + colors.push_back({ color.r(), color.g(), color.b() }); + } + if (colors.empty()) + return std::nullopt; + std::vector weights(colors.size(), 1.f / float(colors.size())); + const std::array mixed = mix_color_solver_components(colors, weights, ColorSolverMixModel::PigmentPainter); + return std::array { mixed[0], mixed[1], mixed[2], 1.f }; +} + +static ExPolygons top_surface_image_visible_top_mask(const Layer &layer, unsigned int zone_id) +{ + ExPolygons mask; + for (const LayerRegion *layerm : layer.regions()) { + if (layerm == nullptr || unsigned(std::max(0, layerm->region().config().solid_infill_filament.value)) != zone_id) + continue; + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.is_top()) + mask.emplace_back(surface.expolygon); + } + return mask.size() > 1 ? union_ex(mask) : mask; +} + +static std::vector top_surface_image_region_plans(const Layer &layer) +{ + std::vector plans(layer.regions().size()); + const PrintObject *object = layer.object(); + if (object == nullptr || object->print() == nullptr) + return plans; + + const Print *print = object->print(); + const PrintConfig &print_config = print->config(); + const TextureMappingManager &texture_mgr = print->texture_mapping_manager(); + const size_t num_physical = print_config.filament_colour.values.size(); + if (num_physical == 0) + return plans; + + Polygons current_layer_perimeters; + for (const LayerRegion *layerm : layer.regions()) + if (layerm != nullptr) + layerm->perimeters.polygons_covered_by_width(current_layer_perimeters, 0.f); + + for (size_t region_id = 0; region_id < layer.regions().size(); ++region_id) { + const LayerRegion *layerm = layer.regions()[region_id]; + if (layerm == nullptr) + continue; + const int raw_zone_id = layerm->region().config().solid_infill_filament.value; + if (raw_zone_id <= 0) + continue; + const unsigned int zone_id = unsigned(raw_zone_id); + const TextureMappingZone *zone = texture_mgr.zone_from_id(zone_id); + if (zone == nullptr || !zone->enabled || zone->deleted || !zone->top_surface_image_printing_active()) + continue; + + std::vector filament_colours = print_config.filament_colour.values; + filament_colours.resize(num_physical, "#FFFFFF"); + std::vector components = + TextureMappingManager::effective_texture_component_ids(*zone, num_physical, filament_colours); + components.erase(std::remove_if(components.begin(), components.end(), [num_physical](unsigned int id) { + return id == 0 || id > num_physical; + }), components.end()); + components.erase(std::unique(components.begin(), components.end()), components.end()); + if (components.empty()) + continue; + + TopSurfaceImageRegionPlan plan; + plan.zone = zone; + plan.zone_id = zone_id; + plan.same_layer_partition = + zone->top_surface_image_printing_method == int(TextureMappingZone::TopSurfaceImageSameLayer45Partition); + plan.components_bottom_to_top = plan.same_layer_partition ? + components : + top_surface_image_components_bottom_to_top(*zone, print_config, components); + if (plan.components_bottom_to_top.empty()) + continue; + plan.max_width_mm = std::clamp(zone->top_surface_image_max_line_width_mm, + TextureMappingZone::MinTopSurfaceImageLineWidthMm, + TextureMappingZone::MaxTopSurfaceImageLineWidthMm); + plan.min_width_mm = std::clamp(zone->top_surface_image_min_line_width_mm, + TextureMappingZone::MinTopSurfaceImageLineWidthMm, + plan.max_width_mm); + plan.fixed_coloring = zone->top_surface_image_fixed_coloring_filaments; + plan.colored_top_layers = std::clamp(zone->top_surface_image_colored_top_layers, + TextureMappingZone::MinTopSurfaceImageColoredTopLayers, + TextureMappingZone::MaxTopSurfaceImageColoredTopLayers); + + const int stack_depth = plan.same_layer_partition ? + plan.colored_top_layers : + int(plan.components_bottom_to_top.size()); + for (int depth = 0; depth < stack_depth; ++depth) { + const Layer *top_layer = &layer; + for (int i = 0; i < depth && top_layer != nullptr; ++i) + top_layer = top_layer->upper_layer; + if (top_layer == nullptr) + break; + + ExPolygons area = top_surface_image_visible_top_mask(*top_layer, zone_id); + if (area.empty()) + continue; + if (!current_layer_perimeters.empty()) + area = diff_ex(area, current_layer_perimeters, ApplySafetyOffset::Yes); + if (area.empty()) + continue; + + if (plan.same_layer_partition) { + const ExPolygons depth_area = area; + for (size_t component_idx = 0; component_idx < plan.components_bottom_to_top.size(); ++component_idx) { + TopSurfaceImageStackSlice slice; + slice.component_id = plan.components_bottom_to_top[component_idx]; + slice.depth = depth; + slice.component_index = component_idx; + slice.component_count = plan.components_bottom_to_top.size(); + slice.same_layer_partition = true; + slice.angle_rad = (depth & 1) ? float(-PI / 4.0) : float(PI / 4.0); + slice.area = depth_area; + plan.slices.emplace_back(std::move(slice)); + } + } else { + TopSurfaceImageStackSlice slice; + slice.component_id = plan.components_bottom_to_top[size_t(stack_depth - 1 - depth)]; + slice.depth = depth; + slice.component_index = size_t(stack_depth - 1 - depth); + slice.component_count = plan.components_bottom_to_top.size(); + slice.area = std::move(area); + plan.slices.emplace_back(std::move(slice)); + } + } + + if (!plan.slices.empty()) + plans[region_id] = std::move(plan); + } + + return plans; +} + +static SurfaceFill& surface_fill_for_params(std::vector &surface_fills, const SurfaceFillParams ¶ms) +{ + for (SurfaceFill &fill : surface_fills) + if (fill.params == params) + return fill; + surface_fills.emplace_back(params); + return surface_fills.back(); +} + +static void append_surface_fill_expolygons(SurfaceFill &fill, + size_t region_id, + const Surface &surface, + ExPolygons &&expolygons, + const LayerRegion &layerm) +{ + if (expolygons.empty()) + return; + if (fill.region_id == size_t(-1)) { + fill.region_id = region_id; + fill.surface = surface; + fill.surface.expolygon = ExPolygon(); + fill.expolygons = std::move(expolygons); + fill.region_id_group.push_back(region_id); + fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons; + } else { + append(fill.expolygons, std::move(expolygons)); + auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id); + if (t == fill.region_id_group.end()) { + fill.region_id_group.push_back(region_id); + fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons); + } + } +} + +static SurfaceFillParams top_surface_image_params_for_slice(const Layer &layer, + const Surface &surface, + const SurfaceFillParams &base_params, + const TopSurfaceImageRegionPlan &plan, + const TopSurfaceImageStackSlice &slice) +{ + SurfaceFillParams params = base_params; + params.extruder = slice.component_id; + params.pattern = ipRectilinear; + params.density = 100.f; + params.angle = slice.angle_rad; + params.fixed_angle = true; + params.bridge = false; + params.bridge_angle = 0.f; + params.multiline = 1; + params.anchor_length = 1000.f; + params.anchor_length_max = 1000.f; + params.extrusion_role = surface.is_top() ? erTopSolidInfill : erSolidInfill; + const PrintConfig &print_config = layer.object()->print()->config(); + const float nozzle = slice.component_id > 0 && size_t(slice.component_id - 1) < print_config.nozzle_diameter.values.size() ? + float(print_config.nozzle_diameter.get_at(size_t(slice.component_id - 1))) : + float(print_config.nozzle_diameter.values.empty() ? 0.4 : print_config.nozzle_diameter.values.front()); + const float height = float((surface.thickness == -1) ? layer.height : surface.thickness); + params.flow = Flow(plan.max_width_mm, height, nozzle); + params.spacing = slice.same_layer_partition && slice.component_count > 0 ? + (plan.min_width_mm * float(slice.component_count) + (plan.max_width_mm - plan.min_width_mm)) : + params.flow.spacing(); + params.texture_mapping_top_surface_image = true; + params.texture_mapping_top_surface_zone_id = plan.zone_id; + params.texture_mapping_top_surface_component_id = slice.component_id; + params.texture_mapping_top_surface_stack_depth = slice.depth; + params.texture_mapping_top_surface_fixed_coloring = plan.fixed_coloring; + params.texture_mapping_top_surface_min_width_mm = plan.min_width_mm; + params.texture_mapping_top_surface_max_width_mm = plan.max_width_mm; + params.texture_mapping_top_surface_same_layer_partition = slice.same_layer_partition; + params.texture_mapping_top_surface_component_index = int(slice.component_index); + params.texture_mapping_top_surface_component_count = int(slice.component_count); + return params; +} + +static ExtrusionPaths top_surface_image_split_path(const ExtrusionPath &path, + const TextureMappingOffsetContext &context, + float min_width_mm, + float max_width_mm, + float nozzle_diameter) +{ + ExtrusionPaths out; + if (path.polyline.points.size() < 2) + return out; + const float height = path.height > 0.f ? path.height : context.layer_height_mm; + const float sample_step_mm = std::clamp(max_width_mm * 0.5f, 0.16f, 0.40f); + for (size_t point_idx = 1; point_idx < path.polyline.points.size(); ++point_idx) { + const Point p0 = path.polyline.points[point_idx - 1]; + const Point p1 = path.polyline.points[point_idx]; + const double len_mm = unscale(p0.distance_to(p1)); + if (!std::isfinite(len_mm) || len_mm <= EPSILON) + continue; + const int steps = std::max(1, int(std::ceil(len_mm / sample_step_mm))); + for (int step = 0; step < steps; ++step) { + const double t0 = double(step) / double(steps); + const double t1 = double(step + 1) / double(steps); + const Point q0 = lerp(p0, p1, t0); + const Point q1 = lerp(p0, p1, t1); + if (q0 == q1) + continue; + const Point qm = lerp(p0, p1, 0.5 * (t0 + t1)); + const std::vector weights = + sample_weight_field_components(context.weight_field, + unscale(qm.x()), + unscale(qm.y()), + context.high_resolution_texture_sampling); + const float coverage = context.active_component_idx < weights.size() ? + std::clamp(weights[context.active_component_idx], 0.f, 1.f) : + 0.f; + const float width = std::clamp(min_width_mm + coverage * (max_width_mm - min_width_mm), + min_width_mm, + max_width_mm); + Polyline polyline; + polyline.points.emplace_back(q0); + polyline.points.emplace_back(q1); + ExtrusionPath segment(std::move(polyline), path); + segment.width = width; + segment.height = height; + segment.mm3_per_mm = Flow(width, height, nozzle_diameter).mm3_per_mm(); + out.emplace_back(std::move(segment)); + } + } + return out; +} + +static ExtrusionEntity* top_surface_image_modulated_entity(const ExtrusionEntity &entity, + const TextureMappingOffsetContext &context, + float min_width_mm, + float max_width_mm, + float nozzle_diameter) +{ + if (const ExtrusionPath *path = dynamic_cast(&entity)) { + ExtrusionPaths paths = top_surface_image_split_path(*path, context, min_width_mm, max_width_mm, nozzle_diameter); + if (paths.empty()) + return entity.clone(); + return new ExtrusionMultiPath(std::move(paths)); + } + if (const ExtrusionMultiPath *multi_path = dynamic_cast(&entity)) { + ExtrusionPaths paths; + for (const ExtrusionPath &path : multi_path->paths) { + ExtrusionPaths split = top_surface_image_split_path(path, context, min_width_mm, max_width_mm, nozzle_diameter); + append(paths, std::move(split)); + } + if (paths.empty()) + return entity.clone(); + ExtrusionMultiPath *out = new ExtrusionMultiPath(std::move(paths)); + if (!multi_path->can_reverse()) + out->set_reverse(); + return out; + } + if (const ExtrusionLoop *loop = dynamic_cast(&entity)) { + ExtrusionPaths paths; + for (const ExtrusionPath &path : loop->paths) { + ExtrusionPaths split = top_surface_image_split_path(path, context, min_width_mm, max_width_mm, nozzle_diameter); + append(paths, std::move(split)); + } + if (paths.empty()) + return entity.clone(); + return new ExtrusionLoop(std::move(paths)); + } + if (const ExtrusionEntityCollection *collection = dynamic_cast(&entity)) { + ExtrusionEntityCollection *out = new ExtrusionEntityCollection(*collection); + for (ExtrusionEntity *&child : out->entities) { + ExtrusionEntity *replacement = + top_surface_image_modulated_entity(*child, context, min_width_mm, max_width_mm, nozzle_diameter); + delete child; + child = replacement; + } + return out; + } + return entity.clone(); +} + +static std::vector top_surface_image_same_layer_fractions(const std::optional &context, + int component_count, + float x_mm, + float y_mm) +{ + component_count = std::max(1, component_count); + std::vector fractions(size_t(component_count), 1.f / float(component_count)); + if (!context) + return fractions; + std::vector weights = + sample_weight_field_components(context->weight_field, + x_mm, + y_mm, + context->high_resolution_texture_sampling); + if (weights.size() != size_t(component_count)) + return fractions; + fractions.assign(size_t(component_count), 0.f); + float sum = 0.f; + for (size_t i = 0; i < weights.size(); ++i) { + fractions[i] = std::max(0.f, weights[i]); + sum += fractions[i]; + } + if (sum <= EPSILON || !std::isfinite(sum)) { + std::fill(fractions.begin(), fractions.end(), 1.f / float(component_count)); + return fractions; + } + for (float &fraction : fractions) + fraction /= sum; + return fractions; +} + +static void top_surface_image_same_layer_partition_fill(ExtrusionEntityCollection &collection, + const Surface &surface, + const SurfaceFillParams ¶ms, + const std::optional &context) +{ + int component_count = std::max(1, params.texture_mapping_top_surface_component_count); + int component_index = std::clamp(params.texture_mapping_top_surface_component_index, 0, component_count - 1); + if (context && int(context->component_ids.size()) == component_count) { + auto component_it = std::find(context->component_ids.begin(), + context->component_ids.end(), + params.texture_mapping_top_surface_component_id); + if (component_it != context->component_ids.end()) + component_index = int(std::distance(context->component_ids.begin(), component_it)); + component_index = std::clamp(component_index, 0, component_count - 1); + } + const float min_width = std::clamp(params.texture_mapping_top_surface_min_width_mm, + TextureMappingZone::MinTopSurfaceImageLineWidthMm, + TextureMappingZone::MaxTopSurfaceImageLineWidthMm); + const float max_width = std::clamp(params.texture_mapping_top_surface_max_width_mm, + min_width, + TextureMappingZone::MaxTopSurfaceImageLineWidthMm); + const float pitch = min_width * float(component_count) + (max_width - min_width); + if (pitch <= EPSILON) + return; + const BoundingBox bbox = get_extents(surface.expolygon); + if (!bbox.defined) + return; + const double theta = params.angle; + const double cos_t = std::cos(theta); + const double sin_t = std::sin(theta); + double min_u = std::numeric_limits::max(); + double min_v = std::numeric_limits::max(); + double max_u = -std::numeric_limits::max(); + double max_v = -std::numeric_limits::max(); + for (size_t i = 0; i < 4; ++i) { + const Point corner = bbox[i]; + const double x = unscale(corner.x()); + const double y = unscale(corner.y()); + const double u = x * cos_t + y * sin_t; + const double v = -x * sin_t + y * cos_t; + min_u = std::min(min_u, u); + max_u = std::max(max_u, u); + min_v = std::min(min_v, v); + max_v = std::max(max_v, v); + } + const double sample_step = std::clamp(double(max_width), 0.32, 0.80); + const double u_start = std::floor((min_u - pitch) / sample_step) * sample_step; + const double u_end = std::ceil((max_u + pitch) / sample_step) * sample_step; + const int band_start = int(std::floor((min_v - pitch) / double(pitch))); + const int band_end = int(std::ceil((max_v + pitch) / double(pitch))); + const ExPolygons clip { surface.expolygon }; + for (int band = band_start; band <= band_end; ++band) { + const double band_v = double(band) * double(pitch); + const double sample_v = band_v + double(pitch) * 0.5; + for (double u0 = u_start; u0 < u_end - EPSILON; u0 += sample_step) { + const double u1 = std::min(u0 + sample_step, u_end); + const double um = 0.5 * (u0 + u1); + const double sample_x = um * cos_t - sample_v * sin_t; + const double sample_y = um * sin_t + sample_v * cos_t; + const std::vector fractions = + top_surface_image_same_layer_fractions(context, + component_count, + float(sample_x), + float(sample_y)); + const float available_width = std::max(0.f, pitch - min_width * float(component_count)); + double lane_offset = 0.0; + float lane_width = min_width; + for (int i = 0; i < component_count; ++i) { + const float width = min_width + available_width * fractions[size_t(i)]; + if (i == component_index) { + lane_width = width; + break; + } + lane_offset += width; + } + lane_width = std::clamp(lane_width, min_width, max_width); + const double v_center = band_v + lane_offset + 0.5 * double(lane_width); + const double x0 = u0 * cos_t - v_center * sin_t; + const double y0 = u0 * sin_t + v_center * cos_t; + const double x1 = u1 * cos_t - v_center * sin_t; + const double y1 = u1 * sin_t + v_center * cos_t; + Polyline polyline; + polyline.points.emplace_back(Point::new_scale(x0, y0)); + polyline.points.emplace_back(Point::new_scale(x1, y1)); + if (polyline.points.front() == polyline.points.back()) + continue; + ExtrusionPath path(params.extrusion_role, + Flow(lane_width, params.flow.height(), params.flow.nozzle_diameter()).mm3_per_mm(), + lane_width, + params.flow.height()); + path.polyline = std::move(polyline); + path.intersect_expolygons(clip, &collection); + } + } +} + +static void apply_top_surface_image_collection_metadata(ExtrusionEntityCollection &collection, + const SurfaceFillParams ¶ms, + const std::optional &context) +{ + collection.texture_mapping_top_surface_image = true; + collection.texture_mapping_top_surface_zone_id = params.texture_mapping_top_surface_zone_id; + collection.texture_mapping_top_surface_desired_component_id = params.texture_mapping_top_surface_component_id; + collection.texture_mapping_top_surface_stack_depth = params.texture_mapping_top_surface_stack_depth; + collection.texture_mapping_top_surface_fixed_coloring = params.texture_mapping_top_surface_fixed_coloring; + if (params.texture_mapping_top_surface_fixed_coloring && + params.texture_mapping_top_surface_component_id > 0) + collection.texture_mapping_extruder_override = int(params.texture_mapping_top_surface_component_id - 1); + if (!context) + return; + ExtrusionEntitiesPtr replacement; + replacement.reserve(collection.entities.size()); + for (const ExtrusionEntity *entity : collection.entities) + replacement.emplace_back(top_surface_image_modulated_entity(*entity, + *context, + params.texture_mapping_top_surface_min_width_mm, + params.texture_mapping_top_surface_max_width_mm, + params.flow.nozzle_diameter())); + collection.clear(); + collection.entities = std::move(replacement); +} + // Detect narrow infill regions // Based on the anti-vibration algorithm from PrusaSlicer: @@ -836,6 +1412,7 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p SurfaceFillParams params; bool has_internal_voids = false; const PrintObjectConfig& object_config = layer.object()->config(); + const std::vector top_surface_plans = top_surface_image_region_plans(layer); auto append_flow_param = [](std::map &flow_params, Flow flow, const ExPolygon &exp) { auto it = flow_params.find(flow); @@ -1012,23 +1589,50 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p if (surface.surface_type != stInternalVoid) { const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()]; if (params != nullptr) { - SurfaceFill &fill = surface_fills[params->idx]; - if (fill.region_id == size_t(-1)) { - fill.region_id = region_id; - fill.surface = surface; - fill.expolygons.emplace_back(std::move(fill.surface.expolygon)); - //BBS - fill.region_id_group.push_back(region_id); - fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons; - } else { - fill.expolygons.emplace_back(surface.expolygon); - //BBS - auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id); - if (t == fill.region_id_group.end()) { - fill.region_id_group.push_back(region_id); - fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons); - } - } + ExPolygons remaining = { surface.expolygon }; + if (region_id < top_surface_plans.size() && + top_surface_plans[region_id].zone != nullptr && + (surface.is_top() || surface.surface_type == stInternalSolid) && + !surface.is_bridge()) { + const TopSurfaceImageRegionPlan &plan = top_surface_plans[region_id]; + for (size_t slice_idx = 0; slice_idx < plan.slices.size();) { + const TopSurfaceImageStackSlice &slice = plan.slices[slice_idx]; + if (remaining.empty()) + break; + ExPolygons image_expolygons = intersection_ex(remaining, slice.area, ApplySafetyOffset::Yes); + if (image_expolygons.empty()) { + ++slice_idx; + continue; + } + ExPolygons image_clip = image_expolygons; + if (slice.same_layer_partition) { + size_t same_depth_end = slice_idx; + while (same_depth_end < plan.slices.size() && + plan.slices[same_depth_end].same_layer_partition && + plan.slices[same_depth_end].depth == slice.depth) + ++same_depth_end; + for (size_t same_idx = slice_idx; same_idx < same_depth_end; ++same_idx) { + SurfaceFillParams image_params = + top_surface_image_params_for_slice(layer, surface, *params, plan, plan.slices[same_idx]); + ExPolygons component_expolygons = image_expolygons; + SurfaceFill &image_fill = surface_fill_for_params(surface_fills, image_params); + append_surface_fill_expolygons(image_fill, region_id, surface, std::move(component_expolygons), layerm); + } + slice_idx = same_depth_end; + } else { + SurfaceFillParams image_params = + top_surface_image_params_for_slice(layer, surface, *params, plan, slice); + SurfaceFill &image_fill = surface_fill_for_params(surface_fills, image_params); + append_surface_fill_expolygons(image_fill, region_id, surface, std::move(image_expolygons), layerm); + ++slice_idx; + } + remaining = diff_ex(remaining, image_clip, ApplySafetyOffset::Yes); + } + } + if (!remaining.empty()) { + SurfaceFill &fill = surface_fills[params->idx]; + append_surface_fill_expolygons(fill, region_id, surface, std::move(remaining), layerm); + } } } } @@ -1037,6 +1641,8 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p Polygons all_polygons; for (SurfaceFill &fill : surface_fills) if (! fill.expolygons.empty()) { + if (fill.params.texture_mapping_top_surface_same_layer_partition) + continue; if (fill.expolygons.size() > 1 || ! all_polygons.empty()) { Polygons polys = to_polygons(std::move(fill.expolygons)); // Make a union of polygons, use a safety offset, subtract the preceding polygons. @@ -1098,7 +1704,9 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p region_id = region_some_infill; const LayerRegion& layerm = *layer.regions()[region_id]; for (SurfaceFill &surface_fill : surface_fills) - if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) { + if (surface_fill.surface.surface_type == stInternalSolid && + !surface_fill.params.texture_mapping_top_surface_image && + std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) { internal_solid_fill = &surface_fill; break; } @@ -1135,7 +1743,8 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p if (layer.object()->config().detect_narrow_internal_solid_infill) { size_t surface_fills_size = surface_fills.size(); for (size_t i = 0; i < surface_fills_size; i++) { - if (surface_fills[i].surface.surface_type != stInternalSolid) + if (surface_fills[i].surface.surface_type != stInternalSolid || + surface_fills[i].params.texture_mapping_top_surface_image) continue; ExPolygons normal_infill; @@ -1300,6 +1909,37 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } if (surface_fill.params.pattern == ipGrid) params.can_reverse = false; + + std::optional top_surface_image_context; + if (surface_fill.params.texture_mapping_top_surface_image) { + const Print *print = this->object()->print(); + const TextureMappingZone *zone = print != nullptr ? + print->texture_mapping_manager().zone_from_id(surface_fill.params.texture_mapping_top_surface_zone_id) : + nullptr; + if (print != nullptr && zone != nullptr) { + const PrintConfig &print_config = print->config(); + std::vector filament_colours = print_config.filament_colour.values; + filament_colours.resize(print_config.filament_colour.values.size(), "#FFFFFF"); + std::vector components = + TextureMappingManager::effective_texture_component_ids(*zone, + print_config.filament_colour.values.size(), + filament_colours); + const std::optional> background = + top_surface_image_equal_blend_background(print_config, components); + top_surface_image_context = + build_texture_mapping_offset_context_for_layer(*this->object(), + *this, + *zone, + surface_fill.params.texture_mapping_top_surface_zone_id, + surface_fill.params.texture_mapping_top_surface_component_id, + surface_fill.params.texture_mapping_top_surface_max_width_mm, + float(this->height), + std::nullopt, + surface_fill.params.texture_mapping_top_surface_min_width_mm, + background); + } + } + for (ExPolygon& expoly : surface_fill.expolygons) { f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); @@ -1328,9 +1968,30 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.density = elefant_density * (elefant_layers - (f->layer_id - 1)) / elefant_layers; } // make fill - f->fill_surface_extrusion(&surface_fill.surface, - params, - m_regions[surface_fill.region_id]->fills.entities); + ExtrusionEntitiesPtr &fill_entities = m_regions[surface_fill.region_id]->fills.entities; + if (surface_fill.params.texture_mapping_top_surface_same_layer_partition) { + ExtrusionEntityCollection *collection = new ExtrusionEntityCollection(); + top_surface_image_same_layer_partition_fill(*collection, + surface_fill.surface, + surface_fill.params, + top_surface_image_context); + if (!collection->empty()) { + apply_top_surface_image_collection_metadata(*collection, surface_fill.params, std::nullopt); + fill_entities.push_back(collection); + } else { + delete collection; + } + } else { + const size_t fill_entities_before = fill_entities.size(); + f->fill_surface_extrusion(&surface_fill.surface, + params, + fill_entities); + if (surface_fill.params.texture_mapping_top_surface_image) { + for (size_t i = fill_entities_before; i < fill_entities.size(); ++i) + if (ExtrusionEntityCollection *collection = dynamic_cast(fill_entities[i])) + apply_top_surface_image_collection_metadata(*collection, surface_fill.params, top_surface_image_context); + } + } } } @@ -1341,8 +2002,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: for (LayerRegion *layerm : m_regions) for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) { ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); - if (const auto *thin_fill_collection = dynamic_cast(thin_fill)) + if (const auto *thin_fill_collection = dynamic_cast(thin_fill)) { collection.texture_mapping_extruder_override = thin_fill_collection->texture_mapping_extruder_override; + collection.texture_mapping_top_surface_image = thin_fill_collection->texture_mapping_top_surface_image; + collection.texture_mapping_top_surface_zone_id = thin_fill_collection->texture_mapping_top_surface_zone_id; + collection.texture_mapping_top_surface_desired_component_id = thin_fill_collection->texture_mapping_top_surface_desired_component_id; + collection.texture_mapping_top_surface_stack_depth = thin_fill_collection->texture_mapping_top_surface_stack_depth; + collection.texture_mapping_top_surface_fixed_coloring = thin_fill_collection->texture_mapping_top_surface_fixed_coloring; + } layerm->fills.entities.push_back(&collection); collection.entities.push_back(thin_fill->clone()); } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 33ef8e77b17..4755eb7c4fe 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -113,6 +113,14 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c (this->num_physical_filaments == 0 || unsigned(extrusions.texture_mapping_extruder_override) < this->num_physical_filaments)) return unsigned(extrusions.texture_mapping_extruder_override); + if (extrusions.texture_mapping_top_surface_image && + !extrusions.texture_mapping_top_surface_fixed_coloring && + extrusions.texture_mapping_top_surface_desired_component_id > 0) { + const unsigned int desired = extrusions.texture_mapping_top_surface_desired_component_id - 1; + if (this->has_extruder(desired)) + return desired; + } + assert(region.config().wall_filament.value > 0); assert(region.config().sparse_infill_filament.value > 0); assert(region.config().solid_infill_filament.value > 0); @@ -214,6 +222,19 @@ static FilamentChangeStats calc_filament_change_info_by_toolorder(const PrintCon static void apply_first_layer_order(const DynamicPrintConfig* config, std::vector& tool_order); +static void apply_top_surface_no_fixed_carry(LayerTools <, unsigned int last_extruder_id) +{ + if (last_extruder_id == 0 || lt.extruders.empty()) + return; + if (std::find(lt.extruders.begin(), lt.extruders.end(), last_extruder_id) != lt.extruders.end()) + return; + for (unsigned int desired : lt.top_surface_image_no_fixed_desired_extruders) + if (desired == last_extruder_id) { + lt.extruders.insert(lt.extruders.begin(), desired); + return; + } +} + void ToolOrdering::handle_dontcare_extruder(const std::vector& tool_order_layer0) { if(m_layer_tools.empty() || tool_order_layer0.empty()) @@ -267,6 +288,7 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector& too break; } } + apply_top_surface_no_fixed_carry(lt, last_extruder_id); last_extruder_id = lt.extruders.back(); } @@ -311,7 +333,8 @@ void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id) ++ last_extruder_id; } - for (LayerTools < : m_layer_tools) { + for (size_t layer_idx = 0; layer_idx < m_layer_tools.size(); ++layer_idx) { + LayerTools < = m_layer_tools[layer_idx]; if (lt.extruders.empty()) continue; if (lt.extruders.size() == 1 && lt.extruders.front() == 0) @@ -345,6 +368,8 @@ void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id) } } + if (layer_idx > 0) + apply_top_surface_no_fixed_carry(lt, last_extruder_id); last_extruder_id = lt.extruders.back(); } @@ -847,6 +872,13 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto has_texture_override_fill = true; continue; } + if (fill->texture_mapping_top_surface_image && + !fill->texture_mapping_top_surface_fixed_coloring && + fill->texture_mapping_top_surface_desired_component_id >= 1 && + (layer_tools.num_physical_filaments == 0 || + fill->texture_mapping_top_surface_desired_component_id <= layer_tools.num_physical_filaments)) + layer_tools.top_surface_image_no_fixed_desired_extruders.emplace_back( + fill->texture_mapping_top_surface_desired_component_id); ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); if (is_solid_infill(role)) has_solid_infill = true; @@ -933,6 +965,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto sort_remove_duplicates(layer.extruders); sort_remove_duplicates(layer.texture_mapping_extruders); sort_remove_duplicates(layer.texture_mapping_component_extruders); + sort_remove_duplicates(layer.top_surface_image_no_fixed_desired_extruders); // 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) diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index f41d2c2ec3b..c619a9a5290 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -158,6 +158,7 @@ public: std::vector extruders; std::vector texture_mapping_extruders; std::vector texture_mapping_component_extruders; + std::vector top_surface_image_no_fixed_desired_extruders; // 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. unsigned int extruder_override = 0; diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 5785fc2beba..cc40f214c50 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -666,6 +666,20 @@ static int modulation_mode_from_name(const std::string &name) return int(TextureMappingZone::ModulationLineWidth); } +static std::string top_surface_image_printing_method_name(int method) +{ + if (method == int(TextureMappingZone::TopSurfaceImageSameLayer45Partition)) + return std::string("same_layer_45_partition"); + return std::string("same_angle_45_width"); +} + +static int top_surface_image_printing_method_from_name(const std::string &name) +{ + if (name == "same_layer_45_partition") + return int(TextureMappingZone::TopSurfaceImageSameLayer45Partition); + return int(TextureMappingZone::TopSurfaceImageSameAngle45Width); +} + static std::string top_visible_recolor_aggressiveness_name(int mode) { switch (clamp_int(mode, @@ -1024,6 +1038,12 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const top_visible_perimeter_recolor_aggressiveness == rhs.top_visible_perimeter_recolor_aggressiveness && top_visible_perimeter_recolor_above_layers == rhs.top_visible_perimeter_recolor_above_layers && top_visible_perimeter_recolor_point_sampling == rhs.top_visible_perimeter_recolor_point_sampling && + top_surface_image_printing_enabled == rhs.top_surface_image_printing_enabled && + top_surface_image_printing_method == rhs.top_surface_image_printing_method && + std::abs(top_surface_image_min_line_width_mm - rhs.top_surface_image_min_line_width_mm) <= eps && + std::abs(top_surface_image_max_line_width_mm - rhs.top_surface_image_max_line_width_mm) <= eps && + top_surface_image_colored_top_layers == rhs.top_surface_image_colored_top_layers && + top_surface_image_fixed_coloring_filaments == rhs.top_surface_image_fixed_coloring_filaments && compact_offset_mode == rhs.compact_offset_mode && use_legacy_fixed_color_mode == rhs.use_legacy_fixed_color_mode && high_speed_image_texture_sampling == rhs.high_speed_image_texture_sampling && @@ -1362,6 +1382,25 @@ std::string TextureMappingManager::serialize_entries() TextureMappingZone::MinTopVisiblePerimeterRecolorAboveLayers, TextureMappingZone::MaxTopVisiblePerimeterRecolorAboveLayers); texture["top_visible_perimeter_recolor_point_sampling"] = zone.top_visible_perimeter_recolor_point_sampling; + texture["top_surface_image_printing_enabled"] = zone.top_surface_image_printing_enabled; + texture["top_surface_image_printing_method"] = + top_surface_image_printing_method_name(zone.top_surface_image_printing_method); + const float top_surface_max_width = + std::clamp(finite_or(zone.top_surface_image_max_line_width_mm, + TextureMappingZone::DefaultTopSurfaceImageMaxLineWidthMm), + TextureMappingZone::MinTopSurfaceImageLineWidthMm, + TextureMappingZone::MaxTopSurfaceImageLineWidthMm); + texture["top_surface_image_min_line_width_mm"] = + std::clamp(finite_or(zone.top_surface_image_min_line_width_mm, + TextureMappingZone::DefaultTopSurfaceImageMinLineWidthMm), + TextureMappingZone::MinTopSurfaceImageLineWidthMm, + top_surface_max_width); + texture["top_surface_image_max_line_width_mm"] = top_surface_max_width; + texture["top_surface_image_colored_top_layers"] = + clamp_int(zone.top_surface_image_colored_top_layers, + TextureMappingZone::MinTopSurfaceImageColoredTopLayers, + TextureMappingZone::MaxTopSurfaceImageColoredTopLayers); + texture["top_surface_image_fixed_coloring_filaments"] = zone.top_surface_image_fixed_coloring_filaments; texture["compact_offset_mode"] = zone.compact_offset_mode; texture["use_legacy_fixed_color_mode"] = zone.use_legacy_fixed_color_mode; texture["high_speed_image_texture_sampling"] = true; @@ -1560,6 +1599,33 @@ void TextureMappingManager::load_entries(const std::string &serialized, zone.top_visible_perimeter_recolor_point_sampling = texture.value("top_visible_perimeter_recolor_point_sampling", TextureMappingZone::DefaultTopVisiblePerimeterRecolorPointSampling); + zone.top_surface_image_printing_enabled = + texture.value("top_surface_image_printing_enabled", + TextureMappingZone::DefaultTopSurfaceImagePrintingEnabled); + zone.top_surface_image_printing_method = + top_surface_image_printing_method_from_name( + texture.value("top_surface_image_printing_method", + top_surface_image_printing_method_name(TextureMappingZone::DefaultTopSurfaceImagePrintingMethod))); + zone.top_surface_image_max_line_width_mm = + std::clamp(finite_or(texture.value("top_surface_image_max_line_width_mm", + TextureMappingZone::DefaultTopSurfaceImageMaxLineWidthMm), + TextureMappingZone::DefaultTopSurfaceImageMaxLineWidthMm), + TextureMappingZone::MinTopSurfaceImageLineWidthMm, + TextureMappingZone::MaxTopSurfaceImageLineWidthMm); + zone.top_surface_image_min_line_width_mm = + std::clamp(finite_or(texture.value("top_surface_image_min_line_width_mm", + TextureMappingZone::DefaultTopSurfaceImageMinLineWidthMm), + TextureMappingZone::DefaultTopSurfaceImageMinLineWidthMm), + TextureMappingZone::MinTopSurfaceImageLineWidthMm, + zone.top_surface_image_max_line_width_mm); + zone.top_surface_image_colored_top_layers = + clamp_int(texture.value("top_surface_image_colored_top_layers", + TextureMappingZone::DefaultTopSurfaceImageColoredTopLayers), + TextureMappingZone::MinTopSurfaceImageColoredTopLayers, + TextureMappingZone::MaxTopSurfaceImageColoredTopLayers); + zone.top_surface_image_fixed_coloring_filaments = + texture.value("top_surface_image_fixed_coloring_filaments", + TextureMappingZone::DefaultTopSurfaceImageFixedColoringFilaments); zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode); zone.use_legacy_fixed_color_mode = texture.value("use_legacy_fixed_color_mode", TextureMappingZone::DefaultUseLegacyFixedColorMode); diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index 832fbc51964..027653a2d9c 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -66,6 +66,11 @@ struct TextureMappingZone TopVisibleRecolorAggressive = 2 }; + enum TopSurfaceImagePrintingMethod : uint8_t { + TopSurfaceImageSameAngle45Width = 0, + TopSurfaceImageSameLayer45Partition = 1 + }; + enum FilamentColorMode : uint8_t { FilamentColorAny = 0, FilamentColorRGB = 1, @@ -140,6 +145,16 @@ struct TextureMappingZone static constexpr int MaxTopVisiblePerimeterRecolorAboveLayers = 5; static constexpr int DefaultTopVisiblePerimeterRecolorAboveLayers = 2; static constexpr bool DefaultTopVisiblePerimeterRecolorPointSampling = true; + static constexpr bool DefaultTopSurfaceImagePrintingEnabled = false; + static constexpr int DefaultTopSurfaceImagePrintingMethod = int(TopSurfaceImageSameAngle45Width); + static constexpr float MinTopSurfaceImageLineWidthMm = 0.32f; + static constexpr float MaxTopSurfaceImageLineWidthMm = 0.80f; + static constexpr float DefaultTopSurfaceImageMinLineWidthMm = 0.32f; + static constexpr float DefaultTopSurfaceImageMaxLineWidthMm = 0.80f; + static constexpr int MinTopSurfaceImageColoredTopLayers = 1; + static constexpr int MaxTopSurfaceImageColoredTopLayers = 20; + static constexpr int DefaultTopSurfaceImageColoredTopLayers = 3; + static constexpr bool DefaultTopSurfaceImageFixedColoringFilaments = true; static constexpr bool DefaultCompactOffsetMode = true; static constexpr bool DefaultUseLegacyFixedColorMode = false; static constexpr bool DefaultHighSpeedImageTextureSampling = true; @@ -228,6 +243,12 @@ struct TextureMappingZone int top_visible_perimeter_recolor_aggressiveness = DefaultTopVisiblePerimeterRecolorAggressiveness; int top_visible_perimeter_recolor_above_layers = DefaultTopVisiblePerimeterRecolorAboveLayers; bool top_visible_perimeter_recolor_point_sampling = DefaultTopVisiblePerimeterRecolorPointSampling; + bool top_surface_image_printing_enabled = DefaultTopSurfaceImagePrintingEnabled; + int top_surface_image_printing_method = DefaultTopSurfaceImagePrintingMethod; + float top_surface_image_min_line_width_mm = DefaultTopSurfaceImageMinLineWidthMm; + float top_surface_image_max_line_width_mm = DefaultTopSurfaceImageMaxLineWidthMm; + int top_surface_image_colored_top_layers = DefaultTopSurfaceImageColoredTopLayers; + bool top_surface_image_fixed_coloring_filaments = DefaultTopSurfaceImageFixedColoringFilaments; bool compact_offset_mode = DefaultCompactOffsetMode; bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode; bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling; @@ -272,6 +293,13 @@ struct TextureMappingZone bool is_linear_gradient() const { return surface_pattern == int(LinearGradient); } bool is_radial_linear_gradient() const { return is_linear_gradient() && linear_gradient_mode == int(LinearGradientRadial); } bool is_surface_gradient() const { return is_2d_gradient() || is_linear_gradient(); } + bool top_surface_image_printing_active() const + { + return top_surface_image_printing_enabled && + is_image_texture() && + (top_surface_image_printing_method == int(TopSurfaceImageSameAngle45Width) || + top_surface_image_printing_method == int(TopSurfaceImageSameLayer45Partition)); + } void apply_default_modulation_mode() { @@ -309,6 +337,12 @@ struct TextureMappingZone top_visible_perimeter_recolor_aggressiveness = DefaultTopVisiblePerimeterRecolorAggressiveness; top_visible_perimeter_recolor_above_layers = DefaultTopVisiblePerimeterRecolorAboveLayers; top_visible_perimeter_recolor_point_sampling = DefaultTopVisiblePerimeterRecolorPointSampling; + top_surface_image_printing_enabled = DefaultTopSurfaceImagePrintingEnabled; + top_surface_image_printing_method = DefaultTopSurfaceImagePrintingMethod; + top_surface_image_min_line_width_mm = DefaultTopSurfaceImageMinLineWidthMm; + top_surface_image_max_line_width_mm = DefaultTopSurfaceImageMaxLineWidthMm; + top_surface_image_colored_top_layers = DefaultTopSurfaceImageColoredTopLayers; + top_surface_image_fixed_coloring_filaments = DefaultTopSurfaceImageFixedColoringFilaments; compact_offset_mode = DefaultCompactOffsetMode; use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode; high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling; diff --git a/src/libslic3r/TextureMappingOffset.cpp b/src/libslic3r/TextureMappingOffset.cpp index 904be4aa3c1..aecfe858a53 100644 --- a/src/libslic3r/TextureMappingOffset.cpp +++ b/src/libslic3r/TextureMappingOffset.cpp @@ -1510,7 +1510,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( float layer_z_mm, float layer_z_falloff_mm, bool high_resolution_texture_sampling, - bool high_speed_image_texture_sampling) + bool high_speed_image_texture_sampling, + std::optional> image_background_rgba_override) { TextureMappingOffsetWeightField weight_field; if (component_colors.empty()) @@ -1616,7 +1617,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( const indexed_triangle_set &its = mesh_ptr->its; const Transform3d volume_trafo = object_trafo * volume->get_matrix(); - const std::array background_color = texture_mapping_background_color(*volume); + const std::array background_color = + image_background_rgba_override ? *image_background_rgba_override : texture_mapping_background_color(*volume); if (!volume->texture_mapping_color_facets.empty()) { std::vector color_facets; @@ -2813,7 +2815,9 @@ std::optional build_texture_mapping_offset_context_ unsigned int active_component_id_override, std::optional base_outer_width_mm_override, std::optional layer_height_mm_override, - std::optional plate_origin_mm_override) + std::optional plate_origin_mm_override, + std::optional min_outer_width_mm_override, + std::optional> image_background_rgba_override) { const Print *print = print_object.print(); if (print == nullptr) @@ -2946,10 +2950,11 @@ std::optional build_texture_mapping_offset_context_ const float base_outer_width_mm = base_outer_width_mm_override ? std::max(0.05f, *base_outer_width_mm_override) : std::max(0.05f, float(print_config.texture_mapping_outer_wall_gradient_max_line_width.value)); - const float config_min_gradient_width_mm = std::clamp( - float(print_config.texture_mapping_outer_wall_gradient_min_line_width.value), - 0.05f, - base_outer_width_mm); + const float config_min_gradient_width_mm = min_outer_width_mm_override ? + std::clamp(*min_outer_width_mm_override, 0.05f, base_outer_width_mm) : + std::clamp(float(print_config.texture_mapping_outer_wall_gradient_min_line_width.value), + 0.05f, + base_outer_width_mm); const float layer_height_mm = layer_height_mm_override ? std::max(0.01f, *layer_height_mm_override) : std::max(0.01f, float(layer.height)); @@ -3019,7 +3024,8 @@ std::optional build_texture_mapping_offset_context_ float(layer.print_z), layer_sample_falloff_mm, high_resolution_texture_sampling, - zone.high_speed_image_texture_sampling); + zone.high_speed_image_texture_sampling, + image_background_rgba_override); if (weight_field.empty()) return std::nullopt; } diff --git a/src/libslic3r/TextureMappingOffset.hpp b/src/libslic3r/TextureMappingOffset.hpp index f9b237d04fc..0e9a2440273 100644 --- a/src/libslic3r/TextureMappingOffset.hpp +++ b/src/libslic3r/TextureMappingOffset.hpp @@ -103,7 +103,9 @@ std::optional build_texture_mapping_offset_context_ unsigned int active_component_id_override = 0, std::optional base_outer_width_mm_override = std::nullopt, std::optional layer_height_mm_override = std::nullopt, - std::optional plate_origin_mm_override = std::nullopt); + std::optional plate_origin_mm_override = std::nullopt, + std::optional min_outer_width_mm_override = std::nullopt, + std::optional> image_background_rgba_override = std::nullopt); std::vector sample_weight_field_components(const TextureMappingOffsetWeightField &weight_field, float x_mm, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 58a8a4a1005..909aaf3ad05 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1867,6 +1867,12 @@ public: int dithering_method, float dithering_resolution_mm, float halftone_dot_size_mm, + bool top_surface_image_printing_enabled, + int top_surface_image_printing_method, + float top_surface_image_min_line_width_mm, + float top_surface_image_max_line_width_mm, + int top_surface_image_colored_top_layers, + bool top_surface_image_fixed_coloring_filaments, const TextureMappingManager &texture_mapping_zones, const TextureMappingGlobalSettings &global_settings, const TextureMappingPrimeTowerImage &prime_tower_image, @@ -1892,9 +1898,10 @@ public: tab_choices.Add(_L("Modulation Mode")); tab_choices.Add(_L("Preview Options")); tab_choices.Add(_L("Experimental Options")); + tab_choices.Add(_L("Top-surface coloring (experimental)")); tab_choices.Add(_L("Prime Tower Texture Mapping")); m_options_tab_choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, tab_choices); - m_options_tab_choice->SetSelection(std::clamp(initial_options_tab, 0, 5)); + m_options_tab_choice->SetSelection(std::clamp(initial_options_tab, 0, 6)); tab_row->Add(m_options_tab_choice, 1, wxALIGN_CENTER_VERTICAL); root->Add(tab_row, 0, wxEXPAND | wxALL, gap); @@ -1914,6 +1921,9 @@ public: auto *experimental_page = new wxPanel(m_options_book, wxID_ANY); auto *experimental_root = new wxBoxSizer(wxVERTICAL); experimental_page->SetSizer(experimental_root); + auto *top_surface_page = new wxPanel(m_options_book, wxID_ANY); + auto *top_surface_root = new wxBoxSizer(wxVERTICAL); + top_surface_page->SetSizer(top_surface_root); auto *global_page = new wxPanel(m_options_book, wxID_ANY); auto *global_root = new wxBoxSizer(wxVERTICAL); global_page->SetSizer(global_root); @@ -2315,6 +2325,104 @@ public: update_modulation_mode_options_visibility(false); experimental_root->Add(experimental_box, 0, wxEXPAND | wxALL, gap); + auto *top_surface_box = new wxStaticBoxSizer(wxVERTICAL, top_surface_page, _L("Top-surface coloring")); + m_top_surface_image_printing_enabled_checkbox = + new wxCheckBox(top_surface_page, wxID_ANY, _L("Enable top-surface image coloring")); + m_top_surface_image_printing_enabled_checkbox->SetValue(top_surface_image_printing_enabled); + top_surface_box->Add(m_top_surface_image_printing_enabled_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + auto *top_surface_method_row = new wxBoxSizer(wxHORIZONTAL); + top_surface_method_row->Add(new wxStaticText(top_surface_page, wxID_ANY, _L("Method")), + 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, + gap); + wxArrayString top_surface_method_choices; + top_surface_method_choices.Add(_L("45 degree width modulation")); + top_surface_method_choices.Add(_L("Same-layer 45 partition")); + m_top_surface_image_method_choice = + new wxChoice(top_surface_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, top_surface_method_choices); + m_top_surface_image_method_choice->SetSelection(std::clamp(top_surface_image_printing_method, + int(TextureMappingZone::TopSurfaceImageSameAngle45Width), + int(TextureMappingZone::TopSurfaceImageSameLayer45Partition))); + top_surface_method_row->Add(m_top_surface_image_method_choice, 1, wxALIGN_CENTER_VERTICAL); + top_surface_box->Add(top_surface_method_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + auto *top_surface_width_row = new wxBoxSizer(wxHORIZONTAL); + top_surface_width_row->Add(new wxStaticText(top_surface_page, wxID_ANY, _L("Line width")), + 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, + gap); + m_top_surface_image_min_line_width_spin = + new wxSpinCtrlDouble(top_surface_page, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxSize(FromDIP(84), -1), + wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, + double(TextureMappingZone::MinTopSurfaceImageLineWidthMm), + double(TextureMappingZone::MaxTopSurfaceImageLineWidthMm), + std::clamp(double(top_surface_image_min_line_width_mm), + double(TextureMappingZone::MinTopSurfaceImageLineWidthMm), + double(TextureMappingZone::MaxTopSurfaceImageLineWidthMm)), + 0.01); + m_top_surface_image_min_line_width_spin->SetDigits(2); + top_surface_width_row->Add(m_top_surface_image_min_line_width_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); + top_surface_width_row->Add(new wxStaticText(top_surface_page, wxID_ANY, _L("to")), + 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, + gap / 2); + m_top_surface_image_max_line_width_spin = + new wxSpinCtrlDouble(top_surface_page, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxSize(FromDIP(84), -1), + wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, + double(TextureMappingZone::MinTopSurfaceImageLineWidthMm), + double(TextureMappingZone::MaxTopSurfaceImageLineWidthMm), + std::clamp(double(top_surface_image_max_line_width_mm), + double(TextureMappingZone::MinTopSurfaceImageLineWidthMm), + double(TextureMappingZone::MaxTopSurfaceImageLineWidthMm)), + 0.01); + m_top_surface_image_max_line_width_spin->SetDigits(2); + top_surface_width_row->Add(m_top_surface_image_max_line_width_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); + top_surface_width_row->Add(new wxStaticText(top_surface_page, wxID_ANY, _L("mm")), 0, wxALIGN_CENTER_VERTICAL); + top_surface_box->Add(top_surface_width_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + m_top_surface_image_colored_top_layers_panel = new wxPanel(top_surface_page, wxID_ANY); + auto *top_surface_colored_layers_row = new wxBoxSizer(wxHORIZONTAL); + m_top_surface_image_colored_top_layers_panel->SetSizer(top_surface_colored_layers_row); + top_surface_colored_layers_row->Add(new wxStaticText(m_top_surface_image_colored_top_layers_panel, wxID_ANY, _L("Colored top layers")), + 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, + gap); + m_top_surface_image_colored_top_layers_spin = + new wxSpinCtrl(m_top_surface_image_colored_top_layers_panel, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxSize(FromDIP(70), -1), + wxSP_ARROW_KEYS | wxALIGN_RIGHT, + TextureMappingZone::MinTopSurfaceImageColoredTopLayers, + TextureMappingZone::MaxTopSurfaceImageColoredTopLayers, + std::clamp(top_surface_image_colored_top_layers, + TextureMappingZone::MinTopSurfaceImageColoredTopLayers, + TextureMappingZone::MaxTopSurfaceImageColoredTopLayers)); + top_surface_colored_layers_row->Add(m_top_surface_image_colored_top_layers_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); + top_surface_colored_layers_row->Add(new wxStaticText(m_top_surface_image_colored_top_layers_panel, wxID_ANY, _L("layers")), + 0, + wxALIGN_CENTER_VERTICAL); + top_surface_box->Add(m_top_surface_image_colored_top_layers_panel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + m_top_surface_image_fixed_coloring_filaments_checkbox = + new wxCheckBox(top_surface_page, wxID_ANY, _L("Fixed top-surface coloring filaments")); + m_top_surface_image_fixed_coloring_filaments_checkbox->SetValue(top_surface_image_fixed_coloring_filaments); + top_surface_box->Add(m_top_surface_image_fixed_coloring_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, gap); + m_top_surface_image_printing_enabled_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { + update_top_surface_image_options_visibility(false); + }); + m_top_surface_image_method_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) { + update_top_surface_image_options_visibility(true); + }); + top_surface_root->Add(top_surface_box, 0, wxEXPAND | wxALL, gap); + update_top_surface_image_options_visibility(false); + m_prime_tower_mapping_enabled_checkbox = new wxCheckBox(global_page, wxID_ANY, _L("Enable prime tower image mapping")); m_prime_tower_mapping_enabled_checkbox->SetValue(m_global_settings.enabled); global_root->Add(m_prime_tower_mapping_enabled_checkbox, 0, wxEXPAND | wxALL, gap); @@ -2451,14 +2559,15 @@ public: m_options_book->AddPage(print_settings_page, _L("Modulation Mode")); m_options_book->AddPage(preview_page, _L("Preview Options")); m_options_book->AddPage(experimental_page, _L("Experimental Options")); + m_options_book->AddPage(top_surface_page, _L("Top-surface coloring (experimental)")); m_options_book->AddPage(global_page, _L("Prime Tower Texture Mapping")); update_options_book_min_size(); m_options_tab_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) { if (m_options_book) - m_options_book->SetSelection(std::clamp(evt.GetSelection(), 0, 5)); + m_options_book->SetSelection(std::clamp(evt.GetSelection(), 0, 6)); }); if (m_options_book) - m_options_book->SetSelection(std::clamp(initial_options_tab, 0, 5)); + m_options_book->SetSelection(std::clamp(initial_options_tab, 0, 6)); root->Add(m_options_book, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); if (wxSizer *buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL)) root->Add(buttons, 0, wxEXPAND | wxALL, gap); @@ -2563,6 +2672,55 @@ public: double(TextureMappingZone::MinHalftoneDotSizeMm), double(TextureMappingZone::MaxHalftoneDotSizeMm))); } + bool top_surface_image_printing_enabled() const + { + return m_top_surface_image_printing_enabled_checkbox != nullptr && + m_top_surface_image_printing_enabled_checkbox->GetValue(); + } + int top_surface_image_printing_method() const + { + return m_top_surface_image_method_choice ? + std::clamp(m_top_surface_image_method_choice->GetSelection(), + int(TextureMappingZone::TopSurfaceImageSameAngle45Width), + int(TextureMappingZone::TopSurfaceImageSameLayer45Partition)) : + TextureMappingZone::DefaultTopSurfaceImagePrintingMethod; + } + float top_surface_image_min_line_width_mm() const + { + const double max_width = m_top_surface_image_max_line_width_spin != nullptr ? + m_top_surface_image_max_line_width_spin->GetValue() : + double(TextureMappingZone::DefaultTopSurfaceImageMaxLineWidthMm); + const double value = m_top_surface_image_min_line_width_spin != nullptr ? + m_top_surface_image_min_line_width_spin->GetValue() : + double(TextureMappingZone::DefaultTopSurfaceImageMinLineWidthMm); + return float(std::clamp(value, + double(TextureMappingZone::MinTopSurfaceImageLineWidthMm), + std::clamp(max_width, + double(TextureMappingZone::MinTopSurfaceImageLineWidthMm), + double(TextureMappingZone::MaxTopSurfaceImageLineWidthMm)))); + } + float top_surface_image_max_line_width_mm() const + { + const double value = m_top_surface_image_max_line_width_spin != nullptr ? + m_top_surface_image_max_line_width_spin->GetValue() : + double(TextureMappingZone::DefaultTopSurfaceImageMaxLineWidthMm); + return float(std::clamp(value, + double(TextureMappingZone::MinTopSurfaceImageLineWidthMm), + double(TextureMappingZone::MaxTopSurfaceImageLineWidthMm))); + } + int top_surface_image_colored_top_layers() const + { + return m_top_surface_image_colored_top_layers_spin ? + std::clamp(m_top_surface_image_colored_top_layers_spin->GetValue(), + TextureMappingZone::MinTopSurfaceImageColoredTopLayers, + TextureMappingZone::MaxTopSurfaceImageColoredTopLayers) : + TextureMappingZone::DefaultTopSurfaceImageColoredTopLayers; + } + bool top_surface_image_fixed_coloring_filaments() const + { + return m_top_surface_image_fixed_coloring_filaments_checkbox == nullptr || + m_top_surface_image_fixed_coloring_filaments_checkbox->GetValue(); + } bool minimum_visibility_offset_enabled() const { return m_minimum_visibility_offset_checkbox && m_minimum_visibility_offset_checkbox->GetValue(); @@ -2638,7 +2796,7 @@ public: const TextureMappingPrimeTowerImage& prime_tower_image() const { return m_prime_tower_image; } const TextureMappingPrimeTowerImage& prime_tower_image_back() const { return m_prime_tower_image_back; } - int selected_options_tab() const { return std::clamp(m_options_tab_choice ? m_options_tab_choice->GetSelection() : 0, 0, 5); } + int selected_options_tab() const { return std::clamp(m_options_tab_choice ? m_options_tab_choice->GetSelection() : 0, 0, 6); } bool strength_offsets_expanded() const { return m_strength_offsets_expanded; } std::vector component_strengths_pct() const @@ -2937,6 +3095,37 @@ private: } } + void update_top_surface_image_options_visibility(bool fit_dialog) + { + const bool enabled = + m_top_surface_image_printing_enabled_checkbox != nullptr && + m_top_surface_image_printing_enabled_checkbox->GetValue(); + if (m_top_surface_image_method_choice != nullptr) + m_top_surface_image_method_choice->Enable(enabled); + if (m_top_surface_image_min_line_width_spin != nullptr) + m_top_surface_image_min_line_width_spin->Enable(enabled); + if (m_top_surface_image_max_line_width_spin != nullptr) + m_top_surface_image_max_line_width_spin->Enable(enabled); + const bool same_layer = + enabled && + m_top_surface_image_method_choice != nullptr && + m_top_surface_image_method_choice->GetSelection() == int(TextureMappingZone::TopSurfaceImageSameLayer45Partition); + if (m_top_surface_image_colored_top_layers_panel != nullptr) + m_top_surface_image_colored_top_layers_panel->Show(same_layer); + if (m_top_surface_image_colored_top_layers_spin != nullptr) + m_top_surface_image_colored_top_layers_spin->Enable(same_layer); + if (m_top_surface_image_fixed_coloring_filaments_checkbox != nullptr) + m_top_surface_image_fixed_coloring_filaments_checkbox->Enable(enabled); + layout_current_options_page(); + if (!fit_dialog) + return; + update_options_book_min_size(); + if (GetSizer() != nullptr) { + Layout(); + Fit(); + } + } + wxChoice *m_options_tab_choice {nullptr}; wxSimplebook *m_options_book {nullptr}; wxChoice *m_texture_mapping_mode_choice {nullptr}; @@ -2969,6 +3158,13 @@ private: wxSpinCtrlDouble *m_dithering_resolution_spin {nullptr}; wxPanel *m_halftone_dot_size_panel {nullptr}; wxSpinCtrlDouble *m_halftone_dot_size_spin {nullptr}; + wxCheckBox *m_top_surface_image_printing_enabled_checkbox {nullptr}; + wxChoice *m_top_surface_image_method_choice {nullptr}; + wxSpinCtrlDouble *m_top_surface_image_min_line_width_spin {nullptr}; + wxSpinCtrlDouble *m_top_surface_image_max_line_width_spin {nullptr}; + wxPanel *m_top_surface_image_colored_top_layers_panel {nullptr}; + wxSpinCtrl *m_top_surface_image_colored_top_layers_spin {nullptr}; + wxCheckBox *m_top_surface_image_fixed_coloring_filaments_checkbox {nullptr}; wxCheckBox *m_use_legacy_fixed_color_mode_checkbox {nullptr}; wxCheckBox *m_minimum_visibility_offset_checkbox {nullptr}; wxSpinCtrl *m_minimum_visibility_offset_spin {nullptr}; @@ -7912,6 +8108,12 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.dithering_method, updated.dithering_resolution_mm, updated.halftone_dot_size_mm, + updated.top_surface_image_printing_enabled, + updated.top_surface_image_printing_method, + updated.top_surface_image_min_line_width_mm, + updated.top_surface_image_max_line_width_mm, + updated.top_surface_image_colored_top_layers, + updated.top_surface_image_fixed_coloring_filaments, bundle->texture_mapping_zones, bundle->texture_mapping_global_settings, wxGetApp().model().texture_mapping_prime_tower_image, @@ -7955,6 +8157,12 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.dithering_method = dlg.dithering_method(); updated.dithering_resolution_mm = dlg.dithering_resolution_mm(); updated.halftone_dot_size_mm = dlg.halftone_dot_size_mm(); + updated.top_surface_image_printing_enabled = dlg.top_surface_image_printing_enabled(); + updated.top_surface_image_printing_method = dlg.top_surface_image_printing_method(); + updated.top_surface_image_min_line_width_mm = dlg.top_surface_image_min_line_width_mm(); + updated.top_surface_image_max_line_width_mm = dlg.top_surface_image_max_line_width_mm(); + updated.top_surface_image_colored_top_layers = dlg.top_surface_image_colored_top_layers(); + updated.top_surface_image_fixed_coloring_filaments = dlg.top_surface_image_fixed_coloring_filaments(); if (updated.dithering_enabled) updated.compact_offset_mode = true; updated.minimum_visibility_offset_enabled = dlg.minimum_visibility_offset_enabled();