diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index ba52f6787b..f8a42247be 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -3834,7 +3834,7 @@ int CLI::run(int argc, char **argv) return; } - std::vector extruders = plate->get_extruders_under_cli(true, print_config); + std::vector extruders = plate->get_wipe_tower_extruders_under_cli(true, print_config); unsigned int filaments_cnt = extruders.size(); if ((filaments_cnt <= 1) && !is_smooth_timelapse){ plate_obj_size_info.has_wipe_tower = false; @@ -4603,6 +4603,8 @@ int CLI::run(int argc, char **argv) Model& model = m_models[0]; arrange_cfg = ArrangeParams(); // reset all params get_print_sequence(cur_plate, m_print_config, arrange_cfg.is_seq_print); + const size_t wipe_tower_filaments_count = cur_plate->get_wipe_tower_extruders_under_cli(true, m_print_config).size(); + const size_t arrange_filaments_count = std::max(assemble_plate.filaments_count, wipe_tower_filaments_count); //Step-1: prepare the arranged data partplate_list.lock_plate(i, false); @@ -4623,7 +4625,8 @@ int CLI::run(int argc, char **argv) } } - if (!arrange_cfg.is_seq_print && (assemble_plate.filaments_count > 1)||(enable_wrapping_detect && !current_wrapping_exclude_area.empty())) + if ((!arrange_cfg.is_seq_print && arrange_filaments_count > 1) || + (enable_wrapping_detect && !current_wrapping_exclude_area.empty())) { //prepare the wipe tower int plate_count = partplate_list.get_plate_count(); @@ -4663,7 +4666,13 @@ int CLI::run(int argc, char **argv) wipe_y_option->set_at(&wt_y_opt, i, 0); Vec3d wipe_tower_size, wipe_tower_pos; - ArrangePolygon wipe_tower_ap = cur_plate->estimate_wipe_tower_polygon(m_print_config, i, wipe_tower_pos, wipe_tower_size, new_extruder_count, assemble_plate.filaments_count, true); + ArrangePolygon wipe_tower_ap = cur_plate->estimate_wipe_tower_polygon(m_print_config, + i, + wipe_tower_pos, + wipe_tower_size, + new_extruder_count, + int(arrange_filaments_count), + true); //update the new wp position wt_x_opt.value = wipe_tower_pos(0); @@ -4767,8 +4776,10 @@ int CLI::run(int argc, char **argv) bool is_seq_print = false; get_print_sequence(cur_plate, m_print_config, is_seq_print); + const size_t wipe_tower_filaments_count = cur_plate->get_wipe_tower_extruders_under_cli(true, m_print_config).size(); + const size_t arrange_filaments_count = std::max(assemble_plate.filaments_count, wipe_tower_filaments_count); - if (!is_seq_print && (assemble_plate.filaments_count > 1) && !has_wipe_tower_position) + if (!is_seq_print && arrange_filaments_count > 1 && !has_wipe_tower_position) { //prepare the wipe tower auto printer_structure_opt = m_print_config.option>("printer_structure"); @@ -4960,8 +4971,16 @@ int CLI::run(int argc, char **argv) wipe_y_option->set_at(&wt_y_opt, plate_index_valid, 0); } + Slic3r::GUI::PartPlate* plate = partplate_list.get_plate(plate_index_valid); + int plate_extruder_size = std::max(extruder_size, int(plate->get_wipe_tower_extruders_under_cli(true, m_print_config).size())); Vec3d wipe_tower_size, wipe_tower_pos; - ArrangePolygon wipe_tower_ap = partplate_list.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(m_print_config, plate_index_valid, wipe_tower_pos, wipe_tower_size, new_extruder_count, extruder_size, true); + ArrangePolygon wipe_tower_ap = plate->estimate_wipe_tower_polygon(m_print_config, + plate_index_valid, + wipe_tower_pos, + wipe_tower_size, + new_extruder_count, + plate_extruder_size, + true); //update the new wp position if (bedid < plate_count) { @@ -5044,7 +5063,7 @@ int CLI::run(int argc, char **argv) if ((filaments_cnt == 0) || need_skip) { // slice filaments info invalid - std::vector extruders = cur_plate->get_extruders_under_cli(true, m_print_config); + std::vector extruders = cur_plate->get_wipe_tower_extruders_under_cli(true, m_print_config); filaments_cnt = extruders.size(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange: slice filaments info invalid or need_skip, get from partplate: filament_count %1%")%filaments_cnt; } diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 85699a20e8..c5db889ccb 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -238,6 +238,7 @@ const std::string BBS_PROJECT_CONFIG_FILE = "Metadata/project_settings.config"; const std::string BBS_MODEL_CONFIG_FILE = "Metadata/model_settings.config"; const std::string BBS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.config.rels"; const std::string PRIME_TOWER_TEXTURE_FILE = "Metadata/texture_mapping/prime_tower_image.png"; +const std::string PRIME_TOWER_TEXTURE_FILE_BACK = "Metadata/texture_mapping/prime_tower_image_back.png"; const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config"; const std::string BBS_LAYER_HEIGHTS_PROFILE_FILE = "Metadata/layer_heights_profile.txt"; const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/layer_config_ranges.xml"; @@ -3620,6 +3621,7 @@ static void append_triangle_material_data(std::vector &uv_valid, Model &model) { model.texture_mapping_prime_tower_image.clear(); + model.texture_mapping_prime_tower_image_back.clear(); const ConfigOptionString *settings_opt = config.option("texture_mapping_global_settings"); if (settings_opt == nullptr || settings_opt->value.empty()) @@ -3627,27 +3629,37 @@ static void append_triangle_material_data(std::vector &uv_valid, TextureMappingGlobalSettings settings; settings.load(settings_opt->value); - if (settings.image_file.empty()) + if (settings.image_file.empty() && settings.image_file_back.empty()) return; - std::vector image_payload; - if (!try_extract_file_from_archive(archive, settings.image_file, image_payload)) { - BOOST_LOG_TRIVIAL(warning) << "3MF prime tower texture payload missing for image='" << settings.image_file << "'"; - return; - } + auto restore_image = [&archive](const std::string &image_file, + const std::string &image_name, + TextureMappingPrimeTowerImage &target) { + if (image_file.empty()) + return; - std::vector imported_rgba; - uint32_t imported_width = 0; - uint32_t imported_height = 0; - if (!decode_png_rgba_from_memory(image_payload, imported_rgba, imported_width, imported_height)) { - BOOST_LOG_TRIVIAL(warning) << "3MF prime tower texture image decode failed for image='" << settings.image_file << "'"; - return; - } + std::vector image_payload; + if (!try_extract_file_from_archive(archive, image_file, image_payload)) { + BOOST_LOG_TRIVIAL(warning) << "3MF prime tower texture payload missing for image='" << image_file << "'"; + return; + } - model.texture_mapping_prime_tower_image.rgba = std::move(imported_rgba); - model.texture_mapping_prime_tower_image.width = imported_width; - model.texture_mapping_prime_tower_image.height = imported_height; - model.texture_mapping_prime_tower_image.image_name = settings.image_name; + std::vector imported_rgba; + uint32_t imported_width = 0; + uint32_t imported_height = 0; + if (!decode_png_rgba_from_memory(image_payload, imported_rgba, imported_width, imported_height)) { + BOOST_LOG_TRIVIAL(warning) << "3MF prime tower texture image decode failed for image='" << image_file << "'"; + return; + } + + target.rgba = std::move(imported_rgba); + target.width = imported_width; + target.height = imported_height; + target.image_name = image_name; + }; + + restore_image(settings.image_file, settings.image_name, model.texture_mapping_prime_tower_image); + restore_image(settings.image_file_back, settings.image_name_back, model.texture_mapping_prime_tower_image_back); } /* @@ -8973,20 +8985,29 @@ static void append_triangle_material_data(std::vector &uv_valid, TextureMappingGlobalSettings settings; if (const ConfigOptionString *opt = config_copy.option("texture_mapping_global_settings")) settings.load(opt->value); + settings.clear_image_reference(); if (model.texture_mapping_prime_tower_image.valid()) { settings.image_file = PRIME_TOWER_TEXTURE_FILE; settings.image_name = model.texture_mapping_prime_tower_image.image_name; settings.image_width = model.texture_mapping_prime_tower_image.width; settings.image_height = model.texture_mapping_prime_tower_image.height; } + if (model.texture_mapping_prime_tower_image_back.valid()) { + settings.image_file_back = PRIME_TOWER_TEXTURE_FILE_BACK; + settings.image_name_back = model.texture_mapping_prime_tower_image_back.image_name; + settings.image_width_back = model.texture_mapping_prime_tower_image_back.width; + settings.image_height_back = model.texture_mapping_prime_tower_image_back.height; + } config_copy.set_key_value("texture_mapping_global_settings", new ConfigOptionString(settings.serialize())); config_copy.save_to_json(temp_file, std::string("project_settings"), std::string("project"), std::string(SLIC3R_VERSION)); return _add_file_to_archive(archive, BBS_PROJECT_CONFIG_FILE, temp_file); } - bool _BBS_3MF_Exporter::_add_prime_tower_texture_file_to_archive(mz_zip_archive& archive, const Model& model) + static bool add_prime_tower_texture_file_to_archive(mz_zip_archive& archive, + const TextureMappingPrimeTowerImage &image, + const std::string &image_file, + std::string &error_message) { - const TextureMappingPrimeTowerImage &image = model.texture_mapping_prime_tower_image; if (!image.valid()) return true; @@ -8999,18 +9020,40 @@ static void append_triangle_material_data(std::vector &uv_valid, MZ_DEFAULT_COMPRESSION, 1); if (png_data == nullptr) { - add_error("Unable to encode prime tower texture image"); + error_message = "Unable to encode prime tower texture image"; return false; } const bool added_png = mz_zip_writer_add_mem(&archive, - PRIME_TOWER_TEXTURE_FILE.c_str(), + image_file.c_str(), png_data, png_size, MZ_NO_COMPRESSION); mz_free(png_data); if (!added_png) { - add_error("Unable to add prime tower texture image file to archive"); + error_message = "Unable to add prime tower texture image file to archive"; + return false; + } + return true; + } + + bool _BBS_3MF_Exporter::_add_prime_tower_texture_file_to_archive(mz_zip_archive& archive, const Model& model) + { + std::string error_message; + const bool added_front = add_prime_tower_texture_file_to_archive(archive, + model.texture_mapping_prime_tower_image, + PRIME_TOWER_TEXTURE_FILE, + error_message); + if (!added_front) { + add_error(error_message); + return false; + } + const bool added_back = add_prime_tower_texture_file_to_archive(archive, + model.texture_mapping_prime_tower_image_back, + PRIME_TOWER_TEXTURE_FILE_BACK, + error_message); + if (!added_back) { + add_error(error_message); return false; } return true; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 699a471f50..2be60aa04e 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1320,6 +1320,25 @@ static float prime_tower_texture_anchor_distance(const std::vector &point return best_projection > -std::numeric_limits::max() ? best_distance : fallback_distance; } +static float prime_tower_texture_anchor_angle(const std::vector &points, float angle_deg) +{ + if (points.empty()) + return std::clamp(angle_deg, 0.f, 360.f); + + Vec2f min_pt(std::numeric_limits::max(), std::numeric_limits::max()); + Vec2f max_pt(std::numeric_limits::lowest(), std::numeric_limits::lowest()); + for (const Vec2f &point : points) { + min_pt.x() = std::min(min_pt.x(), point.x()); + min_pt.y() = std::min(min_pt.y(), point.y()); + max_pt.x() = std::max(max_pt.x(), point.x()); + max_pt.y() = std::max(max_pt.y(), point.y()); + } + float angle = std::clamp(angle_deg, 0.f, 360.f); + if (max_pt.y() - min_pt.y() > max_pt.x() - min_pt.x() + EPSILON) + angle += 90.f; + return angle >= 360.f ? angle - 360.f : angle; +} + static PrimeTowerPreparedTexturePath prime_tower_prepare_texture_path(WipeTowerWriter &writer, const std::vector &points, const Vec2f ¢er, @@ -1350,7 +1369,8 @@ static PrimeTowerPreparedTexturePath prime_tower_prepare_texture_path(WipeTowerW sample_points.reserve(unique_points.size()); for (const Vec2f &point : unique_points) sample_points.emplace_back(writer.point_rotated(point)); - const float anchor_distance = prime_tower_texture_anchor_distance(sample_points, sample_points.size(), center, angle_deg); + const float anchor_distance = + prime_tower_texture_anchor_distance(sample_points, sample_points.size(), center, prime_tower_texture_anchor_angle(sample_points, angle_deg)); return {std::move(unique_points), std::move(sample_points), anchor_distance}; } diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index ee9689090b..a4d8de988f 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -49,14 +49,16 @@ struct PrimeTowerTextureRenderSettings std::vector image_rgba; unsigned int image_width = 0; unsigned int image_height = 0; + std::vector image_rgba_back; + unsigned int image_width_back = 0; + unsigned int image_height_back = 0; std::vector filament_colours; float z_min = 0.f; float z_max = 0.f; bool valid() const { - return enabled && image_width > 0 && image_height > 0 && - image_rgba.size() >= size_t(image_width) * size_t(image_height) * 4; + return enabled && (image_valid(false) || image_valid(true)); } float sample_tool_visibility(size_t tool, float u, float v) const @@ -65,18 +67,47 @@ struct PrimeTowerTextureRenderSettings return 1.f; u -= std::floor(u); v = std::clamp(v, 0.f, 1.f); - const float x = u * float(image_width); - const float y = (1.f - v) * float(image_height - 1); - const unsigned int ix = std::min(image_width - 1, unsigned(std::floor(x))); - const unsigned int iy = std::min(image_height - 1, unsigned(std::floor(y))); - const size_t offset = (size_t(iy) * size_t(image_width) + size_t(ix)) * 4; - const float r = float(image_rgba[offset + 0]) / 255.f; - const float g = float(image_rgba[offset + 1]) / 255.f; - const float b = float(image_rgba[offset + 2]) / 255.f; - return color_mode == GenericSolver || color_mode == Auto ? generic_visibility(tool, r, g, b) : fixed_mode_visibility(tool, r, g, b); + const bool front_valid = image_valid(false); + const bool back_valid = image_valid(true); + bool use_back = false; + if (front_valid && back_valid) { + use_back = u >= 0.5f; + u = use_back ? (u - 0.5f) * 2.f : u * 2.f; + } else { + use_back = back_valid; + } + return sample_image_tool_visibility(tool, u, v, use_back); } private: + bool image_valid(bool back) const + { + return back ? + image_width_back > 0 && image_height_back > 0 && + image_rgba_back.size() >= size_t(image_width_back) * size_t(image_height_back) * 4 : + image_width > 0 && image_height > 0 && + image_rgba.size() >= size_t(image_width) * size_t(image_height) * 4; + } + + float sample_image_tool_visibility(size_t tool, float u, float v, bool back) const + { + const std::vector &rgba = back ? image_rgba_back : image_rgba; + const unsigned int width = back ? image_width_back : image_width; + const unsigned int height = back ? image_height_back : image_height; + if (width == 0 || height == 0 || rgba.size() < size_t(width) * size_t(height) * 4) + return 1.f; + + const float x = u * float(width); + const float y = (1.f - v) * float(height - 1); + const unsigned int ix = std::min(width - 1, unsigned(std::floor(x))); + const unsigned int iy = std::min(height - 1, unsigned(std::floor(y))); + const size_t offset = (size_t(iy) * size_t(width) + size_t(ix)) * 4; + const float r = float(rgba[offset + 0]) / 255.f; + const float g = float(rgba[offset + 1]) / 255.f; + const float b = float(rgba[offset + 2]) / 255.f; + return color_mode == GenericSolver || color_mode == Auto ? generic_visibility(tool, r, g, b) : fixed_mode_visibility(tool, r, g, b); + } + static std::array parse_color(const std::string &hex) { auto hex_byte = [](char hi, char lo) { diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index 6a79b70801..bcc3e44763 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -1304,6 +1304,25 @@ static float prime_tower_texture_anchor_distance(const std::vector& point return best_projection > -std::numeric_limits::max() ? best_distance : fallback_distance; } +static float prime_tower_texture_anchor_angle(const std::vector& points, float angle_deg) +{ + if (points.empty()) + return std::clamp(angle_deg, 0.f, 360.f); + + Vec2f min_pt(std::numeric_limits::max(), std::numeric_limits::max()); + Vec2f max_pt(std::numeric_limits::lowest(), std::numeric_limits::lowest()); + for (const Vec2f& point : points) { + min_pt.x() = std::min(min_pt.x(), point.x()); + min_pt.y() = std::min(min_pt.y(), point.y()); + max_pt.x() = std::max(max_pt.x(), point.x()); + max_pt.y() = std::max(max_pt.y(), point.y()); + } + float angle = std::clamp(angle_deg, 0.f, 360.f); + if (max_pt.y() - min_pt.y() > max_pt.x() - min_pt.x() + EPSILON) + angle += 90.f; + return angle >= 360.f ? angle - 360.f : angle; +} + static PrimeTowerPreparedTexturePath prime_tower_prepare_texture_path(WipeTowerWriter2& writer, const std::vector& points, bool closed_path, @@ -1323,7 +1342,10 @@ static PrimeTowerPreparedTexturePath prime_tower_prepare_texture_path(WipeTowerW sample_points.reserve(points.size()); for (const Vec2f& point : points) sample_points.emplace_back(writer.point_rotated(point)); - return {points, sample_points, prime_tower_texture_anchor_distance(sample_points, sample_points.size() - 1, center, angle_deg)}; + return {points, + sample_points, + prime_tower_texture_anchor_distance( + sample_points, sample_points.size() - 1, center, prime_tower_texture_anchor_angle(sample_points, angle_deg))}; } std::vector unique_points(points.begin(), points.end()); @@ -1347,7 +1369,9 @@ static PrimeTowerPreparedTexturePath prime_tower_prepare_texture_path(WipeTowerW sample_points.reserve(ordered.size()); for (const Vec2f& point : ordered) sample_points.emplace_back(writer.point_rotated(point)); - return {ordered, sample_points, prime_tower_texture_anchor_distance(sample_points, segment_count, center, angle_deg)}; + return {ordered, + sample_points, + prime_tower_texture_anchor_distance(sample_points, segment_count, center, prime_tower_texture_anchor_angle(sample_points, angle_deg))}; } static void prime_tower_textured_path(WipeTowerWriter2& writer, diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f3cc6d0889..9ff9eb697a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -580,6 +580,7 @@ Model& Model::assign_copy(const Model &rhs) this->md_name = rhs.md_name; this->md_value = rhs.md_value; this->texture_mapping_prime_tower_image = rhs.texture_mapping_prime_tower_image; + this->texture_mapping_prime_tower_image_back = rhs.texture_mapping_prime_tower_image_back; return *this; } @@ -616,6 +617,7 @@ Model& Model::assign_copy(Model &&rhs) this->md_name = rhs.md_name; this->md_value = rhs.md_value; this->texture_mapping_prime_tower_image = std::move(rhs.texture_mapping_prime_tower_image); + this->texture_mapping_prime_tower_image_back = std::move(rhs.texture_mapping_prime_tower_image_back); this->backup_path = std::move(rhs.backup_path); this->object_backup_id_map = std::move(rhs.object_backup_id_map); this->next_object_backup_id = rhs.next_object_backup_id; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 155558d1ac..472a3fed15 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1777,6 +1777,7 @@ public: std::vector md_name; std::vector md_value; TextureMappingPrimeTowerImage texture_mapping_prime_tower_image; + TextureMappingPrimeTowerImage texture_mapping_prime_tower_image_back; void SetDesigner(std::string designer, std::string designer_user_id) { if (design_info == nullptr) { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 41339ce448..e94774af17 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -3348,7 +3348,7 @@ void Print::_make_wipe_tower() auto build_prime_tower_texture = [this]() { PrimeTowerTextureRenderSettings texture; - if (!m_texture_mapping_global_settings.effective_enabled(m_texture_mapping_prime_tower_image)) + if (!m_texture_mapping_global_settings.effective_enabled(m_texture_mapping_prime_tower_image, m_texture_mapping_prime_tower_image_back)) return texture; const std::string mode = TextureMappingGlobalSettings::normalize_color_mode_name( @@ -3382,6 +3382,9 @@ void Print::_make_wipe_tower() texture.image_rgba = m_texture_mapping_prime_tower_image.rgba; texture.image_width = m_texture_mapping_prime_tower_image.width; texture.image_height = m_texture_mapping_prime_tower_image.height; + texture.image_rgba_back = m_texture_mapping_prime_tower_image_back.rgba; + texture.image_width_back = m_texture_mapping_prime_tower_image_back.width; + texture.image_height_back = m_texture_mapping_prime_tower_image_back.height; texture.filament_colours = m_config.filament_colour.values; texture.filament_colours.resize(m_config.filament_diameter.values.size(), "#FFFFFF"); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 24a196e3c4..35a00f6232 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -948,6 +948,7 @@ public: TextureMappingManager& texture_mapping_manager() { return m_texture_mapping_mgr; } const TextureMappingGlobalSettings& texture_mapping_global_settings() const { return m_texture_mapping_global_settings; } const TextureMappingPrimeTowerImage& texture_mapping_prime_tower_image() const { return m_texture_mapping_prime_tower_image; } + const TextureMappingPrimeTowerImage& texture_mapping_prime_tower_image_back() const { return m_texture_mapping_prime_tower_image_back; } const PrintObjectConfig& default_object_config() const { return m_default_object_config; } const PrintRegionConfig& default_region_config() const { return m_default_region_config; } ConstPrintObjectPtrsAdaptor objects() const { return ConstPrintObjectPtrsAdaptor(&m_objects); } @@ -1135,6 +1136,7 @@ private: TextureMappingManager m_texture_mapping_mgr; TextureMappingGlobalSettings m_texture_mapping_global_settings; TextureMappingPrimeTowerImage m_texture_mapping_prime_tower_image; + TextureMappingPrimeTowerImage m_texture_mapping_prime_tower_image_back; PrintObjectConfig m_default_object_config; PrintRegionConfig m_default_region_config; PrintObjectPtrs m_objects; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index c8e7113558..bfe8f51601 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1242,6 +1242,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_texture_mapping_mgr.load_entries(new_full_config.opt_string("texture_mapping_definitions"), color_opt->values); m_texture_mapping_global_settings.load(new_full_config.opt_string("texture_mapping_global_settings")); m_texture_mapping_prime_tower_image = model.texture_mapping_prime_tower_image; + m_texture_mapping_prime_tower_image_back = model.texture_mapping_prime_tower_image_back; // The following call may stop the background processing. if (! print_diff.empty()) diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 9e4e7ca70c..cc53edc60c 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -547,7 +547,11 @@ std::string TextureMappingGlobalSettings::serialize() const image_file.empty() && image_name.empty() && image_width == 0 && - image_height == 0) + image_height == 0 && + image_file_back.empty() && + image_name_back.empty() && + image_width_back == 0 && + image_height_back == 0) return {}; nlohmann::json root; @@ -560,6 +564,10 @@ std::string TextureMappingGlobalSettings::serialize() const prime_tower_texture_mapping["image_name"] = image_name; prime_tower_texture_mapping["image_width"] = image_width; prime_tower_texture_mapping["image_height"] = image_height; + prime_tower_texture_mapping["image_file_back"] = image_file_back; + prime_tower_texture_mapping["image_name_back"] = image_name_back; + prime_tower_texture_mapping["image_width_back"] = image_width_back; + prime_tower_texture_mapping["image_height_back"] = image_height_back; root["prime_tower_texture_mapping"] = std::move(prime_tower_texture_mapping); return root.dump(); } @@ -592,6 +600,10 @@ void TextureMappingGlobalSettings::load(const std::string &serialized) image_name = prime_tower_texture_mapping.value("image_name", std::string()); image_width = prime_tower_texture_mapping.value("image_width", 0u); image_height = prime_tower_texture_mapping.value("image_height", 0u); + image_file_back = prime_tower_texture_mapping.value("image_file_back", std::string()); + image_name_back = prime_tower_texture_mapping.value("image_name_back", std::string()); + image_width_back = prime_tower_texture_mapping.value("image_width_back", 0u); + image_height_back = prime_tower_texture_mapping.value("image_height_back", 0u); } void TextureMappingGlobalSettings::clear_image_reference() @@ -600,6 +612,10 @@ void TextureMappingGlobalSettings::clear_image_reference() image_name.clear(); image_width = 0; image_height = 0; + image_file_back.clear(); + image_name_back.clear(); + image_width_back = 0; + image_height_back = 0; } std::string TextureMappingGlobalSettings::normalize_color_mode_name(const std::string &mode) diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index 292735f508..fe9b8955ee 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -214,9 +214,21 @@ struct TextureMappingGlobalSettings std::string image_name; unsigned int image_width = 0; unsigned int image_height = 0; + std::string image_file_back; + std::string image_name_back; + unsigned int image_width_back = 0; + unsigned int image_height_back = 0; - bool has_image_reference() const { return !image_file.empty() || image_width > 0 || image_height > 0; } + bool has_image_reference() const + { + return !image_file.empty() || image_width > 0 || image_height > 0 || + !image_file_back.empty() || image_width_back > 0 || image_height_back > 0; + } bool effective_enabled(const TextureMappingPrimeTowerImage &image) const { return enabled && image.valid(); } + bool effective_enabled(const TextureMappingPrimeTowerImage &image, const TextureMappingPrimeTowerImage &image_back) const + { + return enabled && (image.valid() || image_back.valid()); + } std::string serialize() const; void load(const std::string &serialized); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 04a5332bb1..3dd0484022 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -835,12 +835,298 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } +static constexpr float prime_tower_preview_epsilon = 1e-6f; +static constexpr float prime_tower_preview_offset = 0.001f; + +float prime_tower_preview_anchor_distance(const std::array &points, const Vec2f ¢er, float angle_deg) +{ + float travelled = 0.f; + float fallback_distance = 0.f; + float fallback_dist = std::numeric_limits::max(); + float best_distance = std::numeric_limits::max(); + float best_projection = -std::numeric_limits::max(); + const float angle = angle_deg * float(M_PI / 180.); + const Vec2f ray_dir(std::cos(angle), std::sin(angle)); + + for (size_t i = 0; i < points.size(); ++i) { + const Vec2f a = points[i]; + const Vec2f b = points[(i + 1) % points.size()]; + const Vec2f delta = b - a; + const float len = delta.norm(); + if (len <= prime_tower_preview_epsilon) + continue; + + const Vec2f from_center = a - center; + const float denom = ray_dir.x() * delta.y() - ray_dir.y() * delta.x(); + if (std::abs(denom) > prime_tower_preview_epsilon) { + const float projection = (from_center.x() * delta.y() - from_center.y() * delta.x()) / denom; + const float segment_t = (from_center.x() * ray_dir.y() - from_center.y() * ray_dir.x()) / denom; + if (projection >= -prime_tower_preview_epsilon && + segment_t >= -prime_tower_preview_epsilon && + segment_t <= 1.f + prime_tower_preview_epsilon && + projection > best_projection) { + best_projection = projection; + best_distance = travelled + len * std::clamp(segment_t, 0.f, 1.f); + } + } else if (std::abs(from_center.x() * ray_dir.y() - from_center.y() * ray_dir.x()) <= prime_tower_preview_epsilon) { + const float projection_a = from_center.dot(ray_dir); + const float projection_b = (b - center).dot(ray_dir); + if (projection_a >= -prime_tower_preview_epsilon || projection_b >= -prime_tower_preview_epsilon) { + const bool use_b = projection_b > projection_a; + const float projection = use_b ? projection_b : projection_a; + if (projection > best_projection) { + best_projection = projection; + best_distance = travelled + (use_b ? len : 0.f); + } + } + } + + const float fallback_t = std::clamp((center - a).dot(delta) / (len * len), 0.f, 1.f); + const Vec2f fallback_point = a + delta * fallback_t - center; + const float projection = fallback_point.dot(ray_dir); + const float perpendicular = fallback_point.x() * ray_dir.y() - fallback_point.y() * ray_dir.x(); + const float fallback_score = perpendicular * perpendicular + (projection < 0.f ? projection * projection : 0.f); + if (fallback_score < fallback_dist - prime_tower_preview_epsilon) { + fallback_dist = fallback_score; + fallback_distance = travelled + len * fallback_t; + } + travelled += len; + } + + return best_projection > -std::numeric_limits::max() ? best_distance : fallback_distance; +} + +float prime_tower_preview_texture_v(float z, float texture_z_min, float texture_z_max) +{ + const float v = texture_z_max > texture_z_min + prime_tower_preview_epsilon ? + std::clamp((z - texture_z_min) / (texture_z_max - texture_z_min), 0.f, 1.f) : + 0.f; + return 1.f - v; +} + +float prime_tower_preview_anchor_angle(const std::array &points, float angle_deg) +{ + Vec2f min_pt(std::numeric_limits::max(), std::numeric_limits::max()); + Vec2f max_pt(std::numeric_limits::lowest(), std::numeric_limits::lowest()); + for (const Vec2f &point : points) { + min_pt.x() = std::min(min_pt.x(), point.x()); + min_pt.y() = std::min(min_pt.y(), point.y()); + max_pt.x() = std::max(max_pt.x(), point.x()); + max_pt.y() = std::max(max_pt.y(), point.y()); + } + float angle = std::clamp(angle_deg, 0.f, 360.f); + if (max_pt.y() - min_pt.y() > max_pt.x() - min_pt.x() + prime_tower_preview_epsilon) + angle += 90.f; + return angle >= 360.f ? angle - 360.f : angle; +} + +GUI::GLModel::Geometry prime_tower_image_preview_geometry(float width, + float depth, + float height, + float angle_offset_deg, + float texture_z_min, + float texture_z_max, + int image_slot) +{ + GUI::GLModel::Geometry data; + data.format = {GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3N3T2}; + + if (width <= prime_tower_preview_epsilon || depth <= prime_tower_preview_epsilon || height <= prime_tower_preview_epsilon) + return data; + + const std::array points = {Vec2f(0.f, 0.f), Vec2f(width, 0.f), Vec2f(width, depth), Vec2f(0.f, depth)}; + const std::array distances = {0.f, width, width + depth, 2.f * width + depth}; + const float total_length = 2.f * (width + depth); + const float anchor_distance = prime_tower_preview_anchor_distance(points, + Vec2f(width * 0.5f, depth * 0.5f), + prime_tower_preview_anchor_angle(points, angle_offset_deg)); + + std::vector z_levels = {0.f, height}; + if (texture_z_min > prime_tower_preview_epsilon && texture_z_min < height - prime_tower_preview_epsilon) + z_levels.emplace_back(texture_z_min); + if (texture_z_max > prime_tower_preview_epsilon && texture_z_max < height - prime_tower_preview_epsilon) + z_levels.emplace_back(texture_z_max); + std::sort(z_levels.begin(), z_levels.end()); + z_levels.erase(std::unique(z_levels.begin(), z_levels.end(), [](float lhs, float rhs) { + return std::abs(lhs - rhs) <= prime_tower_preview_epsilon; + }), z_levels.end()); + + data.reserve_vertices(16 * points.size() * (z_levels.size() - 1)); + data.reserve_indices(24 * points.size() * (z_levels.size() - 1)); + + auto texture_u = [anchor_distance, total_length, image_slot](float distance, float mid_distance) { + const float raw_u = (distance - anchor_distance) / total_length; + const float mid_raw_u = (mid_distance - anchor_distance) / total_length; + const float base = std::floor(mid_raw_u); + if (image_slot == 0) + return std::clamp(raw_u - base, 0.f, 1.f); + return image_slot == 1 ? + std::clamp(2.f * (raw_u - base), 0.f, 1.f) : + std::clamp(2.f * (raw_u - base - 0.5f), 0.f, 1.f); + }; + + for (size_t side_idx = 0; side_idx < points.size(); ++side_idx) { + const size_t next_idx = (side_idx + 1) % points.size(); + const Vec2f a = points[side_idx]; + const Vec2f b = points[next_idx]; + const Vec2f delta = b - a; + const float len = delta.norm(); + if (len <= prime_tower_preview_epsilon) + continue; + + const Vec2f dir = delta / len; + const Vec2f outward(dir.y(), -dir.x()); + const Vec3f normal(outward.x(), outward.y(), 0.f); + const float side_start = distances[side_idx]; + const float side_end = next_idx == 0 ? total_length : distances[next_idx]; + + std::vector cuts = {side_start, side_end}; + const int first = int(std::floor((side_start - anchor_distance) / total_length)) - 1; + const int last = int(std::ceil((side_end - anchor_distance) / total_length)) + 1; + for (int wrap = first; wrap <= last; ++wrap) { + const float wrap_cut = anchor_distance + total_length * float(wrap); + if (wrap_cut > side_start + prime_tower_preview_epsilon && wrap_cut < side_end - prime_tower_preview_epsilon) + cuts.emplace_back(wrap_cut); + if (image_slot != 0) { + const float half_cut = anchor_distance + total_length * (float(wrap) + 0.5f); + if (half_cut > side_start + prime_tower_preview_epsilon && half_cut < side_end - prime_tower_preview_epsilon) + cuts.emplace_back(half_cut); + } + } + std::sort(cuts.begin(), cuts.end()); + cuts.erase(std::unique(cuts.begin(), cuts.end(), [](float lhs, float rhs) { + return std::abs(lhs - rhs) <= prime_tower_preview_epsilon; + }), cuts.end()); + + for (size_t cut_idx = 0; cut_idx + 1 < cuts.size(); ++cut_idx) { + const float d0 = cuts[cut_idx]; + const float d1 = cuts[cut_idx + 1]; + if (d1 - d0 <= prime_tower_preview_epsilon) + continue; + + if (image_slot != 0) { + const float mid_raw_u = (0.5f * (d0 + d1) - anchor_distance) / total_length; + const float mid_u = mid_raw_u - std::floor(mid_raw_u); + if ((mid_u >= 0.5f ? 2 : 1) != image_slot) + continue; + } + + const float t0 = std::clamp((d0 - side_start) / len, 0.f, 1.f); + const float t1 = std::clamp((d1 - side_start) / len, 0.f, 1.f); + const Vec2f p0 = a + delta * t0 + outward * prime_tower_preview_offset; + const Vec2f p1 = a + delta * t1 + outward * prime_tower_preview_offset; + const float mid_distance = 0.5f * (d0 + d1); + const float u0 = texture_u(d0, mid_distance); + const float u1 = texture_u(d1, mid_distance); + + for (size_t z_idx = 0; z_idx + 1 < z_levels.size(); ++z_idx) { + const float z0 = z_levels[z_idx]; + const float z1 = z_levels[z_idx + 1]; + const float v0 = prime_tower_preview_texture_v(z0, texture_z_min, texture_z_max); + const float v1 = prime_tower_preview_texture_v(z1, texture_z_min, texture_z_max); + const unsigned int base = unsigned(data.vertices_count()); + + data.add_vertex(Vec3f(p0.x(), p0.y(), z0), normal, Vec2f(u0, v0)); + data.add_vertex(Vec3f(p1.x(), p1.y(), z0), normal, Vec2f(u1, v0)); + data.add_vertex(Vec3f(p1.x(), p1.y(), z1), normal, Vec2f(u1, v1)); + data.add_vertex(Vec3f(p0.x(), p0.y(), z1), normal, Vec2f(u0, v1)); + data.add_triangle(base, base + 1, base + 2); + data.add_triangle(base, base + 2, base + 3); + } + } + } + + return data; +} + +void set_prime_tower_preview_uniforms(GLShaderProgram &shader, + const Transform3d &model_matrix, + const Transform3d &view_matrix, + const Transform3d &projection_matrix, + const std::array &z_range, + const std::array &clipping_plane, + int print_volume_type, + const std::array &print_volume_xy, + const std::array &print_volume_z) +{ + const Transform3d view_model_matrix = view_matrix * model_matrix; + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * + model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("projection_matrix", projection_matrix); + shader.set_uniform("view_normal_matrix", view_normal_matrix); + shader.set_uniform("volume_world_matrix", model_matrix); + shader.set_uniform("z_range", z_range); + shader.set_uniform("clipping_plane", clipping_plane); + shader.set_uniform("print_volume.type", print_volume_type); + shader.set_uniform("print_volume.xy_data", print_volume_xy); + shader.set_uniform("print_volume.z_data", print_volume_z); +} + GLWipeTowerVolume::GLWipeTowerVolume(const std::vector& colors) : GLVolume() { m_colors = colors; } +void GLWipeTowerVolume::set_prime_tower_image_preview(std::vector image_rgba, + unsigned int image_width, + unsigned int image_height, + std::vector image_rgba_back, + unsigned int image_width_back, + unsigned int image_height_back, + float angle_offset_deg, + float width, + float depth, + float height, + float texture_z_min, + float texture_z_max) +{ + auto reset_image = [](PrimeTowerPreviewImage &image) { + image.model.reset(); + image.texture.reset(); + image.rgba.clear(); + image.width = 0; + image.height = 0; + }; + auto valid_image = [](const std::vector &rgba, unsigned int width, unsigned int height) { + return width > 0 && height > 0 && rgba.size() >= size_t(width) * size_t(height) * 4; + }; + auto assign_image = [&](PrimeTowerPreviewImage &target, + std::vector rgba, + unsigned int image_w, + unsigned int image_h, + int image_slot) { + if (!valid_image(rgba, image_w, image_h)) + return; + + GUI::GLModel::Geometry image_geometry = + prime_tower_image_preview_geometry(width, depth, height, angle_offset_deg, texture_z_min, texture_z_max, image_slot); + if (image_geometry.is_empty()) + return; + + target.rgba = std::move(rgba); + target.width = image_w; + target.height = image_h; + target.model.init_from(std::move(image_geometry)); + }; + + reset_image(m_prime_tower_image); + reset_image(m_prime_tower_image_back); + + const bool front_valid = valid_image(image_rgba, image_width, image_height); + const bool back_valid = valid_image(image_rgba_back, image_width_back, image_height_back); + if (front_valid && back_valid) { + assign_image(m_prime_tower_image, std::move(image_rgba), image_width, image_height, 1); + assign_image(m_prime_tower_image_back, std::move(image_rgba_back), image_width_back, image_height_back, 2); + } else if (front_valid) { + assign_image(m_prime_tower_image, std::move(image_rgba), image_width, image_height, 0); + } else if (back_valid) { + assign_image(m_prime_tower_image_back, std::move(image_rgba_back), image_width_back, image_height_back, 0); + } +} + void GLWipeTowerVolume::render() { if (!is_active) @@ -867,7 +1153,106 @@ void GLWipeTowerVolume::render() glFrontFace(GL_CCW); } +void GLWipeTowerVolume::render_prime_tower_image_preview(const Transform3d& view_matrix, + const Transform3d& projection_matrix, + const std::array& z_range, + const std::array& clipping_plane, + int print_volume_type, + const std::array& print_volume_xy, + const std::array& print_volume_z) +{ + if (!is_active || picking || (!m_prime_tower_image.model.is_initialized() && !m_prime_tower_image_back.model.is_initialized())) + return; + + GLShaderProgram *shader = GUI::wxGetApp().get_shader("painted_texture_preview"); + if (shader == nullptr) + return; + + GLboolean blend_enabled = glIsEnabled(GL_BLEND); + GLboolean cull_face_enabled = glIsEnabled(GL_CULL_FACE); + GLboolean depth_mask = GL_TRUE; + GLint cull_face_mode = GL_BACK; + GLint depth_func = GL_LESS; + glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &depth_mask)); + glsafe(::glGetIntegerv(GL_CULL_FACE_MODE, &cull_face_mode)); + glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &depth_func)); + + if (this->is_left_handed()) + glFrontFace(GL_CW); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glCullFace(GL_BACK)); + glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glDepthFunc(GL_LEQUAL)); + + shader->start_using(); + set_prime_tower_preview_uniforms(*shader, + world_matrix(), + view_matrix, + projection_matrix, + z_range, + clipping_plane, + print_volume_type, + print_volume_xy, + print_volume_z); + + glsafe(::glActiveTexture(GL_TEXTURE0)); + shader->set_uniform("uniform_texture", 0); + shader->set_uniform("texture_preview_mix", 1.f); + shader->set_uniform("invalid_texture_mapping", false); + + auto render_image = [](PrimeTowerPreviewImage &image) { + if (!image.model.is_initialized()) + return; + if (image.texture.get_id() == 0) { + if (image.width == 0 || image.height == 0 || image.rgba.empty()) + return; + + std::vector texture_data = image.rgba; + if (!image.texture.load_from_raw_data(std::move(texture_data), image.width, image.height)) + return; + + glsafe(::glBindTexture(GL_TEXTURE_2D, image.texture.get_id())); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + image.rgba.clear(); + } + + glsafe(::glBindTexture(GL_TEXTURE_2D, image.texture.get_id())); + image.model.set_color(ColorRGBA(1.f, 1.f, 1.f, 0.82f)); + image.model.render(); + }; + + render_image(m_prime_tower_image); + render_image(m_prime_tower_image_back); + + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + shader->stop_using(); + + glsafe(::glDepthFunc(depth_func)); + glsafe(::glDepthMask(depth_mask)); + glsafe(::glCullFace(cull_face_mode)); + if (cull_face_enabled) + glsafe(::glEnable(GL_CULL_FACE)); + else + glsafe(::glDisable(GL_CULL_FACE)); + if (blend_enabled) + glsafe(::glEnable(GL_BLEND)); + else + glsafe(::glDisable(GL_BLEND)); + + if (this->is_left_handed()) + glFrontFace(GL_CCW); +} + bool GLWipeTowerVolume::IsTransparent() { + if (m_prime_tower_image.model.is_initialized() || m_prime_tower_image_back.model.is_initialized()) + return true; + for (size_t i = 0; i < m_colors.size(); i++) { if (m_colors[i].is_transparent()) { return true; @@ -995,7 +1380,7 @@ void GLVolumeCollection::load_object_auxiliary( int GLVolumeCollection::load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, - float rotation_angle, bool size_unknown, float brim_width) + float rotation_angle, bool size_unknown, float brim_width, float texture_z_min, float texture_z_max) { int plate_idx = obj_idx - 1000; @@ -1007,7 +1392,7 @@ int GLVolumeCollection::load_wipe_tower_preview( std::vector extruder_colors = GUI::wxGetApp().plater()->get_extruders_colors(); std::vector colors; GUI::PartPlateList& ppl = GUI::wxGetApp().plater()->get_partplate_list(); - std::vector plate_extruders = ppl.get_plate(plate_idx)->get_extruders(true); + std::vector plate_extruders = ppl.get_plate(plate_idx)->get_wipe_tower_extruders(true); TriangleMesh wipe_tower_shell = make_cube(width, depth, height); for (int extruder_id : plate_extruders) { if (extruder_id <= extruder_colors.size()) @@ -1027,6 +1412,28 @@ int GLVolumeCollection::load_wipe_tower_preview( color_part.translate({ 0.f, depth * i / colors.size(), 0. }); v.model_per_colors[i].init_from(color_part); } + const TextureMappingGlobalSettings *texture_mapping_global_settings = GUI::wxGetApp().preset_bundle != nullptr ? + &GUI::wxGetApp().preset_bundle->texture_mapping_global_settings : + nullptr; + const TextureMappingPrimeTowerImage &prime_tower_image = GUI::wxGetApp().model().texture_mapping_prime_tower_image; + const TextureMappingPrimeTowerImage &prime_tower_image_back = GUI::wxGetApp().model().texture_mapping_prime_tower_image_back; + if (texture_mapping_global_settings != nullptr && + texture_mapping_global_settings->effective_enabled(prime_tower_image, prime_tower_image_back)) { + std::vector texture_data(prime_tower_image.rgba.begin(), prime_tower_image.rgba.end()); + std::vector texture_data_back(prime_tower_image_back.rgba.begin(), prime_tower_image_back.rgba.end()); + v.set_prime_tower_image_preview(std::move(texture_data), + prime_tower_image.width, + prime_tower_image.height, + std::move(texture_data_back), + prime_tower_image_back.width, + prime_tower_image_back.height, + texture_mapping_global_settings->angle_offset_deg, + width, + depth, + height, + texture_z_min, + texture_z_max); + } v.model.init_from(wipe_tower_shell); v.mesh_raycaster = std::make_unique(std::make_shared(wipe_tower_shell)); v.set_convex_hull(wipe_tower_shell); @@ -1264,6 +1671,20 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, else volume.first->render(); + if (GLWipeTowerVolume *wipe_tower_volume = dynamic_cast(volume.first)) { + const int texture_preview_print_volume_type = + volume.first->partly_inside && partly_inside_enable ? static_cast(m_print_volume.type) : -1; + shader->stop_using(); + wipe_tower_volume->render_prime_tower_image_preview(view_matrix, + projection_matrix, + m_z_range, + m_clipping_plane, + texture_preview_print_volume_type, + m_print_volume.data, + m_print_volume.zs); + shader->start_using(); + } + #if ENABLE_ENVIRONMENT_MAP if (use_environment_texture) glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index c384b8002f..1f72b3b2b6 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -389,12 +389,42 @@ public: GLWipeTowerVolume(const std::vector& colors); void render() override; void render_with_outline(const GUI::Size& cnv_size) override { render(); } + void set_prime_tower_image_preview(std::vector image_rgba, + unsigned int image_width, + unsigned int image_height, + std::vector image_rgba_back, + unsigned int image_width_back, + unsigned int image_height_back, + float angle_offset_deg, + float width, + float depth, + float height, + float texture_z_min, + float texture_z_max); + void render_prime_tower_image_preview(const Transform3d& view_matrix, + const Transform3d& projection_matrix, + const std::array& z_range, + const std::array& clipping_plane, + int print_volume_type, + const std::array& print_volume_xy, + const std::array& print_volume_z); std::vector model_per_colors; bool IsTransparent(); private: + struct PrimeTowerPreviewImage + { + GUI::GLModel model; + GUI::GLTexture texture; + std::vector rgba; + unsigned int width { 0 }; + unsigned int height { 0 }; + }; + std::vector m_colors; + PrimeTowerPreviewImage m_prime_tower_image; + PrimeTowerPreviewImage m_prime_tower_image_back; }; typedef std::vector GLVolumePtrs; @@ -493,8 +523,17 @@ public: // Timestamp of the last change of the milestone size_t timestamp); - int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); + int load_wipe_tower_preview(int obj_idx, + float pos_x, + float pos_y, + float width, + float depth, + float height, + float rotation_angle, + bool size_unknown, + float brim_width, + float texture_z_min = 0.f, + float texture_z_max = 0.f); int load_real_wipe_tower_preview( int obj_idx, float pos_x, float pos_y,const TriangleMesh& wt_mesh,const TriangleMesh &brim_mesh,bool render_brim, float rotation_angle, bool size_unknown, bool opengl_initialized); GLVolume* new_toolpath_volume(const ColorRGBA& rgba); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index eeba37733c..7bb45e4e95 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2800,7 +2800,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // BBS if (printer_technology == ptFFF && m_config->has("filament_colour") && (m_canvas_type != ECanvasType::CanvasAssembleView)) { // Should the wipe tower be visualized ? - unsigned int filaments_count = (unsigned int)dynamic_cast(m_config->option("filament_colour"))->values.size(); + unsigned int config_filaments_count = (unsigned int)dynamic_cast(m_config->option("filament_colour"))->values.size(); bool wt = dynamic_cast(m_config->option("enable_prime_tower"))->value; auto co = dynamic_cast*>(m_config->option>("print_sequence")); @@ -2813,7 +2813,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re need_wipe_tower |= dynamic_cast(dconfig.option("enable_wrapping_detection"))->value; } - if (wt && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) { + if (wt && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) { for (int plate_id = 0; plate_id < n_plates; plate_id++) { // If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated. PartPlate* part_plate = ppl.get_plate(plate_id); @@ -2834,13 +2834,25 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const Print* print = m_process->fff_print(); const Print* current_print = part_plate->fff_print(); - if (!need_wipe_tower && part_plate->get_extruders(true).size() < 2) continue; + const std::vector wipe_tower_extruders = part_plate->get_wipe_tower_extruders(true); + const size_t wipe_tower_filaments_count = need_wipe_tower ? + std::max(1, wipe_tower_extruders.size()) : + wipe_tower_extruders.size(); + if (!need_wipe_tower && wipe_tower_filaments_count < 2) continue; if (part_plate->get_objects_on_this_plate().empty()) continue; - float brim_width = print->wipe_tower_data(filaments_count).brim_width; + float brim_width = print->wipe_tower_data(wipe_tower_filaments_count > 0 ? wipe_tower_filaments_count : size_t(config_filaments_count)).brim_width; const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count(); - Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 0, false, dynamic_cast(dconfig.option("enable_wrapping_detection"))->value); + Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(print_cfg, + w, + v, + nozzle_nums, + int(wipe_tower_filaments_count), + false, + dynamic_cast(dconfig.option("enable_wrapping_detection"))->value); + const float texture_z_min = 0.f; + const float texture_z_max = float(wipe_tower_size(2)); { const float margin = WIPE_TOWER_MARGIN + brim_width; @@ -2863,7 +2875,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1), (float) wipe_tower_size(0), (float) wipe_tower_size(1), (float) wipe_tower_size(2), a, - /*!print->is_step_done(psWipeTower)*/ true, brim_width); + /*!print->is_step_done(psWipeTower)*/ true, brim_width, texture_z_min, texture_z_max); int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id]; if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 05bdcded21..37f212d2de 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -358,6 +358,8 @@ static int texture_mapping_depth_for_budget(size_t triangle_count, int requested static constexpr float TRUE_COLOR_BRUSH_SUBDIVISION_FRACTION = 1.f / 8.f; static constexpr float TRUE_COLOR_BRUSH_MIN_SUBDIVISION_EDGE_MM = 0.1f; +static constexpr float IMAGE_PROJECTION_RGB_TARGET_TRIANGLE_IMAGE_FRACTION = 1.f / 128.f; +static constexpr float IMAGE_PROJECTION_RGB_MIN_TARGET_TRIANGLE_IMAGE_PX = 2.f; static float true_color_brush_subdivision_target(float brush_radius) { @@ -1453,6 +1455,54 @@ static bool projection_triangle_intersects_overlay(const ProjectionContext min_y <= context.overlay_top + context.overlay_height; } +static bool project_point_to_image_pixel(const ProjectionContext &context, + const Vec3d &world_point, + Vec2f &image_pixel) +{ + if (context.overlay_width <= EPSILON || + context.overlay_height <= EPSILON || + context.image_width == 0 || + context.image_height == 0) + return false; + + Vec2f screen = Vec2f::Zero(); + if (!project_point_to_screen(context, world_point, screen)) + return false; + + image_pixel.x() = ((screen.x() - context.overlay_left) / context.overlay_width) * float(context.image_width); + image_pixel.y() = ((screen.y() - context.overlay_top) / context.overlay_height) * float(context.image_height); + return std::isfinite(image_pixel.x()) && std::isfinite(image_pixel.y()); +} + +static float projection_triangle_image_pixel_span(const ProjectionContext &context, + const Transform3d &world_matrix, + const std::array &vertices) +{ + std::array image_pixels; + size_t projected_count = 0; + for (const Vec3f &vertex : vertices) { + Vec2f image_pixel = Vec2f::Zero(); + if (project_point_to_image_pixel(context, world_matrix * vertex.cast(), image_pixel)) + image_pixels[projected_count++] = image_pixel; + } + + if (projected_count < 2) + return 0.f; + + float span = 0.f; + for (size_t i = 0; i + 1 < projected_count; ++i) + for (size_t j = i + 1; j < projected_count; ++j) + span = std::max(span, (image_pixels[i] - image_pixels[j]).norm()); + return span; +} + +static float image_projection_rgb_target_triangle_pixel_span(const ProjectionContext &context) +{ + const float image_span = float(std::max(context.image_width, context.image_height)); + return std::max(image_span * IMAGE_PROJECTION_RGB_TARGET_TRIANGLE_IMAGE_FRACTION, + IMAGE_PROJECTION_RGB_MIN_TARGET_TRIANGLE_IMAGE_PX); +} + static bool barycentric_weights_2d(const Vec2f &point, const Vec2f &a, const Vec2f &b, const Vec2f &c, Vec3f &weights) { const Vec2f v0 = b - a; @@ -6686,6 +6736,9 @@ bool GLGizmoImageProjection::project_to_rgb_data(ModelObject *object) const ColorRGBA fallback_color = projection_base_color_for_volume(*volume); const Transform3d world_matrix = projection_world_matrix_for_volume(m_parent, object, volume, instance_idx); std::vector projected_triangles(its.indices.size(), false); + std::vector projected_triangle_depths(its.indices.size(), 0); + const float projection_target_span = image_projection_rgb_target_triangle_pixel_span(context); + size_t projected_triangle_count = 0; for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) { const stl_triangle_vertex_indices &tri = its.indices[tri_idx]; @@ -6701,7 +6754,15 @@ bool GLGizmoImageProjection::project_to_rgb_data(ModelObject *object) its.vertices[size_t(tri[1])].cast(), its.vertices[size_t(tri[2])].cast() }; - projected_triangles[tri_idx] = projection_triangle_intersects_overlay(context, world_matrix, vertices); + const bool projected_triangle = projection_triangle_intersects_overlay(context, world_matrix, vertices); + projected_triangles[tri_idx] = projected_triangle; + if (projected_triangle) { + projected_triangle_depths[tri_idx] = + texture_mapping_depth_from_span(projection_triangle_image_pixel_span(context, world_matrix, vertices), + projection_target_span, + 7); + ++projected_triangle_count; + } } TextureMappingColorSampler sampler = [this, volume, source, context, world_matrix, fallback_color, &projected_triangles, &visibility](size_t tri_idx, @@ -6721,19 +6782,63 @@ bool GLGizmoImageProjection::project_to_rgb_data(ModelObject *object) }; const float mesh_span = mesh_max_axis_span(its); - const int safe_max_depth = texture_mapping_depth_for_budget(its.indices.size(), 7, 2200000); + const int background_safe_max_depth = texture_mapping_depth_for_budget(its.indices.size(), 7, 2200000); + const int projected_safe_max_depth = projected_triangle_count == 0 ? + background_safe_max_depth : + texture_mapping_depth_for_budget(projected_triangle_count, 7, 2200000); + const int safe_max_depth = std::max(background_safe_max_depth, projected_safe_max_depth); const float split_threshold = safe_max_depth < 5 ? 0.018f : 0.012f; TextureMappingColorSubdivisionDepths subdivision_depths = - [volume, mesh_span, safe_max_depth, &projected_triangles](size_t tri_idx, const std::array &vertices) { - int base_depth = model_volume_has_bakeable_image_texture_data(volume) ? - texture_mapping_depth_from_span(texture_triangle_uv_pixel_span(volume, tri_idx), 8.f, safe_max_depth) : - texture_mapping_depth_from_span(triangle_max_edge_length(vertices), std::max(mesh_span / 180.f, 0.18f), std::min(6, safe_max_depth)); - if (tri_idx < projected_triangles.size() && projected_triangles[tri_idx]) - base_depth = std::min(std::max(base_depth, 4), safe_max_depth); - return std::make_pair(base_depth, safe_max_depth); + [volume, + mesh_span, + background_safe_max_depth, + projected_safe_max_depth, + &projected_triangles, + &projected_triangle_depths](size_t tri_idx, const std::array &vertices) { + const bool projected_triangle = tri_idx < projected_triangles.size() && projected_triangles[tri_idx]; + const int triangle_max_depth = projected_triangle ? projected_safe_max_depth : background_safe_max_depth; + int depth = 0; + if (model_volume_has_bakeable_image_texture_data(volume)) { + depth = texture_mapping_depth_from_span(texture_triangle_uv_pixel_span(volume, tri_idx), 8.f, triangle_max_depth); + } else if (!projected_triangle) { + depth = texture_mapping_depth_from_span(triangle_max_edge_length(vertices), + std::max(mesh_span / 180.f, 0.18f), + std::min(6, triangle_max_depth)); + } + if (projected_triangle && tri_idx < projected_triangle_depths.size()) + depth = std::max(depth, std::min(projected_triangle_depths[tri_idx], triangle_max_depth)); + depth = std::clamp(depth, 0, triangle_max_depth); + return std::make_pair(depth, depth); }; - volume->texture_mapping_color_facets.set_from_triangle_sampler(*volume, sampler, safe_max_depth, split_threshold, subdivision_depths); + TextureMappingColorLeafResamplePredicate resample_leaf = + [this, context, world_matrix, &visibility, &projected_triangles](size_t tri_idx, + const std::array &vertices, + const std::array &, + uint32_t) { + if (tri_idx >= projected_triangles.size() || !projected_triangles[tri_idx]) + return false; + if (!projection_triangle_intersects_overlay(context, world_matrix, vertices)) + return false; + if (m_pass_through_model) + return true; + + const Vec3f centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.f; + if (projection_point_is_visible(visibility, context, world_matrix, centroid)) + return true; + for (const Vec3f &vertex : vertices) + if (projection_point_is_visible(visibility, context, world_matrix, vertex)) + return true; + return false; + }; + + volume->texture_mapping_color_facets.set_from_triangle_sampler(*volume, + sampler, + safe_max_depth, + split_threshold, + subdivision_depths, + &projected_triangles, + resample_leaf); if (volume->texture_mapping_color_facets.metadata_json().empty()) volume->texture_mapping_color_facets.set_metadata_json(rgb_metadata_json(ColorRGBA(1.f, 1.f, 1.f, 1.f))); changed = true; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2e43f0271e..e62e6e9d19 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -291,6 +291,7 @@ void ArrangeJob::prepare_wipe_tower() bool enable_prime_tower = op && op->getBool(); if (!enable_prime_tower || params.is_seq_print) return; + PartPlateList& ppl = wxGetApp().plater()->get_partplate_list(); bool smooth_timelapse = false; auto sop = current_config.option("timelapse_type"); if (sop) { smooth_timelapse = sop->getInt() == TimelapseType::tlSmooth; } @@ -322,6 +323,19 @@ void ArrangeJob::prepare_wipe_tower() } } } + if (!need_wipe_tower) { + std::set wipe_tower_extruders; + if (only_on_partplate) { + auto plate_extruders = ppl.get_curr_plate()->get_wipe_tower_extruders(true); + wipe_tower_extruders.insert(plate_extruders.begin(), plate_extruders.end()); + } else { + wipe_tower_extruders = ppl.get_wipe_tower_extruders(true); + } + if (wipe_tower_extruders.size() > 1) { + need_wipe_tower = true; + BOOST_LOG_TRIVIAL(info) << "arrange: need wipe tower because virtual filament assignment expands to multiple physical filaments"; + } + } BOOST_LOG_TRIVIAL(info) << "arrange: need_wipe_tower=" << need_wipe_tower; @@ -332,10 +346,9 @@ void ArrangeJob::prepare_wipe_tower() const GLCanvas3D* canvas3D = static_cast(m_plater->canvas3D()); std::set extruder_ids; - PartPlateList& ppl = wxGetApp().plater()->get_partplate_list(); int plate_count = ppl.get_plate_count(); if (!only_on_partplate) { - extruder_ids = ppl.get_extruders(true); + extruder_ids = ppl.get_wipe_tower_extruders(true); } int bedid_unlocked = 0; @@ -352,7 +365,7 @@ void ArrangeJob::prepare_wipe_tower() } else if (need_wipe_tower) { if (only_on_partplate) { - auto plate_extruders = pl->get_extruders(true); + auto plate_extruders = pl->get_wipe_tower_extruders(true); extruder_ids.clear(); extruder_ids.insert(plate_extruders.begin(), plate_extruders.end()); } diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 342abb3162..31c62dbd59 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -22,6 +22,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/TextureMapping.hpp" #include "libslic3r/Utils.hpp" #include "I18N.hpp" @@ -76,6 +77,99 @@ namespace GUI { class Bed3D; +std::vector wipe_tower_filament_colours(const DynamicPrintConfig *config) +{ + auto normalize_count = [](std::vector colours) { + const int filaments_count = std::max(wxGetApp().filaments_cnt(), 0); + if (filaments_count > 0) + colours.resize(size_t(filaments_count), "#FFFFFF"); + return colours; + }; + + if (config != nullptr) { + if (const ConfigOptionStrings *opt = dynamic_cast(config->option("filament_colour")); + opt != nullptr && !opt->values.empty()) + return normalize_count(opt->values); + } + + if (wxGetApp().preset_bundle != nullptr) { + if (const ConfigOptionStrings *opt = + dynamic_cast(wxGetApp().preset_bundle->project_config.option("filament_colour")); + opt != nullptr && !opt->values.empty()) + return normalize_count(opt->values); + } + + const int filaments_count = std::max(wxGetApp().filaments_cnt(), 0); + return std::vector(size_t(filaments_count), "#FFFFFF"); +} + +void append_physical_wipe_tower_extruder(std::vector &out, unsigned int filament_id, size_t num_physical) +{ + if (filament_id >= 1 && filament_id <= num_physical) + out.emplace_back(int(filament_id)); +} + +bool append_texture_wipe_tower_extruders(std::vector &out, + const TextureMappingManager &texture_mgr, + unsigned int filament_id, + size_t num_physical, + const std::vector &filament_colours) +{ + const TextureMappingZone *zone = texture_mgr.zone_from_id(filament_id); + if (zone == nullptr) + return false; + + std::vector component_ids = zone->is_image_texture() ? + TextureMappingManager::effective_texture_component_ids(*zone, num_physical, filament_colours) : + TextureMappingManager::selected_component_ids(*zone, num_physical); + if (component_ids.empty()) + component_ids = TextureMappingManager::selected_component_ids(*zone, num_physical); + + const size_t size_before = out.size(); + for (const unsigned int component_id : component_ids) + append_physical_wipe_tower_extruder(out, component_id, num_physical); + if (out.size() == size_before) + append_physical_wipe_tower_extruder(out, 1, num_physical); + return true; +} + +std::vector expand_wipe_tower_extruders(const std::vector &extruders, const DynamicPrintConfig *config) +{ + if (extruders.empty()) + return extruders; + + const std::vector filament_colours = wipe_tower_filament_colours(config); + const size_t num_physical = filament_colours.size(); + if (num_physical == 0) + return extruders; + + TextureMappingManager texture_mgr; + const std::string texture_mapping_definitions = config != nullptr && config->has("texture_mapping_definitions") ? + config->opt_string("texture_mapping_definitions") : + (wxGetApp().preset_bundle != nullptr && wxGetApp().preset_bundle->project_config.has("texture_mapping_definitions") ? + wxGetApp().preset_bundle->project_config.opt_string("texture_mapping_definitions") : + std::string()); + texture_mgr.load_entries(texture_mapping_definitions, filament_colours); + + std::vector expanded; + expanded.reserve(extruders.size()); + for (const int extruder : extruders) { + if (extruder <= 0) + continue; + + const unsigned int filament_id = static_cast(extruder); + if (filament_id <= num_physical) { + expanded.emplace_back(extruder); + } else if (!append_texture_wipe_tower_extruders(expanded, texture_mgr, filament_id, num_physical, filament_colours)) { + expanded.emplace_back(extruder); + } + } + + std::sort(expanded.begin(), expanded.end()); + expanded.erase(std::unique(expanded.begin(), expanded.end()), expanded.end()); + return expanded; +} + ColorRGBA PartPlate::SELECT_COLOR = { 0.2666f, 0.2784f, 0.2784f, 1.0f }; //{ 0.4196f, 0.4235f, 0.4235f, 1.0f }; ColorRGBA PartPlate::UNSELECT_COLOR = { 0.82f, 0.82f, 0.82f, 1.0f }; ColorRGBA PartPlate::UNSELECT_DARK_COLOR = { 0.384f, 0.384f, 0.412f, 1.0f }; @@ -1630,6 +1724,12 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode) const return plate_extruders; } +std::vector PartPlate::get_wipe_tower_extruders(bool conside_custom_gcode) const +{ + const DynamicPrintConfig *config = wxGetApp().preset_bundle != nullptr ? &wxGetApp().preset_bundle->project_config : nullptr; + return expand_wipe_tower_extruders(get_extruders(conside_custom_gcode), config); +} + std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const { std::vector plate_extruders; @@ -1760,6 +1860,11 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D return plate_extruders; } +std::vector PartPlate::get_wipe_tower_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const +{ + return expand_wipe_tower_extruders(get_extruders_under_cli(conside_custom_gcode, full_config), &full_config); +} + bool PartPlate::check_objects_empty_and_gcode3mf(std::vector &result) const { if (m_model->objects.empty()) {//objects is empty @@ -2134,7 +2239,7 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con // empty plate if (plate_extruder_size == 0) { - std::vector plate_extruders = get_extruders(true); + std::vector plate_extruders = get_wipe_tower_extruders(true); plate_extruder_size = plate_extruders.size(); } if (plate_extruder_size == 0) @@ -4688,6 +4793,19 @@ std::set PartPlateList::get_extruders(bool conside_custom_gcode) const return extruder_ids; } +std::set PartPlateList::get_wipe_tower_extruders(bool conside_custom_gcode) const +{ + int plate_count = get_plate_count(); + std::set extruder_ids; + + for (size_t i = 0; i < plate_count; i++) { + auto plate_extruders = m_plate_list[i]->get_wipe_tower_extruders(conside_custom_gcode); + extruder_ids.insert(plate_extruders.begin(), plate_extruders.end()); + } + + return extruder_ids; +} + //select plate int PartPlateList::select_plate(int index) diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 4b41cc6722..611060d893 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -334,7 +334,9 @@ public: bool check_objects_empty_and_gcode3mf(std::vector &result) const; // get used filaments from config, 1 based idx std::vector get_extruders(bool conside_custom_gcode = false) const; + std::vector get_wipe_tower_extruders(bool conside_custom_gcode = false) const; std::vector get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const; + std::vector get_wipe_tower_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const; std::vector get_extruders_without_support(bool conside_custom_gcode = false) const; // get used filaments from gcode result, 1 based idx std::vector get_used_filaments(); @@ -763,6 +765,7 @@ public: Pointfs get_wrapping_exclude_area() const { return m_wrapping_exclude_areas; } std::set get_extruders(bool conside_custom_gcode = false) const; + std::set get_wipe_tower_extruders(bool conside_custom_gcode = false) const; //select plate int select_plate(int index); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bcc22976f8..d74e671288 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -956,10 +956,12 @@ public: int generic_solver_lookup_mode, const TextureMappingGlobalSettings &global_settings, const TextureMappingPrimeTowerImage &prime_tower_image, + const TextureMappingPrimeTowerImage &prime_tower_image_back, int initial_options_tab) : wxDialog(parent, wxID_ANY, _L("Texture Mapping Options"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , m_global_settings(global_settings) , m_prime_tower_image(prime_tower_image) + , m_prime_tower_image_back(prime_tower_image_back) { const int gap = FromDIP(8); auto *root = new wxBoxSizer(wxVERTICAL); @@ -970,7 +972,7 @@ public: tab_choices.Add(_L("Filament Calibration")); tab_choices.Add(_L("Preview Options")); tab_choices.Add(_L("Experimental Options")); - tab_choices.Add(_L("Global Settings")); + 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, 4)); tab_row->Add(m_options_tab_choice, 1, wxALIGN_CENTER_VERTICAL); @@ -1135,15 +1137,30 @@ public: m_prime_tower_mapping_enabled_checkbox->SetValue(m_global_settings.enabled); global_root->Add(m_prime_tower_mapping_enabled_checkbox, 0, wxEXPAND | wxALL, gap); - auto *prime_image_box = new wxStaticBoxSizer(wxVERTICAL, global_page, _L("Prime Tower Image")); - m_prime_tower_image_label = new wxStaticText(global_page, wxID_ANY, wxEmptyString); - prime_image_box->Add(m_prime_tower_image_label, 0, wxEXPAND | wxALL, gap); - auto *prime_image_buttons = new wxBoxSizer(wxHORIZONTAL); - auto *load_prime_image_button = new wxButton(global_page, wxID_ANY, _L("Load Image")); - auto *clear_prime_image_button = new wxButton(global_page, wxID_ANY, _L("Clear Image")); - prime_image_buttons->Add(load_prime_image_button, 0, wxRIGHT, gap); - prime_image_buttons->Add(clear_prime_image_button, 0); - prime_image_box->Add(prime_image_buttons, 0, wxLEFT | wxRIGHT | wxBOTTOM, gap); + auto *prime_image_box = new wxStaticBoxSizer(wxVERTICAL, global_page, _L("Prime Tower Images")); + wxButton *load_prime_image_button = nullptr; + wxButton *clear_prime_image_button = nullptr; + wxButton *load_prime_image_back_button = nullptr; + wxButton *clear_prime_image_back_button = nullptr; + auto add_prime_image_row = [&](const wxString &title, + wxStaticText *&label, + wxButton *&load_button, + wxButton *&clear_button) { + auto *row = new wxBoxSizer(wxHORIZONTAL); + auto *title_label = new wxStaticText(global_page, wxID_ANY, title); + title_label->SetMinSize(wxSize(FromDIP(44), -1)); + label = new wxStaticText(global_page, wxID_ANY, wxEmptyString); + label->SetMinSize(wxSize(FromDIP(120), -1)); + load_button = new wxButton(global_page, wxID_ANY, _L("Load...")); + clear_button = new wxButton(global_page, wxID_ANY, _L("Clear")); + row->Add(title_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + row->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + row->Add(load_button, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(2), gap / 2)); + row->Add(clear_button, 0, wxALIGN_CENTER_VERTICAL); + prime_image_box->Add(row, 0, wxEXPAND | wxALL, gap); + }; + add_prime_image_row(_L("Front"), m_prime_tower_image_label, load_prime_image_button, clear_prime_image_button); + add_prime_image_row(_L("Back"), m_prime_tower_image_back_label, load_prime_image_back_button, clear_prime_image_back_button); global_root->Add(prime_image_box, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); auto *angle_row = new wxBoxSizer(wxHORIZONTAL); @@ -1181,10 +1198,22 @@ public: color_mode_row->Add(m_prime_tower_color_mode_choice, 1, wxALIGN_CENTER_VERTICAL); global_root->Add(color_mode_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); - load_prime_image_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { load_prime_tower_image(); }); + load_prime_image_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { load_prime_tower_image(false); }); clear_prime_image_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { m_prime_tower_image.clear(); - m_global_settings.clear_image_reference(); + m_global_settings.image_file.clear(); + m_global_settings.image_name.clear(); + m_global_settings.image_width = 0; + m_global_settings.image_height = 0; + update_prime_tower_image_label(); + }); + load_prime_image_back_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { load_prime_tower_image(true); }); + clear_prime_image_back_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { + m_prime_tower_image_back.clear(); + m_global_settings.image_file_back.clear(); + m_global_settings.image_name_back.clear(); + m_global_settings.image_width_back = 0; + m_global_settings.image_height_back = 0; update_prime_tower_image_label(); }); update_prime_tower_image_label(); @@ -1193,7 +1222,7 @@ public: m_options_book->AddPage(filament_page, _L("Filament Calibration")); m_options_book->AddPage(preview_page, _L("Preview Options")); m_options_book->AddPage(experimental_page, _L("Experimental Options")); - m_options_book->AddPage(global_page, _L("Global Settings")); + m_options_book->AddPage(global_page, _L("Prime Tower Texture Mapping")); m_options_book->SetMinSize(wxSize(FromDIP(420), std::max({image_page->GetBestSize().y, filament_page->GetBestSize().y, @@ -1249,16 +1278,31 @@ public: settings.angle_offset_deg = float(std::clamp(m_prime_tower_angle_spin ? m_prime_tower_angle_spin->GetValue() : 0.0, 0.0, 360.0)); settings.prime_tower_color_mode = prime_tower_color_mode_name(m_prime_tower_color_mode_choice ? m_prime_tower_color_mode_choice->GetSelection() : 0); + settings.image_file.clear(); + settings.image_name.clear(); + settings.image_width = 0; + settings.image_height = 0; if (m_prime_tower_image.valid()) { settings.image_file = "Metadata/texture_mapping/prime_tower_image.png"; settings.image_name = m_prime_tower_image.image_name; settings.image_width = m_prime_tower_image.width; settings.image_height = m_prime_tower_image.height; } + settings.image_file_back.clear(); + settings.image_name_back.clear(); + settings.image_width_back = 0; + settings.image_height_back = 0; + if (m_prime_tower_image_back.valid()) { + settings.image_file_back = "Metadata/texture_mapping/prime_tower_image_back.png"; + settings.image_name_back = m_prime_tower_image_back.image_name; + settings.image_width_back = m_prime_tower_image_back.width; + settings.image_height_back = m_prime_tower_image_back.height; + } return settings; } 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, 4); } std::vector component_strengths_pct() const @@ -1311,24 +1355,33 @@ private: void update_prime_tower_image_label() { - if (m_prime_tower_image_label == nullptr) - return; - if (m_prime_tower_image.valid()) { - wxString name = from_u8(m_prime_tower_image.image_name); + auto update_label = [](wxStaticText *label, const TextureMappingPrimeTowerImage &image) { + if (label == nullptr) + return; + if (!image.valid()) { + label->SetLabel(_L("No image loaded")); + label->SetToolTip(wxEmptyString); + return; + } + wxString name = from_u8(image.image_name); if (name.empty()) name = _L("Loaded image"); - m_prime_tower_image_label->SetLabel(wxString::Format("%s (%u x %u)", name.c_str(), m_prime_tower_image.width, m_prime_tower_image.height)); - } else { - m_prime_tower_image_label->SetLabel(_L("No image loaded")); - } + wxString display_name = name; + if (display_name.length() > 34) + display_name = display_name.Left(15) + "..." + display_name.Right(16); + const wxString dimensions = wxString::Format("%u x %u", image.width, image.height); + label->SetLabel(wxString::Format("%s (%s)", display_name.c_str(), dimensions.c_str())); + label->SetToolTip(wxString::Format("%s (%s)", name.c_str(), dimensions.c_str())); + }; + update_label(m_prime_tower_image_label, m_prime_tower_image); + update_label(m_prime_tower_image_back_label, m_prime_tower_image_back); Layout(); - Fit(); } - void load_prime_tower_image() + void load_prime_tower_image(bool back) { wxFileDialog dlg(this, - _L("Load prime tower image"), + back ? _L("Load prime tower back image") : _L("Load prime tower front image"), wxEmptyString, wxEmptyString, _L("Image files (*.png;*.jpg;*.jpeg;*.bmp)|*.png;*.jpg;*.jpeg;*.bmp"), @@ -1354,11 +1407,22 @@ private: } loaded.image_name = boost::filesystem::path(into_u8(dlg.GetPath())).filename().string(); - m_prime_tower_image = std::move(loaded); - m_global_settings.image_file = "Metadata/texture_mapping/prime_tower_image.png"; - m_global_settings.image_name = m_prime_tower_image.image_name; - m_global_settings.image_width = m_prime_tower_image.width; - m_global_settings.image_height = m_prime_tower_image.height; + TextureMappingPrimeTowerImage &target = back ? m_prime_tower_image_back : m_prime_tower_image; + target = std::move(loaded); + if (back) { + m_global_settings.image_file_back = "Metadata/texture_mapping/prime_tower_image_back.png"; + m_global_settings.image_name_back = target.image_name; + m_global_settings.image_width_back = target.width; + m_global_settings.image_height_back = target.height; + } else { + m_global_settings.image_file = "Metadata/texture_mapping/prime_tower_image.png"; + m_global_settings.image_name = target.image_name; + m_global_settings.image_width = target.width; + m_global_settings.image_height = target.height; + } + m_global_settings.enabled = true; + if (m_prime_tower_mapping_enabled_checkbox != nullptr) + m_prime_tower_mapping_enabled_checkbox->SetValue(true); update_prime_tower_image_label(); } @@ -1395,10 +1459,12 @@ private: wxChoice *m_generic_solver_lookup_choice {nullptr}; wxCheckBox *m_prime_tower_mapping_enabled_checkbox {nullptr}; wxStaticText *m_prime_tower_image_label {nullptr}; + wxStaticText *m_prime_tower_image_back_label {nullptr}; wxSpinCtrlDouble *m_prime_tower_angle_spin {nullptr}; wxChoice *m_prime_tower_color_mode_choice {nullptr}; TextureMappingGlobalSettings m_global_settings; TextureMappingPrimeTowerImage m_prime_tower_image; + TextureMappingPrimeTowerImage m_prime_tower_image_back; std::vector m_minimum_offset_sliders; std::vector m_minimum_offset_spins; std::vector m_strength_sliders; @@ -5305,6 +5371,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.generic_solver_lookup_mode, bundle->texture_mapping_global_settings, wxGetApp().model().texture_mapping_prime_tower_image, + wxGetApp().model().texture_mapping_prime_tower_image_back, p->m_texture_mapping_advanced_options_tab); const int result = dlg.ShowModal(); p->m_texture_mapping_advanced_options_tab = dlg.selected_options_tab(); @@ -5324,6 +5391,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.generic_solver_lookup_mode = dlg.generic_solver_lookup_mode(); bundle->texture_mapping_global_settings = dlg.global_settings(); wxGetApp().model().texture_mapping_prime_tower_image = dlg.prime_tower_image(); + wxGetApp().model().texture_mapping_prime_tower_image_back = dlg.prime_tower_image_back(); set_config_string("texture_mapping_global_settings", bundle->texture_mapping_global_settings.serialize()); if (updated.filament_strengths_pct.size() < palette.size()) updated.filament_strengths_pct.resize(palette.size(), 100.f); @@ -5342,6 +5410,12 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) while (!updated.filament_minimum_offsets_pct.empty() && std::abs(updated.filament_minimum_offsets_pct.back()) <= 1e-6f) updated.filament_minimum_offsets_pct.pop_back(); apply_zone(std::move(updated)); + if (p->plater != nullptr) { + if (GLCanvas3D *canvas = p->plater->get_view3D_canvas3D()) + canvas->reload_scene(true, true); + if (GLCanvas3D *canvas = p->plater->get_assmeble_canvas3D()) + canvas->reload_scene(true, true); + } update_texture_mapping_panel(false); });