From d9e74f22f67276daa1ec5445373b47be8b2f672d Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Sat, 23 May 2026 17:52:03 +0100 Subject: [PATCH] Add radial gradients --- .../110/painted_surface_gradient_preview.fs | 13 +- .../140/painted_surface_gradient_preview.fs | 13 +- src/libslic3r/TextureMapping.cpp | 28 ++ src/libslic3r/TextureMapping.hpp | 20 +- src/libslic3r/TextureMappingOffset.cpp | 107 +++++- src/libslic3r/TextureMappingOffset.hpp | 2 + src/slic3r/GUI/3DScene.cpp | 248 +++++++++++- src/slic3r/GUI/3DScene.hpp | 5 +- src/slic3r/GUI/GLCanvas3D.cpp | 1 - .../GLGizmoTextureGradientPointPicker.cpp | 10 +- .../GLGizmoTextureGradientPointPicker.hpp | 2 + src/slic3r/GUI/MMUPaintedTexturePreview.cpp | 143 ++++++- src/slic3r/GUI/MMUPaintedTexturePreview.hpp | 10 +- src/slic3r/GUI/Plater.cpp | 356 ++++++++++++++++-- 14 files changed, 859 insertions(+), 99 deletions(-) diff --git a/resources/shaders/110/painted_surface_gradient_preview.fs b/resources/shaders/110/painted_surface_gradient_preview.fs index c0abe7abeb0..ae36f42bd88 100644 --- a/resources/shaders/110/painted_surface_gradient_preview.fs +++ b/resources/shaders/110/painted_surface_gradient_preview.fs @@ -38,8 +38,10 @@ uniform vec3 gradient_center; uniform float gradient_z_min; uniform float gradient_z_max; uniform bool gradient_linear_mode; +uniform bool gradient_linear_radial_mode; uniform vec3 gradient_linear_start; uniform vec3 gradient_linear_end; +uniform float gradient_linear_radius_mm; uniform int gradient_linear_lut_count; uniform vec3 gradient_linear_lut_colors[MAX_LINEAR_GRADIENT_LUT_COLORS]; @@ -193,9 +195,14 @@ vec3 linear_gradient_lut_color(float t) vec3 linear_gradient_color(int count) { - vec3 direction_vec = gradient_linear_end - gradient_linear_start; - float denom = dot(direction_vec, direction_vec); - float t = denom > EPSILON ? clamp(dot(world_pos.xyz - gradient_linear_start, direction_vec) / denom, 0.0, 1.0) : 0.0; + float t = 0.0; + if (gradient_linear_radial_mode) { + t = clamp(length(world_pos.xyz - gradient_linear_start) / max(gradient_linear_radius_mm, EPSILON), 0.0, 1.0); + } else { + vec3 direction_vec = gradient_linear_end - gradient_linear_start; + float denom = dot(direction_vec, direction_vec); + t = denom > EPSILON ? clamp(dot(world_pos.xyz - gradient_linear_start, direction_vec) / denom, 0.0, 1.0) : 0.0; + } if (count <= 0) return clamp(gradient_base_color, 0.0, 1.0); return linear_gradient_lut_color(t); diff --git a/resources/shaders/140/painted_surface_gradient_preview.fs b/resources/shaders/140/painted_surface_gradient_preview.fs index 4a449d02e65..11246b0c8f4 100644 --- a/resources/shaders/140/painted_surface_gradient_preview.fs +++ b/resources/shaders/140/painted_surface_gradient_preview.fs @@ -38,8 +38,10 @@ uniform vec3 gradient_center; uniform float gradient_z_min; uniform float gradient_z_max; uniform bool gradient_linear_mode; +uniform bool gradient_linear_radial_mode; uniform vec3 gradient_linear_start; uniform vec3 gradient_linear_end; +uniform float gradient_linear_radius_mm; uniform int gradient_linear_lut_count; uniform vec3 gradient_linear_lut_colors[MAX_LINEAR_GRADIENT_LUT_COLORS]; @@ -195,9 +197,14 @@ vec3 linear_gradient_lut_color(float t) vec3 linear_gradient_color(int count) { - vec3 direction_vec = gradient_linear_end - gradient_linear_start; - float denom = dot(direction_vec, direction_vec); - float t = denom > EPSILON ? clamp(dot(world_pos.xyz - gradient_linear_start, direction_vec) / denom, 0.0, 1.0) : 0.0; + float t = 0.0; + if (gradient_linear_radial_mode) { + t = clamp(length(world_pos.xyz - gradient_linear_start) / max(gradient_linear_radius_mm, EPSILON), 0.0, 1.0); + } else { + vec3 direction_vec = gradient_linear_end - gradient_linear_start; + float denom = dot(direction_vec, direction_vec); + t = denom > EPSILON ? clamp(dot(world_pos.xyz - gradient_linear_start, direction_vec) / denom, 0.0, 1.0) : 0.0; + } if (count <= 0) return clamp(gradient_base_color, 0.0, 1.0); return linear_gradient_lut_color(t); diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index c622449a105..ae4c596509c 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -552,6 +552,16 @@ static int surface_pattern_from_name(const std::string &name) return int(TextureMappingZone::ImageTexture); } +static std::string linear_gradient_mode_name(int mode) +{ + return mode == int(TextureMappingZone::LinearGradientRadial) ? "radial" : "linear"; +} + +static int linear_gradient_mode_from_name(const std::string &name) +{ + return name == "radial" ? int(TextureMappingZone::LinearGradientRadial) : int(TextureMappingZone::LinearGradientLinear); +} + static nlohmann::json weights_to_json(const std::string &weights, size_t expected_count) { nlohmann::json out = nlohmann::json::array(); @@ -976,6 +986,10 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const floats_equal(filament_transmission_distances_mm, rhs.filament_transmission_distances_mm) && anchors_equal(linear_gradient_start, rhs.linear_gradient_start) && anchors_equal(linear_gradient_end, rhs.linear_gradient_end) && + linear_gradient_mode == rhs.linear_gradient_mode && + std::abs(linear_gradient_radius_mm - rhs.linear_gradient_radius_mm) <= eps && + linear_gradient_radius_percent == rhs.linear_gradient_radius_percent && + std::abs(linear_gradient_radius_pct - rhs.linear_gradient_radius_pct) <= eps && show_linear_gradient_direction_arrow == rhs.show_linear_gradient_direction_arrow; } @@ -1216,6 +1230,14 @@ std::string TextureMappingManager::serialize_entries() nlohmann::json linear_gradient; linear_gradient["start"] = linear_gradient_anchor_to_json(zone.linear_gradient_start); linear_gradient["end"] = linear_gradient_anchor_to_json(zone.linear_gradient_end); + linear_gradient["mode"] = linear_gradient_mode_name(zone.linear_gradient_mode); + linear_gradient["radius_mm"] = std::isfinite(zone.linear_gradient_radius_mm) ? + std::max(0.f, zone.linear_gradient_radius_mm) : + TextureMappingZone::DefaultLinearGradientRadiusMm; + linear_gradient["radius_percent"] = zone.linear_gradient_radius_percent; + linear_gradient["radius_pct"] = std::isfinite(zone.linear_gradient_radius_pct) ? + std::max(0.f, zone.linear_gradient_radius_pct) : + TextureMappingZone::DefaultLinearGradientRadiusPct; linear_gradient["show_direction_arrow"] = zone.show_linear_gradient_direction_arrow; entry["linear_gradient"] = std::move(linear_gradient); } @@ -1373,6 +1395,12 @@ void TextureMappingManager::load_entries(const std::string &serialized, const nlohmann::json linear_gradient = entry.value("linear_gradient", nlohmann::json::object()); zone.linear_gradient_start = linear_gradient_anchor_from_json(linear_gradient.value("start", nlohmann::json::object())); zone.linear_gradient_end = linear_gradient_anchor_from_json(linear_gradient.value("end", nlohmann::json::object())); + zone.linear_gradient_mode = linear_gradient_mode_from_name(linear_gradient.value("mode", std::string("linear"))); + const float radius_mm = linear_gradient.value("radius_mm", TextureMappingZone::DefaultLinearGradientRadiusMm); + zone.linear_gradient_radius_mm = std::isfinite(radius_mm) ? std::max(0.f, radius_mm) : TextureMappingZone::DefaultLinearGradientRadiusMm; + zone.linear_gradient_radius_percent = linear_gradient.value("radius_percent", TextureMappingZone::DefaultLinearGradientRadiusPercent); + const float radius_pct = linear_gradient.value("radius_pct", TextureMappingZone::DefaultLinearGradientRadiusPct); + zone.linear_gradient_radius_pct = std::isfinite(radius_pct) ? std::max(0.f, radius_pct) : TextureMappingZone::DefaultLinearGradientRadiusPct; zone.show_linear_gradient_direction_arrow = linear_gradient.value("show_direction_arrow", true); } diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index 62811b3e442..26c97cd8de6 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -24,6 +24,11 @@ struct TextureMappingZone LinearGradient = 2 }; + enum LinearGradientMode : uint8_t { + LinearGradientLinear = 0, + LinearGradientRadial = 1 + }; + enum OffsetControlMode : uint8_t { OffsetBasic = 0, OffsetAdvanced = 1 @@ -102,6 +107,10 @@ struct TextureMappingZone }; static constexpr int DefaultSurfacePattern = int(ImageTexture); + static constexpr int DefaultLinearGradientMode = int(LinearGradientLinear); + static constexpr float DefaultLinearGradientRadiusMm = 0.f; + static constexpr bool DefaultLinearGradientRadiusPercent = true; + static constexpr float DefaultLinearGradientRadiusPct = 100.f; static constexpr int DefaultOffsetMode = int(OffsetBasic); static constexpr bool DefaultOffsetRotationEnabled = true; static constexpr float DefaultOffsetRotations = 1.f; @@ -215,11 +224,16 @@ struct TextureMappingZone std::vector filament_transmission_distances_mm; LinearGradientAnchor linear_gradient_start; LinearGradientAnchor linear_gradient_end; - bool show_linear_gradient_direction_arrow = true; + int linear_gradient_mode = DefaultLinearGradientMode; + float linear_gradient_radius_mm = DefaultLinearGradientRadiusMm; + bool linear_gradient_radius_percent = DefaultLinearGradientRadiusPercent; + float linear_gradient_radius_pct = DefaultLinearGradientRadiusPct; + bool show_linear_gradient_direction_arrow = true; bool is_image_texture() const { return surface_pattern == int(ImageTexture); } bool is_2d_gradient() const { return surface_pattern == int(Gradient2D); } 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 uses_perimeter_path_modulation() const { return modulation_mode == int(ModulationPerimeterPath); } @@ -269,6 +283,10 @@ struct TextureMappingZone preview_simulate_colors = DefaultPreviewSimulateColors; preview_limit_resolution = DefaultPreviewLimitResolution; auto_adjust_filament_selection = DefaultAutoAdjustFilamentSelection; + linear_gradient_mode = DefaultLinearGradientMode; + linear_gradient_radius_mm = DefaultLinearGradientRadiusMm; + linear_gradient_radius_percent = DefaultLinearGradientRadiusPercent; + linear_gradient_radius_pct = DefaultLinearGradientRadiusPct; filament_strengths_pct.clear(); filament_minimum_offsets_pct.clear(); filament_transmission_distances_mm.clear(); diff --git a/src/libslic3r/TextureMappingOffset.cpp b/src/libslic3r/TextureMappingOffset.cpp index 564d78bae97..9c6418d3479 100644 --- a/src/libslic3r/TextureMappingOffset.cpp +++ b/src/libslic3r/TextureMappingOffset.cpp @@ -122,6 +122,17 @@ const ModelObject *texture_mapping_anchor_model_object(const Print &print, return nullptr; } +const PrintObject *texture_mapping_anchor_print_object(const Print &print, + const TextureMappingZone::LinearGradientAnchor &anchor) +{ + for (const PrintObject *print_object : print.objects()) { + const ModelObject *model_object = print_object != nullptr ? print_object->model_object() : nullptr; + if (texture_mapping_anchor_matches_model_object(model_object, anchor)) + return print_object; + } + return nullptr; +} + const ModelInstance *texture_mapping_anchor_model_instance(const ModelObject *object, const TextureMappingZone::LinearGradientAnchor &anchor) { @@ -212,6 +223,45 @@ std::pair texture_mapping_default_linear_gradient_points(const Pri return { Vec3f(cx, cy, z_min), Vec3f(cx, cy, z_max) }; } +std::pair texture_mapping_default_radial_gradient(const PrintObject &print_object) +{ + const BoundingBox bbox = print_object.bounding_box(); + const float min_x = unscale(bbox.min.x()); + const float max_x = unscale(bbox.max.x()); + const float min_y = unscale(bbox.min.y()); + const float max_y = unscale(bbox.max.y()); + const float height = std::max(0.01f, unscale(print_object.height())); + const Vec3f center(0.5f * (min_x + max_x), 0.5f * (min_y + max_y), 0.5f * height); + const Vec3f diagonal(std::max(0.f, max_x - min_x), std::max(0.f, max_y - min_y), height); + const float radius = std::max(0.01f, 0.5f * diagonal.norm()); + return { center, radius }; +} + +float texture_mapping_linear_gradient_radius_mm(const PrintObject &print_object, + const TextureMappingZone &zone, + float default_radius_mm) +{ + if (!zone.linear_gradient_radius_percent) { + const float radius_mm = std::isfinite(zone.linear_gradient_radius_mm) ? zone.linear_gradient_radius_mm : default_radius_mm; + return std::max(0.01f, radius_mm); + } + + const float radius_value = std::isfinite(zone.linear_gradient_radius_pct) ? + std::max(0.f, zone.linear_gradient_radius_pct) : + TextureMappingZone::DefaultLinearGradientRadiusPct; + if (zone.linear_gradient_start.valid) { + const Print *print = print_object.print(); + const PrintObject *anchor_print_object = print != nullptr ? + texture_mapping_anchor_print_object(*print, zone.linear_gradient_start) : + nullptr; + if (anchor_print_object != nullptr) + return std::max(0.01f, texture_mapping_default_radial_gradient(*anchor_print_object).second * radius_value / 100.f); + return std::max(0.01f, radius_value); + } + + return std::max(0.01f, default_radius_mm * radius_value / 100.f); +} + int texture_mapping_color_hex_digit(char ch) { return ch >= '0' && ch <= '9' ? ch - '0' : @@ -2967,20 +3017,30 @@ std::optional build_texture_mapping_offset_context_ context.nonlinear_offset_adjustment = zone.nonlinear_offset_adjustment; context.object_center = print_object.bounding_box().center(); if (linear_gradient_mode) { + const bool radial_mode = zone.linear_gradient_mode == int(TextureMappingZone::LinearGradientRadial); auto default_points = texture_mapping_default_linear_gradient_points(print_object); + auto default_radial = texture_mapping_default_radial_gradient(print_object); std::optional start = texture_mapping_linear_gradient_anchor_print_point(print_object, zone.linear_gradient_start, plate_origin_mm_override); - std::optional end = - texture_mapping_linear_gradient_anchor_print_point(print_object, - zone.linear_gradient_end, - plate_origin_mm_override); - context.linear_gradient_start_mm = start ? *start : default_points.first; - context.linear_gradient_end_mm = end ? *end : default_points.second; - if ((context.linear_gradient_end_mm - context.linear_gradient_start_mm).squaredNorm() <= 1e-8f) { - context.linear_gradient_start_mm = default_points.first; - context.linear_gradient_end_mm = default_points.second; + std::optional end; + if (!radial_mode) + end = texture_mapping_linear_gradient_anchor_print_point(print_object, + zone.linear_gradient_end, + plate_origin_mm_override); + context.linear_gradient_radial_mode = radial_mode; + if (radial_mode) { + context.linear_gradient_start_mm = start ? *start : default_radial.first; + context.linear_gradient_radius_mm = texture_mapping_linear_gradient_radius_mm(print_object, zone, default_radial.second); + context.linear_gradient_end_mm = context.linear_gradient_start_mm + Vec3f::UnitX() * context.linear_gradient_radius_mm; + } else { + context.linear_gradient_start_mm = start ? *start : default_points.first; + context.linear_gradient_end_mm = end ? *end : default_points.second; + if ((context.linear_gradient_end_mm - context.linear_gradient_start_mm).squaredNorm() <= 1e-8f) { + context.linear_gradient_start_mm = default_points.first; + context.linear_gradient_end_mm = default_points.second; + } } } context.active_component_id = active_component_id; @@ -3052,18 +3112,23 @@ float texture_mapping_offset_surface_inset_mm(const TextureMappingOffsetContext } inset_strength = std::clamp(1.f - desired_strength, 0.f, 1.f); } else if (context.linear_gradient_mode) { - const Vec3f gradient_vec = context.linear_gradient_end_mm - context.linear_gradient_start_mm; - const float denom = gradient_vec.squaredNorm(); - if (denom <= 1e-8f) { - inset_strength = 0.f; + float t = 0.f; + float z_mm = context.layer != nullptr ? float(context.layer->print_z) : 0.f; + if (std::isfinite(surface_v_mm)) + z_mm = surface_v_mm; + if (!std::isfinite(z_mm)) + z_mm = 0.f; + const Vec3f sample(unscale(point.x()), unscale(point.y()), z_mm); + if (context.linear_gradient_radial_mode) { + const float radius = std::max(0.01f, context.linear_gradient_radius_mm); + t = clamp01f((sample - context.linear_gradient_start_mm).norm() / radius); } else { - float z_mm = context.layer != nullptr ? float(context.layer->print_z) : 0.f; - if (std::isfinite(surface_v_mm)) - z_mm = surface_v_mm; - if (!std::isfinite(z_mm)) - z_mm = 0.f; - const Vec3f sample(unscale(point.x()), unscale(point.y()), z_mm); - const float t = clamp01f((sample - context.linear_gradient_start_mm).dot(gradient_vec) / denom); + const Vec3f gradient_vec = context.linear_gradient_end_mm - context.linear_gradient_start_mm; + const float denom = gradient_vec.squaredNorm(); + if (denom > 1e-8f) + t = clamp01f((sample - context.linear_gradient_start_mm).dot(gradient_vec) / denom); + } + if (context.linear_gradient_radial_mode || (context.linear_gradient_end_mm - context.linear_gradient_start_mm).squaredNorm() > 1e-8f) { float desired_strength = context.active_component_idx == 0 ? 1.f - t : context.active_component_idx == 1 ? t : 0.f; @@ -3071,6 +3136,8 @@ float texture_mapping_offset_surface_inset_mm(const TextureMappingOffsetContext if (max_strength > EPSILON) desired_strength = clamp01f(desired_strength / max_strength); inset_strength = std::clamp(1.f - desired_strength, 0.f, 1.f); + } else { + inset_strength = 0.f; } } else { double theta_direction_x = outward_x; diff --git a/src/libslic3r/TextureMappingOffset.hpp b/src/libslic3r/TextureMappingOffset.hpp index 618952c348a..4cd77513f35 100644 --- a/src/libslic3r/TextureMappingOffset.hpp +++ b/src/libslic3r/TextureMappingOffset.hpp @@ -64,6 +64,8 @@ struct TextureMappingOffsetContext { Point object_center; Vec3f linear_gradient_start_mm { Vec3f::Zero() }; Vec3f linear_gradient_end_mm { Vec3f::Zero() }; + bool linear_gradient_radial_mode { false }; + float linear_gradient_radius_mm { 0.f }; unsigned int active_component_id { 0 }; size_t active_component_idx { size_t(-1) }; std::vector component_ids; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 91a5835723c..2bac87eb4e6 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -194,6 +194,19 @@ const ModelObject *linear_gradient_anchor_model_object(const Model &model, return nullptr; } +bool linear_gradient_anchor_matches_model_object(const Model &model, + const ModelObject *object, + const TextureMappingZone::LinearGradientAnchor &anchor) +{ + if (object == nullptr) + return false; + if (anchor.object_backup_id >= 0 && linear_gradient_model_object_backup_id(object) == anchor.object_backup_id) + return true; + if (anchor.object_id != 0 && object->id().id == anchor.object_id) + return true; + return anchor.object_index_valid && anchor.object_index < model.objects.size() && model.objects[anchor.object_index] == object; +} + const ModelInstance *linear_gradient_anchor_model_instance(const ModelObject *object, const TextureMappingZone::LinearGradientAnchor &anchor) { @@ -233,6 +246,105 @@ std::optional linear_gradient_anchor_global_point(const TextureMappingZon return linear_gradient_vec3_is_finite(fallback) ? std::optional(fallback) : std::nullopt; } +std::optional linear_gradient_anchor_global_point(const GLVolumePtrs &volumes, + const TextureMappingZone::LinearGradientAnchor &anchor) +{ + if (!anchor.valid) + return std::nullopt; + + const Vec3f local = linear_gradient_anchor_array_point(anchor.local_point); + const Model &model = GUI::wxGetApp().model(); + const ModelObject *anchor_object = linear_gradient_anchor_model_object(model, anchor); + const ModelInstance *anchor_instance = linear_gradient_anchor_model_instance(anchor_object, anchor); + for (const GLVolume *volume : volumes) { + if (volume == nullptr || + volume->object_idx() < 0 || + volume->instance_idx() < 0 || + volume->object_idx() >= int(model.objects.size())) + continue; + + const ModelObject *object = model.objects[size_t(volume->object_idx())]; + if (object == nullptr || !linear_gradient_anchor_matches_model_object(model, object, anchor)) + continue; + if (size_t(volume->instance_idx()) >= object->instances.size()) + continue; + + const ModelInstance *instance = object->instances[size_t(volume->instance_idx())]; + if (anchor_instance != nullptr && instance != anchor_instance) + continue; + if (anchor_instance == nullptr && object->instances.size() != 1) + continue; + + const Vec3f global = (volume->get_instance_transformation().get_matrix() * local.cast()).cast(); + if (linear_gradient_vec3_is_finite(global)) + return global; + } + + return linear_gradient_anchor_global_point(anchor); +} + +std::optional linear_gradient_anchor_object_radius(const GLVolumePtrs &volumes, + const TextureMappingZone::LinearGradientAnchor &anchor) +{ + if (!anchor.valid) + return std::nullopt; + + const Model &model = GUI::wxGetApp().model(); + const ModelObject *anchor_object = linear_gradient_anchor_model_object(model, anchor); + const ModelInstance *anchor_instance = linear_gradient_anchor_model_instance(anchor_object, anchor); + BoundingBoxf3 bbox; + bool defined = false; + for (const GLVolume *volume : volumes) { + if (volume == nullptr || + volume->object_idx() < 0 || + volume->instance_idx() < 0 || + volume->object_idx() >= int(model.objects.size())) + continue; + + const ModelObject *object = model.objects[size_t(volume->object_idx())]; + if (object == nullptr || !linear_gradient_anchor_matches_model_object(model, object, anchor)) + continue; + if (size_t(volume->instance_idx()) >= object->instances.size()) + continue; + + const ModelInstance *instance = object->instances[size_t(volume->instance_idx())]; + if (anchor_instance != nullptr && instance != anchor_instance) + continue; + if (anchor_instance == nullptr && object->instances.size() != 1) + continue; + + bbox.merge(volume->transformed_convex_hull_bounding_box()); + defined = true; + } + + if (!defined) + return std::nullopt; + const float radius = 0.5f * (bbox.max.cast() - bbox.min.cast()).norm(); + return std::isfinite(radius) && radius > 0.f ? std::optional(radius) : std::nullopt; +} + +float linear_gradient_radial_radius_mm(const GLVolumePtrs &volumes, + const TextureMappingZone &zone, + float default_radius_mm) +{ + if (!zone.linear_gradient_radius_percent) { + const float radius_mm = std::isfinite(zone.linear_gradient_radius_mm) ? zone.linear_gradient_radius_mm : default_radius_mm; + return std::max(0.01f, radius_mm); + } + + const float radius_value = std::isfinite(zone.linear_gradient_radius_pct) ? + std::max(0.f, zone.linear_gradient_radius_pct) : + TextureMappingZone::DefaultLinearGradientRadiusPct; + if (zone.linear_gradient_start.valid) { + const std::optional anchor_radius = linear_gradient_anchor_object_radius(volumes, zone.linear_gradient_start); + if (anchor_radius) + return std::max(0.01f, *anchor_radius * radius_value / 100.f); + return std::max(0.01f, radius_value); + } + + return std::max(0.01f, default_radius_mm * radius_value / 100.f); +} + bool model_volume_uses_texture_mapping_zone(const ModelVolume &model_volume, unsigned int zone_id) { if (zone_id == 0) @@ -283,6 +395,8 @@ struct LinearGradientArrowUsage { bool any { false }; Vec3f default_start { Vec3f::Zero() }; Vec3f default_end { Vec3f::UnitZ() }; + Vec3f radial_center { Vec3f::Zero() }; + float radial_radius { 1.f }; }; LinearGradientArrowUsage linear_gradient_arrow_usage(const GLVolumePtrs &volumes, unsigned int zone_id) @@ -326,12 +440,15 @@ LinearGradientArrowUsage linear_gradient_arrow_usage(const GLVolumePtrs &volumes Vec3f center_sum = Vec3f::Zero(); float max_z = 0.f; + float max_radius = 0.01f; size_t count = 0; for (const auto &item : object_boxes) { if (!item.second.defined) continue; - center_sum += item.second.center().cast(); + const Vec3f center = item.second.center().cast(); + center_sum += center; max_z = std::max(max_z, float(item.second.max.z())); + max_radius = std::max(max_radius, 0.5f * (item.second.max.cast() - item.second.min.cast()).norm()); ++count; } if (count == 0) @@ -341,6 +458,8 @@ LinearGradientArrowUsage linear_gradient_arrow_usage(const GLVolumePtrs &volumes usage.any = true; usage.default_start = Vec3f(avg.x(), avg.y(), 0.f); usage.default_end = Vec3f(avg.x(), avg.y(), std::max(0.01f, max_z)); + usage.radial_center = avg; + usage.radial_radius = std::max(0.01f, max_radius); return usage; } @@ -479,6 +598,63 @@ void append_linear_gradient_sphere(GUI::GLModel::Geometry &geometry, } } +void append_linear_gradient_dashed_ring(GUI::GLModel::Geometry &geometry, + const Vec3f ¢er, + const Vec3f &u, + const Vec3f &v, + float radius, + float tube_radius, + const ColorRGBA &color) +{ + constexpr int dash_count = 20; + constexpr int arc_steps = 4; + constexpr int tube_sectors = 8; + constexpr float dash_fraction = 0.42f; + if (radius <= 1e-5f || tube_radius <= 1e-6f) + return; + Vec3f normal = u.cross(v).normalized(); + if (!linear_gradient_vec3_is_finite(normal) || normal.squaredNorm() <= 1e-8f) + normal = Vec3f::UnitZ(); + for (int dash = 0; dash < dash_count; ++dash) { + const float a_start = 2.f * float(PI) * float(dash) / float(dash_count); + const float a_end = a_start + 2.f * float(PI) * dash_fraction / float(dash_count); + std::vector> ids(size_t(arc_steps + 1), std::vector(size_t(tube_sectors + 1), 0)); + for (int step = 0; step <= arc_steps; ++step) { + const float t = float(step) / float(arc_steps); + const float a = a_start + (a_end - a_start) * t; + const Vec3f radial = std::cos(a) * u + std::sin(a) * v; + const Vec3f p = center + radius * radial; + for (int sector = 0; sector <= tube_sectors; ++sector) { + const float b = 2.f * float(PI) * float(sector) / float(tube_sectors); + const Vec3f tube_normal = std::cos(b) * radial + std::sin(b) * normal; + ids[size_t(step)][size_t(sector)] = + linear_gradient_add_vertex(geometry, p + tube_radius * tube_normal, tube_normal, color); + } + } + for (int step = 0; step < arc_steps; ++step) { + for (int sector = 0; sector < tube_sectors; ++sector) { + const unsigned int a = ids[size_t(step)][size_t(sector)]; + const unsigned int b = ids[size_t(step + 1)][size_t(sector)]; + const unsigned int c = ids[size_t(step)][size_t(sector + 1)]; + const unsigned int d = ids[size_t(step + 1)][size_t(sector + 1)]; + geometry.add_triangle(a, b, c); + geometry.add_triangle(c, b, d); + } + } + } +} + +void append_linear_gradient_dashed_sphere(GUI::GLModel::Geometry &geometry, + const Vec3f ¢er, + float radius, + float tube_radius, + const ColorRGBA &color) +{ + append_linear_gradient_dashed_ring(geometry, center, Vec3f::UnitX(), Vec3f::UnitY(), radius, tube_radius, color); + append_linear_gradient_dashed_ring(geometry, center, Vec3f::UnitX(), Vec3f::UnitZ(), radius, tube_radius, color); + append_linear_gradient_dashed_ring(geometry, center, Vec3f::UnitY(), Vec3f::UnitZ(), radius, tube_radius, color); +} + void append_linear_gradient_arrow(GUI::GLModel::Geometry &geometry, const Vec3f &start, const Vec3f &end, @@ -1235,7 +1411,9 @@ void GLVolume::render_mmu_texture_preview(const Transform3d &view_matrix, int print_volume_type, const std::array &print_volume_xy, const std::array &print_volume_z, - bool opaque) + bool opaque, + const SurfaceGradientAnchorResolver *surface_gradient_anchor_resolver, + const SurfaceGradientAnchorRadiusResolver *surface_gradient_anchor_radius_resolver) { if (picking || !printable || object_idx() < 0 || volume_idx() < 0) return; @@ -1367,7 +1545,9 @@ void GLVolume::render_mmu_texture_preview(const Transform3d &view_matrix, print_volume_xy, print_volume_z, opaque, - model_volume); + model_volume, + surface_gradient_anchor_resolver, + surface_gradient_anchor_radius_resolver); } if (this->is_left_handed()) @@ -2905,6 +3085,12 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, float(m_clipping_plane[2]), float(m_clipping_plane[3]) }; + const SurfaceGradientAnchorResolver surface_gradient_anchor_resolver = [this](const TextureMappingZone::LinearGradientAnchor &anchor) { + return linear_gradient_anchor_global_point(this->volumes, anchor); + }; + const SurfaceGradientAnchorRadiusResolver surface_gradient_anchor_radius_resolver = [this](const TextureMappingZone::LinearGradientAnchor &anchor) { + return linear_gradient_anchor_object_radius(this->volumes, anchor); + }; const bool render_model_texture_preview = volume.first->object_idx() >= 0 && volume.first->volume_idx() >= 0 && !volume.first->is_wipe_tower && !volume.first->is_modifier && !volume.first->is_extrusion_path; @@ -2916,7 +3102,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, texture_preview_clipping_plane, texture_preview_print_volume_type, m_print_volume.data, - m_print_volume.zs); + m_print_volume.zs, + false, + &surface_gradient_anchor_resolver, + &surface_gradient_anchor_radius_resolver); if (volume.first->is_wipe_tower) { GLWipeTowerVolume *wipe_tower_volume = static_cast(volume.first); wipe_tower_volume->render_prime_tower_image_preview(view_matrix, @@ -2976,6 +3165,8 @@ void GLVolumeCollection::render_linear_gradient_direction_arrows(const Transform black_geometry.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3N3C4 }; GUI::GLModel::Geometry color_geometry; color_geometry.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3N3C4 }; + GUI::GLModel::Geometry radius_geometry; + radius_geometry.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3N3C4 }; for (const TextureMappingZone &zone : texture_mgr.zones()) { if (!zone.enabled || zone.deleted || !zone.is_linear_gradient() || !zone.show_linear_gradient_direction_arrow) @@ -2986,24 +3177,34 @@ void GLVolumeCollection::render_linear_gradient_direction_arrows(const Transform continue; const LinearGradientArrowUsage usage = linear_gradient_arrow_usage(volumes, zone.zone_id); - const std::optional start_anchor = linear_gradient_anchor_global_point(zone.linear_gradient_start); - const std::optional end_anchor = linear_gradient_anchor_global_point(zone.linear_gradient_end); + const std::optional start_anchor = linear_gradient_anchor_global_point(volumes, zone.linear_gradient_start); + const bool radial_mode = zone.linear_gradient_mode == int(TextureMappingZone::LinearGradientRadial); + const std::optional end_anchor = radial_mode ? + std::nullopt : + linear_gradient_anchor_global_point(volumes, zone.linear_gradient_end); if (!usage.any && !start_anchor && !end_anchor) continue; - Vec3f start = start_anchor ? *start_anchor : usage.default_start; - Vec3f end = end_anchor ? *end_anchor : usage.default_end; + Vec3f start = start_anchor ? *start_anchor : (radial_mode ? usage.radial_center : usage.default_start); + Vec3f end = usage.default_end; + if (radial_mode) + end = start + Vec3f::UnitX(); + if (end_anchor) + end = *end_anchor; if (!start_anchor && !usage.any) - start = end - Vec3f::UnitZ(); + start = radial_mode ? end - Vec3f::UnitX() : end - Vec3f::UnitZ(); if (!end_anchor && !usage.any) - end = start + Vec3f::UnitZ(); + end = start + (radial_mode ? Vec3f::UnitX() : Vec3f::UnitZ()); if (!linear_gradient_vec3_is_finite(start) || !linear_gradient_vec3_is_finite(end)) continue; Vec3f delta = end - start; float length = delta.norm(); if (length <= 1e-5f) { - if (usage.any) { + if (radial_mode) { + delta = Vec3f::UnitX(); + length = 1.f; + } else if (usage.any) { start = usage.default_start; end = usage.default_end; delta = end - start; @@ -3012,6 +3213,11 @@ void GLVolumeCollection::render_linear_gradient_direction_arrows(const Transform if (length <= 1e-5f) continue; } + if (radial_mode) { + length = linear_gradient_radial_radius_mm(volumes, zone, usage.radial_radius); + end = start + Vec3f::UnitX() * length; + delta = end - start; + } const std::array start_color = linear_gradient_filament_color(component_ids[0], filament_colors); const std::array end_color = linear_gradient_filament_color(component_ids[1], filament_colors); @@ -3040,11 +3246,15 @@ void GLVolumeCollection::render_linear_gradient_direction_arrows(const Transform const float sphere_radius = shaft_radius * 2.45f; if (start_anchor) append_linear_gradient_sphere(black_geometry, *start_anchor, sphere_radius, ColorRGBA::BLACK()); - if (end_anchor) + if (!radial_mode && end_anchor) append_linear_gradient_sphere(black_geometry, *end_anchor, sphere_radius, ColorRGBA::BLACK()); + if (radial_mode) { + const float tube_radius = 0.5f; + append_linear_gradient_dashed_sphere(radius_geometry, start, length, tube_radius, ColorRGBA(0.f, 0.f, 0.f, 0.4f)); + } } - if (black_geometry.is_empty() && color_geometry.is_empty()) + if (black_geometry.is_empty() && color_geometry.is_empty() && radius_geometry.is_empty()) return; GLShaderProgram *shader = GUI::wxGetApp().get_shader("flat_vertex_color"); @@ -3079,6 +3289,18 @@ void GLVolumeCollection::render_linear_gradient_direction_arrows(const Transform shader->set_uniform("view_model_matrix", view_matrix); shader->set_uniform("projection_matrix", projection_matrix); + if (!radius_geometry.is_empty()) { + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthFunc(GL_LEQUAL)); + glsafe(::glDepthMask(GL_FALSE)); + GUI::GLModel radius_model; + radius_model.init_from(std::move(radius_geometry)); + radius_model.render(shader); + } + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_FALSE)); + if (!black_geometry.is_empty()) { GUI::GLModel black_model; black_model.init_from(std::move(black_geometry)); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index ff3b3a63004..74543fc7501 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -15,6 +15,7 @@ #include "GLModel.hpp" #include "GLTexture.hpp" #include "GLShader.hpp" +#include "MMUPaintedTexturePreview.hpp" #include "MeshUtils.hpp" #include @@ -370,7 +371,9 @@ public: int print_volume_type = -1, const std::array &print_volume_xy = std::array{ 0.f, 0.f, 0.f, 0.f }, const std::array &print_volume_z = std::array{ 0.f, 0.f }, - bool opaque = false); + bool opaque = false, + const SurfaceGradientAnchorResolver *surface_gradient_anchor_resolver = nullptr, + const SurfaceGradientAnchorRadiusResolver *surface_gradient_anchor_radius_resolver = nullptr); void invalidate_texture_mapping_preview(); void set_bounding_boxes_as_dirty() { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dec59a22d2b..bcf8a1df7e5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2042,7 +2042,6 @@ void GLCanvas3D::render(bool only_init) _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id); // BBS: GUI refactor: add canvas size as parameters _render_gcode(cnv_size.get_width(), cnv_size.get_height()); - m_volumes.render_linear_gradient_direction_arrows(camera.get_view_matrix(), camera.get_projection_matrix()); } /* assemble render*/ else if (m_canvas_type == ECanvasType::CanvasAssembleView) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.cpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.cpp index dc1ae9b0e35..dbf8fd7b5c1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.cpp @@ -271,9 +271,13 @@ void GLGizmoTextureGradientPointPicker::on_render_input_window(float, float, flo ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.09f, 0.10f, 0.88f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f)); if (ImGui::Begin("linear_gradient_point_picker_overlay", nullptr, flags)) { - const std::string message = m_target == Target::End ? - _u8L("Click an object to set gradient end point") : - _u8L("Click an object to set gradient start point"); + const std::string message = m_radial_mode ? + (m_target == Target::End ? + _u8L("Click an object to set radial gradient radius point") : + _u8L("Click an object to set radial gradient center point")) : + (m_target == Target::End ? + _u8L("Click an object to set gradient end point") : + _u8L("Click an object to set gradient start point")); ImGui::TextUnformatted(message.c_str()); } ImGui::End(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.hpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.hpp index 09a13315836..78dd7b4aee1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureGradientPointPicker.hpp @@ -40,6 +40,7 @@ public: HoverCallback hover_callback = HoverCallback(), CancelCallback cancel_callback = CancelCallback()); void cancel_hover_preview(); + void set_radial_mode(bool radial_mode) { m_radial_mode = radial_mode; } Target target() const { return m_target; } private: @@ -65,6 +66,7 @@ private: bool m_has_hover { false }; bool m_hover_preview_emitted { false }; bool m_last_hover_preview_had_hit { false }; + bool m_radial_mode { false }; Vec2d m_mouse_pos { Vec2d::Zero() }; }; diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index 8a717d37b62..74471b99cca 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -120,8 +120,10 @@ struct SurfaceGradientPreviewSettings float z_min = 0.f; float z_max = 0.f; bool linear_gradient = false; + bool linear_gradient_radial = false; Vec3f linear_start = Vec3f::Zero(); Vec3f linear_end = Vec3f::UnitZ(); + float linear_radius_mm = 1.f; std::vector> linear_gradient_lut_colors; }; @@ -4233,7 +4235,8 @@ std::optional surface_gradient_anchor_global_point(const TextureMappingZo std::optional surface_gradient_anchor_world_point_for_volume(const ModelVolume &model_volume, const Transform3d &world_matrix, - const TextureMappingZone::LinearGradientAnchor &anchor) + const TextureMappingZone::LinearGradientAnchor &anchor, + const SurfaceGradientAnchorResolver *surface_gradient_anchor_resolver) { if (!anchor.valid) return std::nullopt; @@ -4248,13 +4251,105 @@ std::optional surface_gradient_anchor_world_point_for_volume(const ModelV return world; } + if (surface_gradient_anchor_resolver != nullptr) { + const std::optional live_global = (*surface_gradient_anchor_resolver)(anchor); + if (live_global && surface_gradient_anchor_vec_finite(*live_global)) + return live_global; + } + return surface_gradient_anchor_global_point(anchor); } +std::optional surface_gradient_anchor_object_radius(const TextureMappingZone::LinearGradientAnchor &anchor) +{ + if (!anchor.valid) + return std::nullopt; + + const Model &model = GUI::wxGetApp().model(); + const ModelObject *object = surface_gradient_anchor_model_object(model, anchor); + if (object == nullptr) + return std::nullopt; + + const ModelInstance *anchor_instance = surface_gradient_anchor_model_instance(object, anchor); + BoundingBoxf3 bbox; + bool defined = false; + auto merge_instance = [&bbox, &defined, object](const ModelInstance *instance) { + if (instance == nullptr) + return; + for (const ModelVolume *volume : object->volumes) { + if (volume == nullptr) + continue; + bbox.merge(volume->mesh().transformed_bounding_box(instance->get_matrix() * volume->get_matrix(), 0.0)); + defined = true; + } + }; + if (anchor_instance != nullptr) + merge_instance(anchor_instance); + else + for (const ModelInstance *instance : object->instances) + merge_instance(instance); + + if (!defined) + return std::nullopt; + const float radius = 0.5f * (bbox.max.cast() - bbox.min.cast()).norm(); + return std::isfinite(radius) && radius > 0.f ? std::optional(radius) : std::nullopt; +} + +float surface_gradient_linear_radius_mm(const TextureMappingZone &zone, + float default_radius_mm, + const SurfaceGradientAnchorRadiusResolver *surface_gradient_anchor_radius_resolver) +{ + if (!zone.linear_gradient_radius_percent) { + const float radius_mm = std::isfinite(zone.linear_gradient_radius_mm) ? zone.linear_gradient_radius_mm : default_radius_mm; + return std::max(0.01f, radius_mm); + } + + const float radius_value = std::isfinite(zone.linear_gradient_radius_pct) ? + std::max(0.f, zone.linear_gradient_radius_pct) : + TextureMappingZone::DefaultLinearGradientRadiusPct; + if (zone.linear_gradient_start.valid) { + std::optional anchor_radius; + if (surface_gradient_anchor_radius_resolver != nullptr) + anchor_radius = (*surface_gradient_anchor_radius_resolver)(zone.linear_gradient_start); + if (!anchor_radius) + anchor_radius = surface_gradient_anchor_object_radius(zone.linear_gradient_start); + if (anchor_radius) + return std::max(0.01f, *anchor_radius * radius_value / 100.f); + return std::max(0.01f, radius_value); + } + + return std::max(0.01f, default_radius_mm * radius_value / 100.f); +} + +float surface_gradient_object_radius_for_volume(const ModelVolume &model_volume, + const Transform3d &world_matrix, + float fallback_radius) +{ + const ModelObject *object = model_volume.get_object(); + if (object == nullptr) + return fallback_radius; + + const Transform3d instance_matrix = world_matrix * model_volume.get_matrix().inverse(); + BoundingBoxf3 bbox; + bool defined = false; + for (const ModelVolume *volume : object->volumes) { + if (volume == nullptr) + continue; + bbox.merge(volume->mesh().transformed_bounding_box(instance_matrix * volume->get_matrix(), 0.0)); + defined = true; + } + if (!defined) + return fallback_radius; + const float radius = 0.5f * (bbox.max.cast() - bbox.min.cast()).norm(); + return std::isfinite(radius) && radius > 0.f ? radius : fallback_radius; +} + std::optional surface_gradient_preview_settings_for_zone(const ModelVolume &model_volume, const Transform3d &world_matrix, const TextureMappingZone &zone, - size_t num_physical) + size_t num_physical, + const SurfaceGradientAnchorResolver *surface_gradient_anchor_resolver = nullptr, + const SurfaceGradientAnchorRadiusResolver *surface_gradient_anchor_radius_resolver = nullptr) { if (!is_gradient_zone(zone)) return std::nullopt; @@ -4355,17 +4450,31 @@ std::optional surface_gradient_preview_settings_ settings.z_min = min_pt.z(); settings.z_max = max_pt.z(); if (settings.linear_gradient) { - settings.linear_start = Vec3f(settings.center.x(), settings.center.y(), settings.z_min); - settings.linear_end = Vec3f(settings.center.x(), settings.center.y(), settings.z_max); - if (std::optional start = - surface_gradient_anchor_world_point_for_volume(model_volume, world_matrix, zone.linear_gradient_start)) - settings.linear_start = *start; - if (std::optional end = - surface_gradient_anchor_world_point_for_volume(model_volume, world_matrix, zone.linear_gradient_end)) - settings.linear_end = *end; - if ((settings.linear_end - settings.linear_start).squaredNorm() <= 1e-8f) { + settings.linear_gradient_radial = zone.linear_gradient_mode == int(TextureMappingZone::LinearGradientRadial); + const Vec3f bbox_diagonal = max_pt - min_pt; + const float default_radius = surface_gradient_object_radius_for_volume(model_volume, + world_matrix, + std::max(0.01f, 0.5f * bbox_diagonal.norm())); + if (settings.linear_gradient_radial) { + settings.linear_start = settings.center; + if (std::optional start = + surface_gradient_anchor_world_point_for_volume(model_volume, world_matrix, zone.linear_gradient_start, surface_gradient_anchor_resolver)) + settings.linear_start = *start; + settings.linear_radius_mm = surface_gradient_linear_radius_mm(zone, default_radius, surface_gradient_anchor_radius_resolver); + settings.linear_end = settings.linear_start + Vec3f::UnitX() * settings.linear_radius_mm; + } else { settings.linear_start = Vec3f(settings.center.x(), settings.center.y(), settings.z_min); settings.linear_end = Vec3f(settings.center.x(), settings.center.y(), settings.z_max); + if (std::optional start = + surface_gradient_anchor_world_point_for_volume(model_volume, world_matrix, zone.linear_gradient_start, surface_gradient_anchor_resolver)) + settings.linear_start = *start; + if (std::optional end = + surface_gradient_anchor_world_point_for_volume(model_volume, world_matrix, zone.linear_gradient_end, surface_gradient_anchor_resolver)) + settings.linear_end = *end; + if ((settings.linear_end - settings.linear_start).squaredNorm() <= 1e-8f) { + settings.linear_start = Vec3f(settings.center.x(), settings.center.y(), settings.z_min); + settings.linear_end = Vec3f(settings.center.x(), settings.center.y(), settings.z_max); + } } } return settings; @@ -4509,12 +4618,14 @@ void set_surface_gradient_preview_uniforms(GLShaderProgram &shader, const Surfac shader.set_uniform("gradient_clockwise", settings == nullptr || settings->clockwise); shader.set_uniform("gradient_fade_mode", settings != nullptr ? settings->fade_mode : int(TextureMappingZone::OffsetFadeNone)); shader.set_uniform("gradient_linear_mode", settings != nullptr && settings->linear_gradient); + shader.set_uniform("gradient_linear_radial_mode", settings != nullptr && settings->linear_gradient_radial); const Vec3f center = settings != nullptr ? settings->center : Vec3f::Zero(); shader.set_uniform("gradient_center", center); shader.set_uniform("gradient_z_min", settings != nullptr ? settings->z_min : 0.f); shader.set_uniform("gradient_z_max", settings != nullptr ? settings->z_max : 0.f); shader.set_uniform("gradient_linear_start", settings != nullptr ? settings->linear_start : Vec3f::Zero()); shader.set_uniform("gradient_linear_end", settings != nullptr ? settings->linear_end : Vec3f::UnitZ()); + shader.set_uniform("gradient_linear_radius_mm", settings != nullptr ? settings->linear_radius_mm : 1.f); const size_t lut_count = settings == nullptr ? size_t(0) : std::min(settings->linear_gradient_lut_colors.size(), k_surface_gradient_preview_lut_size); shader.set_uniform("gradient_linear_lut_count", int(lut_count)); @@ -5085,6 +5196,10 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature, signature_mix(std::hash{}(zone.preview_limit_resolution ? 1 : 0)); signature_mix_float(zone.contrast_pct, 100.f); signature_mix_float(zone.tone_gamma); + signature_mix(std::hash{}(zone.linear_gradient_mode)); + signature_mix_float(zone.linear_gradient_radius_mm, 1000.f); + signature_mix(std::hash{}(zone.linear_gradient_radius_percent ? 1 : 0)); + signature_mix_float(zone.linear_gradient_radius_pct, 1000.f); for (const float strength_pct : zone.filament_strengths_pct) signature_mix_float(strength_pct, 100.f); for (const float minimum_offset_pct : zone.filament_minimum_offsets_pct) @@ -5351,7 +5466,9 @@ void render_model_vertex_color_preview_models( const std::array &print_volume_xy, const std::array &print_volume_z, bool opaque, - const ModelVolume *model_volume) + const ModelVolume *model_volume, + const SurfaceGradientAnchorResolver *surface_gradient_anchor_resolver, + const SurfaceGradientAnchorRadiusResolver *surface_gradient_anchor_radius_resolver) { if (models.empty() || colors.size() != models.size() || filament_ids.size() != models.size()) return; @@ -5403,7 +5520,7 @@ void render_model_vertex_color_preview_models( if (model_volume == nullptr || gradient_shader == nullptr) continue; const std::optional settings = - surface_gradient_preview_settings_for_zone(*model_volume, model_matrix, *zone, num_physical); + surface_gradient_preview_settings_for_zone(*model_volume, model_matrix, *zone, num_physical, surface_gradient_anchor_resolver, surface_gradient_anchor_radius_resolver); if (!settings && !invalid) continue; if (!use_shader(gradient_shader)) diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.hpp b/src/slic3r/GUI/MMUPaintedTexturePreview.hpp index ecb1d285669..0803da60441 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.hpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.hpp @@ -6,9 +6,12 @@ #include "libslic3r/Color.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/TextureMapping.hpp" #include "libslic3r/TriangleSelector.hpp" #include +#include +#include #include #include @@ -16,6 +19,9 @@ namespace Slic3r { class TextureMappingManager; +using SurfaceGradientAnchorResolver = std::function(const TextureMappingZone::LinearGradientAnchor &)>; +using SurfaceGradientAnchorRadiusResolver = std::function(const TextureMappingZone::LinearGradientAnchor &)>; + bool build_mmu_texture_preview_models( const ModelVolume &model_volume, const std::vector> &triangles_per_type, @@ -149,7 +155,9 @@ void render_model_vertex_color_preview_models( const std::array &print_volume_xy = std::array{ 0.f, 0.f, 0.f, 0.f }, const std::array &print_volume_z = std::array{ 0.f, 0.f }, bool opaque = false, - const ModelVolume *model_volume = nullptr); + const ModelVolume *model_volume = nullptr, + const SurfaceGradientAnchorResolver *surface_gradient_anchor_resolver = nullptr, + const SurfaceGradientAnchorRadiusResolver *surface_gradient_anchor_radius_resolver = nullptr); } // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4034ffb0f32..2b73f603fc1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -981,7 +982,8 @@ static std::vector texture_mapping_channel_labels(int filament_color_m static wxString texture_mapping_summary(const TextureMappingZone &zone, size_t num_physical) { const bool raw_offset = zone.is_image_texture() && zone.texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues); - wxString summary = zone.is_linear_gradient() ? _L("Linear Gradient") : + wxString summary = zone.is_radial_linear_gradient() ? _L("Radial Gradient") : + zone.is_linear_gradient() ? _L("Linear Gradient") : zone.is_2d_gradient() ? _L("2D Gradient") : from_u8(TextureMappingManager::filament_color_mode_name(zone.filament_color_mode)); if (!zone.is_surface_gradient() && summary == "any") @@ -1004,6 +1006,8 @@ static wxString texture_mapping_summary(const TextureMappingZone &zone, size_t n static wxString texture_mapping_menu_label(const TextureMappingZone &zone) { + if (zone.is_radial_linear_gradient()) + return _L("Texture Mapping Radial Gradient"); if (zone.is_linear_gradient()) return _L("Texture Mapping Linear Gradient"); if (zone.is_2d_gradient()) @@ -1050,12 +1054,14 @@ public: void set_data(const std::vector &palette, const std::vector &component_ids, const wxColour &fallback, - bool linear_gradient = false) + bool linear_gradient = false, + bool radial_gradient = false) { m_palette = palette; m_component_ids = component_ids; m_fallback = fallback.IsOk() ? fallback : wxColour("#26A69A"); m_linear_gradient = linear_gradient; + m_radial_gradient = radial_gradient; Refresh(); } @@ -1081,16 +1087,32 @@ private: if (m_linear_gradient && m_component_ids.size() >= 2) { const wxColour start = color_for(m_component_ids[0]); const wxColour end = color_for(m_component_ids[1]); - const int h = std::max(1, rect.GetHeight()); - for (int y = 0; y < h; ++y) { - const double t = h <= 1 ? 0.0 : double(h - 1 - y) / double(h - 1); - const auto mix = [t](unsigned char a, unsigned char b) { - return static_cast(std::lround(double(a) * (1.0 - t) + double(b) * t)); - }; - dc.SetBrush(wxBrush(wxColour(mix(start.Red(), end.Red()), - mix(start.Green(), end.Green()), - mix(start.Blue(), end.Blue())))); - dc.DrawRectangle(rect.GetLeft(), rect.GetTop() + y, rect.GetWidth(), 1); + auto mix_channel = [](double t, unsigned char a, unsigned char b) { + return static_cast(std::lround(double(a) * (1.0 - t) + double(b) * t)); + }; + if (m_radial_gradient) { + const double cx = double(rect.GetLeft()) + 0.5 * double(rect.GetWidth() - 1); + const double cy = double(rect.GetTop()) + 0.5 * double(rect.GetHeight() - 1); + const double radius = std::max(1.0, std::hypot(0.5 * double(rect.GetWidth()), 0.5 * double(rect.GetHeight()))); + for (int y = rect.GetTop(); y <= rect.GetBottom(); ++y) { + for (int x = rect.GetLeft(); x <= rect.GetRight(); ++x) { + const double t = std::clamp(std::hypot(double(x) - cx, double(y) - cy) / radius, 0.0, 1.0); + dc.SetPen(wxPen(wxColour(mix_channel(t, start.Red(), end.Red()), + mix_channel(t, start.Green(), end.Green()), + mix_channel(t, start.Blue(), end.Blue())))); + dc.DrawPoint(x, y); + } + } + dc.SetPen(*wxTRANSPARENT_PEN); + } else { + const int h = std::max(1, rect.GetHeight()); + for (int y = 0; y < h; ++y) { + const double t = h <= 1 ? 0.0 : double(h - 1 - y) / double(h - 1); + dc.SetBrush(wxBrush(wxColour(mix_channel(t, start.Red(), end.Red()), + mix_channel(t, start.Green(), end.Green()), + mix_channel(t, start.Blue(), end.Blue())))); + dc.DrawRectangle(rect.GetLeft(), rect.GetTop() + y, rect.GetWidth(), 1); + } } } else if (!m_component_ids.empty()) { const int slots = int(m_component_ids.size()); @@ -1108,6 +1130,7 @@ private: std::vector m_component_ids; wxColour m_fallback {wxColour("#26A69A")}; bool m_linear_gradient { false }; + bool m_radial_gradient { false }; }; class TextureMappingNumberSwatch : public wxPanel @@ -2690,6 +2713,7 @@ struct Sidebar::priv wxStaticLine* m_staticline2; StaticBox* m_panel_texture_mapping_title = nullptr; wxScrolledWindow* m_panel_texture_mapping_content = nullptr; + wxPanel* m_panel_texture_mapping_resize_handle = nullptr; wxBoxSizer* m_sizer_texture_mapping_content = nullptr; ScalableButton* m_texture_mapping_icon = nullptr; wxStaticText* m_staticText_texture_mapping = nullptr; @@ -2697,6 +2721,11 @@ struct Sidebar::priv std::unordered_set m_expanded_texture_mapping_rows; int m_texture_mapping_advanced_options_tab = 0; bool m_texture_mapping_filament_strength_offsets_expanded = false; + int m_texture_mapping_base_height = 0; + int m_texture_mapping_user_height = 0; + bool m_texture_mapping_resizing = false; + int m_texture_mapping_resize_start_screen_y = 0; + int m_texture_mapping_resize_start_height = 0; wxPanel* m_panel_project_title; ScalableButton* m_filament_icon = nullptr; Button * m_flushing_volume_btn = nullptr; @@ -2737,6 +2766,7 @@ struct Sidebar::priv bool sync_extruder_list(bool &only_external_material); bool switch_diameter(bool single); void update_sync_status(const MachineObject* obj); + void apply_texture_mapping_area_height(Sidebar *sidebar); #ifdef _WIN32 wxString btn_reslice_tip; @@ -2840,6 +2870,27 @@ Sidebar::priv::~priv() #endif } +void Sidebar::priv::apply_texture_mapping_area_height(Sidebar *sidebar) +{ + if (sidebar == nullptr || m_panel_texture_mapping_content == nullptr || m_panel_texture_mapping_content->GetSizer() == nullptr) + return; + + m_panel_texture_mapping_content->Layout(); + m_panel_texture_mapping_content->FitInside(); + + const int default_max_height = sidebar->FromDIP(260); + wxSize min_size = m_panel_texture_mapping_content->GetSizer()->GetMinSize(); + m_texture_mapping_base_height = std::max(sidebar->FromDIP(1), std::min(min_size.y, default_max_height)); + + const int requested_height = m_texture_mapping_user_height > 0 ? + std::max(m_texture_mapping_user_height, m_texture_mapping_base_height) : + m_texture_mapping_base_height; + const int visible_height = std::max(sidebar->FromDIP(1), std::min(min_size.y, requested_height)); + + m_panel_texture_mapping_content->SetMaxSize(wxSize(-1, requested_height)); + m_panel_texture_mapping_content->SetMinSize(wxSize(-1, visible_height)); +} + void Sidebar::priv::show_preset_comboboxes() { const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; @@ -4303,13 +4354,6 @@ Sidebar::Sidebar(Plater *parent) p->m_panel_filament_title = new StaticBox(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_NONE); p->m_panel_filament_title->SetBackgroundColor(title_bg); p->m_panel_filament_title->SetBackgroundColor2(0xF1F1F1); - p->m_panel_filament_title->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &e) { - if (e.GetPosition().x > (p->m_flushing_volume_btn->IsShown() - ? p->m_flushing_volume_btn->GetPosition().x : (p->m_bpButton_add_filament->GetPosition().x - FromDIP(30)))) // ORCA exclude area of del button from titlebar collapse/expand feature to fix undesired collapse when user spams del filament button - return; - p->m_panel_filament_content->Show(!p->m_panel_filament_content->IsShown()); - m_scrolled_sizer->Layout(); - }); wxBoxSizer* bSizer39; bSizer39 = new wxBoxSizer( wxHORIZONTAL ); @@ -4397,6 +4441,7 @@ Sidebar::Sidebar(Plater *parent) bSizer39->Add(set_btn, 0, wxALIGN_CENTER | wxLEFT, FromDIP(SidebarProps::WideSpacing())); bSizer39->AddSpacer(FromDIP(SidebarProps::TitlebarMargin())); + p->m_panel_filament_title->Layout(); // add filament content p->m_panel_filament_content = new wxScrolledWindow( p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); @@ -4428,6 +4473,43 @@ Sidebar::Sidebar(Plater *parent) update_filaments_area_height(); // ORCA scrolled_sizer->Add(p->m_panel_filament_content, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(SidebarProps::ContentMarginV())); // ORCA use vertical margin on parent otherwise it shows scrollbar even on 1 filament + + auto toggle_filament_content = [this]() { + if (p->m_panel_filament_content == nullptr) + return; + p->m_panel_filament_content->Show(!p->m_panel_filament_content->IsShown()); + m_scrolled_sizer->Layout(); + }; + auto filament_title_button_hit = [this](const wxPoint &pos) { + for (wxWindow *window : {static_cast(p->m_filament_icon), + static_cast(p->m_flushing_volume_btn), + static_cast(p->m_bpButton_del_filament), + static_cast(p->m_bpButton_add_filament), + static_cast(p->m_bpButton_ams_filament), + static_cast(p->m_bpButton_set_filament)}) { + if (window == nullptr || !window->IsShown()) + continue; + wxRect rect(window->GetPosition(), window->GetSize()); + rect.Inflate(FromDIP(6), FromDIP(4)); + if (rect.Contains(pos)) + return true; + } + return false; + }; + auto handle_filament_title_click = [this, toggle_filament_content, filament_title_button_hit](wxMouseEvent &e) { + wxWindow *event_window = dynamic_cast(e.GetEventObject()); + wxPoint title_pos = e.GetPosition(); + if (event_window != nullptr && event_window != p->m_panel_filament_title) + title_pos = p->m_panel_filament_title->ScreenToClient(event_window->ClientToScreen(e.GetPosition())); + if (filament_title_button_hit(title_pos)) + return; + toggle_filament_content(); + }; + p->m_panel_filament_title->Bind(wxEVT_LEFT_UP, handle_filament_title_click); + p->m_staticText_filament_settings->Bind(wxEVT_LEFT_UP, handle_filament_title_click); + p->m_filament_icon->Bind(wxEVT_BUTTON, [toggle_filament_content](wxCommandEvent &) { + toggle_filament_content(); + }); } { @@ -4516,6 +4598,44 @@ Sidebar::Sidebar(Plater *parent) p->m_panel_texture_mapping_content->SetSizer(p->m_sizer_texture_mapping_content); p->m_panel_texture_mapping_content->Layout(); scrolled_sizer->Add(p->m_panel_texture_mapping_content, 0, wxEXPAND, 0); + p->m_panel_texture_mapping_resize_handle = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(5)), wxBORDER_NONE); + p->m_panel_texture_mapping_resize_handle->SetMinSize(wxSize(-1, FromDIP(5))); + p->m_panel_texture_mapping_resize_handle->SetMaxSize(wxSize(-1, FromDIP(5))); + p->m_panel_texture_mapping_resize_handle->SetCursor(wxCursor(wxCURSOR_SIZENS)); + p->m_panel_texture_mapping_resize_handle->SetBackgroundColour(p->m_panel_texture_mapping_content->GetBackgroundColour()); + scrolled_sizer->Add(p->m_panel_texture_mapping_resize_handle, 0, wxEXPAND, 0); + + auto resize_texture_mapping_to = [this](int height) { + p->m_texture_mapping_user_height = std::max(p->m_texture_mapping_base_height, height); + p->apply_texture_mapping_area_height(this); + m_scrolled_sizer->Layout(); + Layout(); + }; + p->m_panel_texture_mapping_resize_handle->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + if (p->m_panel_texture_mapping_content == nullptr) + return; + p->m_texture_mapping_resizing = true; + p->m_texture_mapping_resize_start_screen_y = p->m_panel_texture_mapping_resize_handle->ClientToScreen(e.GetPosition()).y; + p->m_texture_mapping_resize_start_height = std::max(p->m_texture_mapping_user_height, p->m_texture_mapping_base_height); + if (p->m_panel_texture_mapping_resize_handle->HasCapture() == false) + p->m_panel_texture_mapping_resize_handle->CaptureMouse(); + }); + p->m_panel_texture_mapping_resize_handle->Bind(wxEVT_MOTION, [this, resize_texture_mapping_to](wxMouseEvent &e) { + if (!p->m_texture_mapping_resizing) { + e.Skip(); + return; + } + const int delta = p->m_panel_texture_mapping_resize_handle->ClientToScreen(e.GetPosition()).y - p->m_texture_mapping_resize_start_screen_y; + resize_texture_mapping_to(p->m_texture_mapping_resize_start_height + delta); + }); + p->m_panel_texture_mapping_resize_handle->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &) { + p->m_texture_mapping_resizing = false; + if (p->m_panel_texture_mapping_resize_handle != nullptr && p->m_panel_texture_mapping_resize_handle->HasCapture()) + p->m_panel_texture_mapping_resize_handle->ReleaseMouse(); + }); + p->m_panel_texture_mapping_resize_handle->Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent &) { + p->m_texture_mapping_resizing = false; + }); p->m_panel_texture_mapping_title->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) { int button_left = p->m_panel_texture_mapping_title->GetClientSize().x; @@ -4523,12 +4643,16 @@ Sidebar::Sidebar(Plater *parent) button_left = std::min(button_left, p->m_btn_add_texture_map->GetPosition().x); if (e.GetPosition().x > button_left - FromDIP(12)) return; - p->m_panel_texture_mapping_content->Show(!p->m_panel_texture_mapping_content->IsShown()); + const bool show_content = !p->m_panel_texture_mapping_content->IsShown(); + p->m_panel_texture_mapping_content->Show(show_content); + if (p->m_panel_texture_mapping_resize_handle != nullptr) + p->m_panel_texture_mapping_resize_handle->Show(show_content); m_scrolled_sizer->Layout(); }); p->m_panel_texture_mapping_title->Hide(); p->m_panel_texture_mapping_content->Hide(); + p->m_panel_texture_mapping_resize_handle->Hide(); } { @@ -6167,16 +6291,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) content_sizer->Clear(true); content_sizer->AddSpacer(FromDIP(SidebarProps::ContentMargin())); auto update_texture_mapping_area_height = [this]() { - if (p->m_panel_texture_mapping_content == nullptr || p->m_panel_texture_mapping_content->GetSizer() == nullptr) - return; - const int max_height = FromDIP(260); - p->m_panel_texture_mapping_content->SetMaxSize(wxSize(-1, max_height)); - p->m_panel_texture_mapping_content->Layout(); - p->m_panel_texture_mapping_content->FitInside(); - wxSize min_size = p->m_panel_texture_mapping_content->GetSizer()->GetMinSize(); - if (min_size.y > max_height) - min_size.y = max_height; - p->m_panel_texture_mapping_content->SetMinSize(wxSize(-1, std::max(FromDIP(1), min_size.y))); + p->apply_texture_mapping_area_height(this); }; if (p->m_btn_add_texture_map != nullptr) @@ -6185,6 +6300,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) if (num_physical < 2) { p->m_panel_texture_mapping_title->Hide(); p->m_panel_texture_mapping_content->Hide(); + if (p->m_panel_texture_mapping_resize_handle != nullptr) + p->m_panel_texture_mapping_resize_handle->Hide(); m_scrolled_sizer->Layout(); Layout(); return; @@ -6192,6 +6309,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) p->m_panel_texture_mapping_title->Show(); p->m_panel_texture_mapping_content->Show(); + if (p->m_panel_texture_mapping_resize_handle != nullptr) + p->m_panel_texture_mapping_resize_handle->Show(); const bool is_dark = wxGetApp().dark_mode(); const wxColour rows_bg = is_dark ? wxColour(45, 45, 49) : wxColour(246, 248, 251); @@ -6200,6 +6319,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) const wxColour summary_fg = is_dark ? wxColour(182, 182, 182) : wxColour(96, 96, 96); const int gap = FromDIP(6); p->m_panel_texture_mapping_content->SetBackgroundColour(rows_bg); + if (p->m_panel_texture_mapping_resize_handle != nullptr) + p->m_panel_texture_mapping_resize_handle->SetBackgroundColour(rows_bg); auto make_sidebar_combo = [](wxWindow *parent, const wxArrayString &choices, int selection, const wxSize &size) { auto *combo = new ::ComboBox(parent, wxID_ANY, wxString(), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY); @@ -6347,6 +6468,14 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) return int(TextureMappingZone::ImageTexture); }; + auto linear_gradient_mode_selection = [](const TextureMappingZone &zone) { + return zone.linear_gradient_mode == int(TextureMappingZone::LinearGradientRadial) ? 1 : 0; + }; + + auto linear_gradient_mode_from_selection = [](int selection) { + return selection == 1 ? int(TextureMappingZone::LinearGradientRadial) : int(TextureMappingZone::LinearGradientLinear); + }; + if (visible_zone_indices.empty()) { auto *empty_label = new wxStaticText(p->m_panel_texture_mapping_content, wxID_ANY, _L("No texture maps yet.")); empty_label->SetForegroundColour(summary_fg); @@ -6440,7 +6569,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) TextureMappingManager::effective_texture_component_ids(entry, num_physical, physical_colors) : texture_mapping_selected_ids(entry, num_physical), parse_texture_mapping_color(entry.display_color), - entry.is_linear_gradient()); + entry.is_linear_gradient(), + entry.is_radial_linear_gradient()); header_sizer->Add(preview, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); row_sizer->Add(header, 0, wxEXPAND | wxTOP | wxBOTTOM, gap); @@ -6462,7 +6592,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) TextureMappingManager::effective_texture_component_ids(zone, num_physical, physical_colors) : texture_mapping_selected_ids(zone, num_physical), parse_texture_mapping_color(zone.display_color), - zone.is_linear_gradient()); + zone.is_linear_gradient(), + zone.is_radial_linear_gradient()); } if (swatch != nullptr) swatch->set_data(parse_texture_mapping_color(zone.display_color), zone_id); @@ -6522,7 +6653,19 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) } editor_sizer->Add(filaments_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); - auto *linear_gradient_row = new wxWrapSizer(wxHORIZONTAL, wxWRAPSIZER_DEFAULT_FLAGS); + auto *linear_gradient_mode_row = new wxWrapSizer(wxHORIZONTAL, wxWRAPSIZER_DEFAULT_FLAGS); + linear_gradient_mode_row->Add(new wxStaticText(editor, wxID_ANY, _L("Gradient mode")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + wxArrayString linear_gradient_mode_choices; + linear_gradient_mode_choices.Add(_L("Linear")); + linear_gradient_mode_choices.Add(_L("Radial")); + auto *linear_gradient_mode_choice = make_sidebar_combo(editor, + linear_gradient_mode_choices, + linear_gradient_mode_selection(entry), + wxSize(FromDIP(170), -1)); + linear_gradient_mode_row->Add(linear_gradient_mode_choice, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + editor_sizer->Add(linear_gradient_mode_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + + auto *linear_gradient_row = new wxBoxSizer(wxHORIZONTAL); linear_gradient_row->Add(new wxStaticText(editor, wxID_ANY, _L("Gradient")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); auto *start_filament_combo = make_filament_swatch_combo(editor, selected_ids.empty() ? 1 : selected_ids.front()); auto *end_filament_combo = make_filament_swatch_combo(editor, selected_ids.size() > 1 ? selected_ids[1] : (selected_ids.empty() ? 1 : selected_ids.front())); @@ -6531,9 +6674,15 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) start_filament_combo->Enable(can_choose_linear_gradient_filaments); if (end_filament_combo != nullptr) end_filament_combo->Enable(can_choose_linear_gradient_filaments); - auto *set_start_btn = new wxButton(editor, wxID_ANY, _L("Set Start")); - auto *set_end_btn = new wxButton(editor, wxID_ANY, _L("Set End")); + const bool entry_radial_gradient = linear_gradient_mode_selection(entry) == 1; + auto *set_start_btn = new wxButton(editor, + wxID_ANY, + entry_radial_gradient ? + (entry.linear_gradient_start.valid ? _L("Set Center") : _L("Add Center")) : + (entry.linear_gradient_start.valid ? _L("Set Start") : _L("Add Start"))); + auto *set_end_btn = new wxButton(editor, wxID_ANY, entry.linear_gradient_end.valid ? _L("Set End") : _L("Add End")); auto *clear_points_btn = new wxButton(editor, wxID_ANY, _L("Clear Points")); + clear_points_btn->Enable(entry.linear_gradient_start.valid || entry.linear_gradient_end.valid); ::CheckBox *show_direction_arrow_chk = nullptr; linear_gradient_row->Add(new wxStaticText(editor, wxID_ANY, _L("Start")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); linear_gradient_row->Add(start_filament_combo, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); @@ -6547,6 +6696,33 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) linear_gradient_buttons_row->Add(clear_points_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); editor_sizer->Add(linear_gradient_buttons_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + auto *linear_gradient_radius_row = new wxWrapSizer(wxHORIZONTAL, wxWRAPSIZER_DEFAULT_FLAGS); + linear_gradient_radius_row->Add(new wxStaticText(editor, wxID_ANY, _L("Radius")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + const double linear_gradient_radius_value = entry.linear_gradient_radius_percent ? + std::clamp(double(entry.linear_gradient_radius_pct), 0.0, 100000.0) : + std::clamp(double(entry.linear_gradient_radius_mm), 0.0, 100000.0); + auto *linear_gradient_radius_spin = new wxSpinCtrlDouble(editor, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxSize(FromDIP(96), -1), + wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, + 0.0, + 100000.0, + linear_gradient_radius_value, + 1.0); + linear_gradient_radius_spin->SetDigits(2); + ::CheckBox *linear_gradient_radius_percent_chk = nullptr; + auto *linear_gradient_radius_unit_label = new wxStaticText(editor, wxID_ANY, entry.linear_gradient_radius_percent ? _L("%") : _L("mm")); + linear_gradient_radius_row->Add(linear_gradient_radius_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); + linear_gradient_radius_row->Add(linear_gradient_radius_unit_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + auto *linear_gradient_radius_percent_row = make_sidebar_checkbox_row(editor, + linear_gradient_radius_percent_chk, + _L("Percent"), + entry.linear_gradient_radius_percent); + linear_gradient_radius_row->Add(linear_gradient_radius_percent_row, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + editor_sizer->Add(linear_gradient_radius_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + auto *show_direction_arrow_row = make_sidebar_checkbox_row(editor, show_direction_arrow_chk, _L("Show Direction Arrow"), @@ -6555,24 +6731,40 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) wxWeakRef set_start_btn_ref(set_start_btn); wxWeakRef set_end_btn_ref(set_end_btn); + wxWeakRef clear_points_btn_ref(clear_points_btn); wxWindowRef editor_ref(editor); wxWindowRef row_ref(row); auto set_linear_gradient_picker_button_labels = [this, + zone_index, + mgr_ptr, set_start_btn_ref, set_end_btn_ref, + clear_points_btn_ref, + linear_gradient_mode_choice, editor_ref, row_ref, update_texture_mapping_area_height](GLGizmoTextureGradientPointPicker::Target active_target) { + const TextureMappingZone *zone = mgr_ptr != nullptr && zone_index < mgr_ptr->zones().size() ? &mgr_ptr->zones()[zone_index] : nullptr; + const bool radial_gradient = linear_gradient_mode_choice != nullptr && linear_gradient_mode_choice->GetSelection() == 1; + const bool has_start = zone != nullptr && zone->linear_gradient_start.valid; + const bool has_end = zone != nullptr && zone->linear_gradient_end.valid; if (wxButton *button = set_start_btn_ref.get()) { - button->SetLabel(active_target == GLGizmoTextureGradientPointPicker::Target::Start ? _L("Cancel selection") : _L("Set Start")); + const wxString inactive_label = radial_gradient ? + (has_start ? _L("Set Center") : _L("Add Center")) : + (has_start ? _L("Set Start") : _L("Add Start")); + button->SetLabel(active_target == GLGizmoTextureGradientPointPicker::Target::Start ? _L("Cancel selection") : inactive_label); button->InvalidateBestSize(); button->Fit(); } if (wxButton *button = set_end_btn_ref.get()) { - button->SetLabel(active_target == GLGizmoTextureGradientPointPicker::Target::End ? _L("Cancel selection") : _L("Set End")); + button->SetLabel(active_target == GLGizmoTextureGradientPointPicker::Target::End ? _L("Cancel selection") : (has_end ? _L("Set End") : _L("Add End"))); + button->Show(!radial_gradient); + button->Enable(!radial_gradient); button->InvalidateBestSize(); button->Fit(); } + if (wxButton *button = clear_points_btn_ref.get()) + button->Enable(has_start || has_end); if (wxWindow *window = editor_ref.get()) window->Layout(); if (wxWindow *window = row_ref.get()) @@ -6620,8 +6812,9 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) editor_sizer->Add(button_row, 0, wxEXPAND | wxALL, gap); auto apply_controls = [zone_index, mgr_ptr, num_physical, filament_checks, surface_choice, - start_filament_combo, end_filament_combo, - linear_gradient_filament_ids, surface_pattern_from_selection, mode_choice, preview_colors_chk, + start_filament_combo, end_filament_combo, linear_gradient_mode_choice, linear_gradient_radius_spin, + linear_gradient_radius_percent_chk, + linear_gradient_filament_ids, surface_pattern_from_selection, linear_gradient_mode_from_selection, mode_choice, preview_colors_chk, show_direction_arrow_chk, contrast_spin, apply_zone](int changed_side = 0) { if (mgr_ptr == nullptr) return; @@ -6645,6 +6838,19 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.component_ids = encode_texture_mapping_component_ids(ids); updated.component_a = ids.empty() ? 1 : ids.front(); updated.component_b = ids.size() > 1 ? ids[1] : updated.component_a; + updated.linear_gradient_mode = linear_gradient_mode_from_selection(linear_gradient_mode_choice != nullptr ? + linear_gradient_mode_choice->GetSelection() : + 0); + updated.linear_gradient_radius_percent = linear_gradient_radius_percent_chk != nullptr ? + linear_gradient_radius_percent_chk->GetValue() : + updated.linear_gradient_radius_percent; + if (linear_gradient_radius_spin != nullptr) { + const float radius_value = std::clamp(float(linear_gradient_radius_spin->GetValue()), 0.f, 100000.f); + if (updated.linear_gradient_radius_percent) + updated.linear_gradient_radius_pct = radius_value; + else + updated.linear_gradient_radius_mm = radius_value; + } updated.filament_color_mode = mode_choice != nullptr ? texture_mapping_color_mode_from_selection(mode_choice->GetSelection(), updated.filament_color_mode) : updated.filament_color_mode; @@ -6666,13 +6872,16 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) } evt.Skip(); }); - auto update_pattern_visibility = [this, editor_sizer, filaments_row, linear_gradient_row, linear_gradient_buttons_row, show_direction_arrow_row, mode_row, contrast_row, preview_colors_row, offset_btn, advanced_btn, surface_choice, row, editor, update_texture_mapping_area_height]() { + auto update_pattern_visibility = [this, editor_sizer, filaments_row, linear_gradient_mode_row, linear_gradient_mode_choice, linear_gradient_row, linear_gradient_buttons_row, linear_gradient_radius_row, show_direction_arrow_row, mode_row, contrast_row, preview_colors_row, offset_btn, advanced_btn, surface_choice, row, editor, update_texture_mapping_area_height, set_linear_gradient_picker_button_labels]() { const int selection = surface_choice == nullptr ? 0 : surface_choice->GetSelection(); const bool image_texture = selection == 0; const bool linear_gradient = selection == 1; + const bool radial_gradient = linear_gradient && linear_gradient_mode_choice != nullptr && linear_gradient_mode_choice->GetSelection() == 1; editor_sizer->Show(filaments_row, !linear_gradient, true); + editor_sizer->Show(linear_gradient_mode_row, linear_gradient, true); editor_sizer->Show(linear_gradient_row, linear_gradient, true); editor_sizer->Show(linear_gradient_buttons_row, linear_gradient, true); + editor_sizer->Show(linear_gradient_radius_row, radial_gradient, true); editor_sizer->Show(show_direction_arrow_row, linear_gradient, true); editor_sizer->Show(mode_row, image_texture, true); editor_sizer->Show(contrast_row, image_texture, true); @@ -6681,6 +6890,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) offset_btn->Show(selection == 2); if (advanced_btn != nullptr) advanced_btn->Show(); + set_linear_gradient_picker_button_labels(GLGizmoTextureGradientPointPicker::Target::None); editor->Layout(); row->Layout(); update_texture_mapping_area_height(); @@ -6693,6 +6903,11 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) update_pattern_visibility(); apply_controls(); }); + if (linear_gradient_mode_choice != nullptr) + linear_gradient_mode_choice->Bind(wxEVT_COMBOBOX, [update_pattern_visibility, apply_controls](wxCommandEvent &) { + update_pattern_visibility(); + apply_controls(); + }); for (::CheckBox *chk : filament_checks) if (chk != nullptr) chk->Bind(wxEVT_TOGGLEBUTTON, [apply_controls](wxCommandEvent &evt) { @@ -6712,7 +6927,11 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) filament_checks, start_filament_combo, end_filament_combo, + linear_gradient_mode_choice, + linear_gradient_radius_spin, + linear_gradient_radius_percent_chk, linear_gradient_filament_ids, + linear_gradient_mode_from_selection, mode_choice, surface_choice, preview_colors_chk, @@ -6750,6 +6969,19 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.component_ids = encode_texture_mapping_component_ids(ids); updated.component_a = ids.empty() ? 1 : ids.front(); updated.component_b = ids.size() > 1 ? ids[1] : updated.component_a; + updated.linear_gradient_mode = linear_gradient_mode_from_selection(linear_gradient_mode_choice != nullptr ? + linear_gradient_mode_choice->GetSelection() : + 0); + updated.linear_gradient_radius_percent = linear_gradient_radius_percent_chk != nullptr ? + linear_gradient_radius_percent_chk->GetValue() : + updated.linear_gradient_radius_percent; + if (linear_gradient_radius_spin != nullptr) { + const float radius_value = std::clamp(float(linear_gradient_radius_spin->GetValue()), 0.f, 100000.f); + if (updated.linear_gradient_radius_percent) + updated.linear_gradient_radius_pct = radius_value; + else + updated.linear_gradient_radius_mm = radius_value; + } updated.filament_color_mode = mode_choice != nullptr ? texture_mapping_color_mode_from_selection(mode_choice->GetSelection(), updated.filament_color_mode) : updated.filament_color_mode; @@ -6791,6 +7023,46 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) apply_controls(); evt.Skip(); }); + auto update_linear_gradient_radius_unit = [linear_gradient_radius_percent_chk, linear_gradient_radius_unit_label]() { + if (linear_gradient_radius_unit_label != nullptr) + linear_gradient_radius_unit_label->SetLabel(linear_gradient_radius_percent_chk != nullptr && linear_gradient_radius_percent_chk->GetValue() ? _L("%") : _L("mm")); + }; + if (linear_gradient_radius_percent_chk != nullptr) + linear_gradient_radius_percent_chk->Bind(wxEVT_TOGGLEBUTTON, [zone_index, + mgr_ptr, + linear_gradient_radius_spin, + linear_gradient_radius_percent_chk, + update_linear_gradient_radius_unit, + apply_controls](wxCommandEvent &evt) { + if (linear_gradient_radius_spin != nullptr && mgr_ptr != nullptr && zone_index < mgr_ptr->zones().size()) { + const TextureMappingZone &zone = mgr_ptr->zones()[zone_index]; + const double value = linear_gradient_radius_percent_chk->GetValue() ? + std::clamp(double(zone.linear_gradient_radius_pct), 0.0, 100000.0) : + std::clamp(double(zone.linear_gradient_radius_mm), 0.0, 100000.0); + linear_gradient_radius_spin->SetValue(value); + } + update_linear_gradient_radius_unit(); + apply_controls(); + evt.Skip(); + }); + if (linear_gradient_radius_spin != nullptr) { + linear_gradient_radius_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_controls](wxSpinDoubleEvent &) { apply_controls(); }); + linear_gradient_radius_spin->Bind(wxEVT_TEXT_ENTER, [apply_controls](wxCommandEvent &) { apply_controls(); }); + linear_gradient_radius_spin->Bind(wxEVT_KILL_FOCUS, [apply_controls](wxFocusEvent &evt) { + apply_controls(); + evt.Skip(); + }); + linear_gradient_radius_spin->Bind(wxEVT_CHAR_HOOK, [linear_gradient_radius_spin, apply_controls](wxKeyEvent &evt) { + const int key = evt.GetKeyCode(); + if (key == WXK_UP || key == WXK_NUMPAD_UP || key == WXK_DOWN || key == WXK_NUMPAD_DOWN) { + const double direction = (key == WXK_UP || key == WXK_NUMPAD_UP) ? 1.0 : -1.0; + linear_gradient_radius_spin->SetValue(std::clamp(linear_gradient_radius_spin->GetValue() + direction, 0.0, 100000.0)); + apply_controls(); + return; + } + evt.Skip(); + }); + } contrast_spin->Bind(wxEVT_SPINCTRL, [apply_controls](wxCommandEvent &) { apply_controls(); }); contrast_spin->Bind(wxEVT_TEXT_ENTER, [apply_controls](wxCommandEvent &) { apply_controls(); }); contrast_spin->Bind(wxEVT_KILL_FOCUS, [apply_controls](wxFocusEvent &evt) { @@ -6859,9 +7131,12 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) apply_linear_gradient_pick, make_linear_gradient_anchor, set_config_string, + linear_gradient_mode_choice, set_linear_gradient_picker_button_labels](bool start_point) { if (p->plater == nullptr) return; + if (!start_point && linear_gradient_mode_choice != nullptr && linear_gradient_mode_choice->GetSelection() == 1) + return; GLCanvas3D *canvas = p->plater->get_current_canvas3D(true); if (canvas == nullptr) return; @@ -6884,6 +7159,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) if (mgr_ptr == nullptr || zone_index >= mgr_ptr->zones().size()) return; const TextureMappingZone original = mgr_ptr->zones()[zone_index]; + picker->set_radial_mode(original.linear_gradient_mode == int(TextureMappingZone::LinearGradientRadial)); const std::string original_serialized = serialize_texture_mapping_manager(mgr_ptr); TextureMappingZone active_preview = original; active_preview.surface_pattern = int(TextureMappingZone::LinearGradient);