diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1ec0e1c8a0..f76f07f70c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -149,6 +149,8 @@ set(SLIC3R_SOURCES Format/SVG.cpp Format/SLAArchiveFormatRegistry.hpp Format/SLAArchiveFormatRegistry.cpp + Format/PrintRequest.cpp + Format/PrintRequest.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp GCode/Thumbnails.cpp @@ -481,6 +483,8 @@ set(SLIC3R_SOURCES SLA/DefaultSupportTree.cpp SLA/BranchingTreeSLA.hpp SLA/BranchingTreeSLA.cpp + SLA/ZCorrection.hpp + SLA/ZCorrection.cpp BranchingTree/BranchingTree.cpp BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp diff --git a/src/libslic3r/Format/PrintRequest.cpp b/src/libslic3r/Format/PrintRequest.cpp new file mode 100644 index 0000000000..c7a0a53fb1 --- /dev/null +++ b/src/libslic3r/Format/PrintRequest.cpp @@ -0,0 +1,161 @@ +#include "PrintRequest.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Exception.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Format/STL.hpp" + +//#include "slic3r/GUI/format.hpp" + +namespace Slic3r { +namespace pt = boost::property_tree; +namespace { +void read_file(const char* input_file, pt::ptree& tree) +{ + boost::filesystem::path path(input_file); + + boost::nowide::ifstream ifs(path.string()); + try { + pt::read_xml(ifs, tree); + } + catch (const boost::property_tree::xml_parser::xml_parser_error& err) { + throw Slic3r::RuntimeError("Failed reading PrintRequest file. File format is corrupt."); + } +} + +void read_tree(const boost::property_tree::ptree::value_type& section, boost::filesystem::path& model_path, std::string& material, std::string& material_color, std::vector& transformation_matrix) +{ + for (const auto& data : section.second) { + if (data.first == "Path") { + model_path = boost::filesystem::path(data.second.data()); + } + else if (data.first == "Material") { + material = data.second.data(); + } + else if (data.first == "MaterialColor") { + material_color = data.second.data(); + } + else if (data.first == "TransformationMatrix") { + transformation_matrix.reserve(16); + for (const auto& element : data.second) { + transformation_matrix.emplace_back(element.second.data()); + } + } + } +} +bool fill_model(Model* model, const boost::filesystem::path& model_path, const std::string& material, const std::vector& transformation_matrix) +{ + if (!boost::filesystem::exists(model_path)) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path doesn't exists. " + model_path.string()); + if (!boost::algorithm::iends_with(model_path.string(), ".stl")) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path is not stl file. " + model_path.string()); + bool result = load_stl(model_path.string().c_str(), model); + if (!material.empty()) { + model->objects.back()->volumes.front()->set_material_id(material); + } + return result; +} +void add_instance(Model* model, const boost::filesystem::path& model_path, const std::vector& transformation_matrix) +{ + if (transformation_matrix.size() >= 16) { + + auto string_to_double = [model_path](const std::string& from) -> double { + double ret_val; + auto answer = fast_float::from_chars(from.data(), from.data() + from.size(), ret_val); + if (answer.ec != std::errc()) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Couldn't parse transformation matrix. " + model_path.string()); + return ret_val; + }; + + Vec3d offset_vector; + Slic3r::Transform3d matrix; + try + { + offset_vector = Slic3r::Vec3d(string_to_double(transformation_matrix[3]), string_to_double(transformation_matrix[7]), string_to_double(transformation_matrix[11])); + // PrintRequest is row-major 4x4, Slic3r::Transform3d (Eigen) is column-major by default 3x3 + matrix(0, 0) = string_to_double(transformation_matrix[0]); + matrix(1, 0) = string_to_double(transformation_matrix[1]); + matrix(2, 0) = string_to_double(transformation_matrix[2]); + matrix(0, 1) = string_to_double(transformation_matrix[4]); + matrix(1, 1) = string_to_double(transformation_matrix[5]); + matrix(2, 1) = string_to_double(transformation_matrix[6]); + matrix(0, 2) = string_to_double(transformation_matrix[8]); + matrix(1, 2) = string_to_double(transformation_matrix[9]); + matrix(2, 2) = string_to_double(transformation_matrix[10]); + } + catch (const Slic3r::RuntimeError& e) { + throw e; + } + + + ModelObject* object = model->objects.back(); + Slic3r::Geometry::Transformation transformation(matrix); + transformation.set_offset(offset_vector); + object->add_instance(transformation); + } +} + +} + +bool load_printRequest(const char* input_file, Model* model) +{ + pt::ptree tree; + try + { + read_file(input_file, tree); + } + catch (const std::exception& e) + { + throw e; + } + + bool result = true; + + for (const auto& section0 : tree) { + if (section0.first != "PrintRequest") + continue; + if (section0.second.empty()) + continue; + for (const auto& section1 : section0.second) { + if (section1.first != "Files") + continue; + if (section1.second.empty()) + continue; + for (const auto& section2 : section1.second) { + if (section2.first != "File") + continue; + if (section2.second.empty()) + continue; + boost::filesystem::path model_path; + std::string material; + std::string material_color; + std::vector transformation_matrix; + + try + { + read_tree(section2, model_path, material, material_color, transformation_matrix); + result = result && fill_model(model, model_path, material, transformation_matrix); + if (!result) + return false; + add_instance(model, model_path, transformation_matrix); + } + catch (const std::exception& e) + { + throw e; + } + + + } + } + } + + return true; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/PrintRequest.hpp b/src/libslic3r/Format/PrintRequest.hpp new file mode 100644 index 0000000000..bf92d20482 --- /dev/null +++ b/src/libslic3r/Format/PrintRequest.hpp @@ -0,0 +1,12 @@ +#ifndef slic3r_Format_PrintRequest_hpp_ +#define slic3r_Format_PrintRequest_hpp_ + + + +namespace Slic3r { +class Model; +bool load_printRequest(const char* input_file, Model* model); + +} //namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/libslic3r/GCode/SeamAligned.cpp b/src/libslic3r/GCode/SeamAligned.cpp index 11148c4006..895146898c 100644 --- a/src/libslic3r/GCode/SeamAligned.cpp +++ b/src/libslic3r/GCode/SeamAligned.cpp @@ -261,6 +261,29 @@ struct SeamCandidate { std::vector visibilities; }; +std::vector get_shell_seam( + const Shells::Shell<> &shell, + const std::function &chooser +) { + std::vector result; + result.reserve(shell.size()); + for (std::size_t i{0}; i < shell.size(); ++i) { + const Shells::Slice<> &slice{shell[i]}; + if (slice.boundary.is_degenerate) { + if (std::optional seam_choice{ + choose_degenerate_seam_point(slice.boundary)}) { + result.push_back(*seam_choice); + } else { + result.emplace_back(); + } + } else { + const SeamChoice choice{chooser(slice.boundary, i)}; + result.push_back(choice); + } + } + return result; +} + SeamCandidate get_seam_candidate( const Shells::Shell<> &shell, const Vec2d &starting_position, @@ -273,7 +296,7 @@ SeamCandidate get_seam_candidate( std::vector choice_visibilities(shell.size(), 1.0); std::vector choices{ - Seams::get_shell_seam(shell, [&, previous_position{starting_position}](const Perimeter &perimeter, std::size_t slice_index) mutable { + get_shell_seam(shell, [&, previous_position{starting_position}](const Perimeter &perimeter, std::size_t slice_index) mutable { SeamChoice candidate{Seams::choose_seam_point( perimeter, Impl::Nearest{previous_position, params.max_detour} )}; diff --git a/src/libslic3r/GCode/SeamChoice.cpp b/src/libslic3r/GCode/SeamChoice.cpp index dfcb4b4b08..2a577413d8 100644 --- a/src/libslic3r/GCode/SeamChoice.cpp +++ b/src/libslic3r/GCode/SeamChoice.cpp @@ -47,65 +47,4 @@ std::optional choose_degenerate_seam_point(const Perimeters::Perimet return std::nullopt; } -std::optional> maybe_get_shell_seam( - const Shells::Shell<> &shell, - const std::function(const Perimeters::Perimeter &, std::size_t)> &chooser -) { - std::vector result; - result.reserve(shell.size()); - for (std::size_t i{0}; i < shell.size(); ++i) { - const Shells::Slice<> &slice{shell[i]}; - if (slice.boundary.is_degenerate) { - if (std::optional seam_choice{ - choose_degenerate_seam_point(slice.boundary)}) { - result.push_back(*seam_choice); - } else { - result.emplace_back(); - } - } else { - const std::optional choice{chooser(slice.boundary, i)}; - if (!choice) { - return std::nullopt; - } - result.push_back(*choice); - } - } - return result; -} - -std::vector get_shell_seam( - const Shells::Shell<> &shell, - const std::function &chooser -) { - std::optional> seam{maybe_get_shell_seam( - shell, - [&](const Perimeters::Perimeter &perimeter, std::size_t slice_index) { - return chooser(perimeter, slice_index); - } - )}; - if (!seam) { - // Should be unreachable as chooser always returns a SeamChoice! - return std::vector(shell.size()); - } - return *seam; -} - -std::vector> get_object_seams( - Shells::Shells<> &&shells, - const std::function(const Shells::Shell<>&)> &get_shell_seam -) { - std::vector> layer_seams(get_layer_count(shells)); - - for (Shells::Shell<> &shell : shells) { - std::vector seam{get_shell_seam(shell)}; - - for (std::size_t perimeter_id{}; perimeter_id < shell.size(); ++perimeter_id) { - const SeamChoice &choice{seam[perimeter_id]}; - Perimeters::Perimeter &perimeter{shell[perimeter_id].boundary}; - layer_seams[shell[perimeter_id].layer_index].emplace_back(choice, std::move(perimeter)); - } - } - - return layer_seams; -} } // namespace Slic3r::Seams diff --git a/src/libslic3r/GCode/SeamChoice.hpp b/src/libslic3r/GCode/SeamChoice.hpp index d33933b71f..484c1d53da 100644 --- a/src/libslic3r/GCode/SeamChoice.hpp +++ b/src/libslic3r/GCode/SeamChoice.hpp @@ -2,7 +2,7 @@ #define libslic3r_SeamChoice_hpp_ #include "libslic3r/Polygon.hpp" -#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/GCode/SeamShells.hpp" namespace Slic3r::Seams { @@ -52,21 +52,6 @@ SeamChoice choose_seam_point( std::optional choose_degenerate_seam_point(const Perimeters::Perimeter &perimeter); -std::optional> maybe_get_shell_seam( - const Shells::Shell<> &shell, - const std::function(const Perimeters::Perimeter &, std::size_t)> &chooser -); - -std::vector get_shell_seam( - const Shells::Shell<> &shell, - const std::function &chooser -); - -std::vector> get_object_seams( - Shells::Shells<> &&shells, - const std::function(const Shells::Shell<> &)> &get_shell_seam -); - } // namespace Slic3r::Seams #endif // libslic3r_SeamChoice_hpp_ diff --git a/src/libslic3r/GCode/SeamGeometry.cpp b/src/libslic3r/GCode/SeamGeometry.cpp index 563836e2e3..8688fb0e45 100644 --- a/src/libslic3r/GCode/SeamGeometry.cpp +++ b/src/libslic3r/GCode/SeamGeometry.cpp @@ -1,4 +1,5 @@ #include "libslic3r/GCode/SeamGeometry.hpp" +#include "ClipperUtils.hpp" #include "KDTreeIndirect.hpp" #include "Layer.hpp" #include @@ -188,6 +189,54 @@ std::vector get_extrusions(tcb::span obj return result; } +BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance) { + BoundedPolygons result; + result.reserve(external_perimeters.size()); + + using std::transform, std::back_inserter; + + transform( + external_perimeters.begin(), external_perimeters.end(), back_inserter(result), + [&](const Geometry::Extrusion &external_perimeter) { + const auto [choosen_index, _]{Geometry::pick_closest_bounding_box( + external_perimeter.bounding_box, + external_perimeter.island_boundary_bounding_boxes + )}; + + const double distance{Geometry::bounding_box_distance( + external_perimeter.island_boundary_bounding_boxes[choosen_index], + external_perimeter.bounding_box + )}; + + if (distance > max_bb_distance) { + Polygons expanded_extrusion{expand(external_perimeter.polygon, external_perimeter.width / 2.0)}; + if (!expanded_extrusion.empty()) { + return BoundedPolygon{ + expanded_extrusion.front(), expanded_extrusion.front().bounding_box(), external_perimeter.polygon.is_clockwise() + }; + } + } + + const bool is_hole{choosen_index != 0}; + const Polygon &adjacent_boundary{ + !is_hole ? external_perimeter.island_boundary.contour : + external_perimeter.island_boundary.holes[choosen_index - 1]}; + return BoundedPolygon{adjacent_boundary, external_perimeter.island_boundary_bounding_boxes[choosen_index], is_hole}; + } + ); + return result; +} + +std::vector project_to_geometry(const std::vector &extrusions, const double max_bb_distance) { + std::vector result(extrusions.size()); + + for (std::size_t layer_index{0}; layer_index < extrusions.size(); ++layer_index) { + result[layer_index] = project_to_geometry(extrusions[layer_index], max_bb_distance); + } + + return result; +} + std::vector oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance) { const double total_distance{(from - to).norm()}; const auto points_count{static_cast(std::ceil(total_distance / max_distance)) + 1}; diff --git a/src/libslic3r/GCode/SeamGeometry.hpp b/src/libslic3r/GCode/SeamGeometry.hpp index ff3c11d83f..80faa53440 100644 --- a/src/libslic3r/GCode/SeamGeometry.hpp +++ b/src/libslic3r/GCode/SeamGeometry.hpp @@ -43,6 +43,17 @@ using Extrusions = std::vector; std::vector get_extrusions(tcb::span object_layers); +struct BoundedPolygon { + Polygon polygon; + BoundingBox bounding_box; + bool is_hole{false}; +}; + +using BoundedPolygons = std::vector; + +BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance); +std::vector project_to_geometry(const std::vector &extrusions, const double max_bb_distance); + Vec2d get_polygon_normal( const std::vector &points, const std::size_t index, const double min_arm_length ); diff --git a/src/libslic3r/GCode/SeamPerimeters.cpp b/src/libslic3r/GCode/SeamPerimeters.cpp index 32c44b7b84..6664494711 100644 --- a/src/libslic3r/GCode/SeamPerimeters.cpp +++ b/src/libslic3r/GCode/SeamPerimeters.cpp @@ -384,26 +384,32 @@ Perimeter Perimeter::create( std::move(angle_types)}; } -Shells::Shells<> create_perimeters( - const std::vector> &shells, +LayerPerimeters create_perimeters( + const std::vector &polygons, const std::vector &layer_infos, const ModelInfo::Painting &painting, const PerimeterParams ¶ms ) { - std::vector> result; - result.reserve(shells.size()); + LayerPerimeters result; + result.reserve(polygons.size()); + std::transform( - shells.begin(), shells.end(), std::back_inserter(result), - [](const Shells::Shell &shell) { return Shells::Shell<>(shell.size()); } + polygons.begin(), polygons.end(), std::back_inserter(result), + [](const Geometry::BoundedPolygons &layer) { return BoundedPerimeters(layer.size()); } ); - Geometry::iterate_nested(shells, [&](const std::size_t shell_index, const std::size_t polygon_index){ - const Shells::Shell &shell{shells[shell_index]}; - const Shells::Slice& slice{shell[polygon_index]}; - const Polygon &polygon{slice.boundary}; - const LayerInfo &layer_info{layer_infos[slice.layer_index]}; - result[shell_index][polygon_index] = {Perimeter::create(polygon, painting, layer_info, params), slice.layer_index}; - }); + Geometry::iterate_nested( + polygons, + [&](const std::size_t layer_index, const std::size_t polygon_index) { + const Geometry::BoundedPolygons &layer{polygons[layer_index]}; + const Geometry::BoundedPolygon &bounded_polygon{layer[polygon_index]}; + const LayerInfo &layer_info{layer_infos[layer_index]}; + result[layer_index][polygon_index] = BoundedPerimeter{ + Perimeter::create(bounded_polygon.polygon, painting, layer_info, params), + bounded_polygon.bounding_box}; + } + ); return result; } + } // namespace Slic3r::Seams::Perimeter diff --git a/src/libslic3r/GCode/SeamPerimeters.hpp b/src/libslic3r/GCode/SeamPerimeters.hpp index 21c50e0a98..47be8806eb 100644 --- a/src/libslic3r/GCode/SeamPerimeters.hpp +++ b/src/libslic3r/GCode/SeamPerimeters.hpp @@ -5,8 +5,8 @@ #include "libslic3r/GCode/SeamPainting.hpp" #include "libslic3r/KDTreeIndirect.hpp" - -#include "libslic3r/GCode/SeamShells.hpp" +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/GCode/SeamGeometry.hpp" namespace Slic3r { class Layer; @@ -107,6 +107,8 @@ struct Perimeter { struct IndexToCoord { + IndexToCoord(const tcb::span positions): positions(positions) {} + IndexToCoord() = default; double operator()(const size_t index, size_t dim) const; tcb::span positions; @@ -162,32 +164,25 @@ struct Perimeter PointTrees blocked_points{}; }; -/** - * @brief Create a Perimeter for each polygon in each of the shells. - */ -Shells::Shells create_perimeters( - const std::vector> &shells, +using Perimeters = std::vector; + +struct BoundedPerimeter { + Perimeter perimeter; + BoundingBox bounding_box; +}; + +using BoundedPerimeters = std::vector; +using LayerPerimeters = std::vector; + +LayerPerimeters create_perimeters( + const std::vector &polygons, const std::vector &layer_infos, const ModelInfo::Painting &painting, const PerimeterParams ¶ms ); -inline std::size_t get_layer_count( - const Shells::Shells<> &shells -) { - std::size_t layer_count{0}; - for (const Shells::Shell<> &shell : shells) { - for (const Shells::Slice<>& slice : shell) { - if (slice.layer_index >= layer_count) { - layer_count = slice.layer_index + 1; - } - } - } - return layer_count; -} - inline std::vector extract_points( - const Perimeters::Perimeter &perimeter, const Perimeters::PointType point_type + const Perimeter &perimeter, const PointType point_type ) { std::vector result; for (std::size_t i{0}; i < perimeter.positions.size(); ++i) { diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 088cfc674c..2a764b2663 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -7,8 +7,10 @@ #include #include + #include "SeamPlacer.hpp" +#include "libslic3r/GCode/SeamShells.hpp" #include "libslic3r/GCode/SeamAligned.hpp" #include "libslic3r/GCode/SeamRear.hpp" #include "libslic3r/GCode/SeamRandom.hpp" @@ -17,16 +19,15 @@ namespace Slic3r::Seams { -using ObjectShells = std::vector>>; using ObjectPainting = std::map; -ObjectShells partition_to_shells( +ObjectLayerPerimeters get_perimeters( SpanOfConstPtrs objects, const Params ¶ms, const ObjectPainting& object_painting, const std::function &throw_if_canceled ) { - ObjectShells result; + ObjectLayerPerimeters result; for (const PrintObject *print_object : objects) { const ModelInfo::Painting &painting{object_painting.at(print_object)}; @@ -37,25 +38,38 @@ ObjectShells partition_to_shells( const Perimeters::LayerInfos layer_infos{Perimeters::get_layer_infos( print_object->layers(), params.perimeter.elephant_foot_compensation )}; - Shells::Shells shell_polygons{ - Shells::create_shells(extrusions, params.max_distance)}; + const std::vector projected{Geometry::project_to_geometry(extrusions, params.max_distance)}; + Perimeters::LayerPerimeters perimeters{Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; - Shells::Shells<> perimeters{ - Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)}; throw_if_canceled(); - result.emplace_back(print_object, std::move(perimeters)); + result.emplace(print_object, std::move(perimeters)); + } + return result; +} + +Perimeters::LayerPerimeters sort_to_layers(Shells::Shells<> &&shells) { + const std::size_t layer_count{Shells::get_layer_count(shells)}; + Perimeters::LayerPerimeters result(layer_count); + + for (Shells::Shell<> &shell : shells) { + for (Shells::Slice<> &slice : shell) { + const BoundingBox bounding_box{Geometry::scaled(slice.boundary.positions)}; + result[slice.layer_index].push_back( + Perimeters::BoundedPerimeter{std::move(slice.boundary), bounding_box} + ); + } } return result; } ObjectSeams precalculate_seams( const Params ¶ms, - ObjectShells &&seam_data, + ObjectLayerPerimeters &&seam_data, const std::function &throw_if_canceled ) { ObjectSeams result; - for (auto &[print_object, shells] : seam_data) { + for (auto &[print_object, layer_perimeters] : seam_data) { switch (params.seam_preference) { case spAligned: { const Transform3d transformation{print_object->trafo_centered()}; @@ -68,17 +82,18 @@ ObjectSeams precalculate_seams( points_visibility, params.convex_visibility_modifier, params.concave_visibility_modifier}; + Shells::Shells<> shells{Shells::create_shells(std::move(layer_perimeters), params.max_distance)}; result[print_object] = Aligned::get_object_seams( std::move(shells), visibility_calculator, params.aligned ); break; } case spRear: { - result[print_object] = Rear::get_object_seams(std::move(shells), params.rear_project_threshold); + result[print_object] = Rear::get_object_seams(std::move(layer_perimeters), params.rear_tolerance, params.rear_y_offset); break; } case spRandom: { - result[print_object] = Random::get_object_seams(std::move(shells), params.random_seed); + result[print_object] = Random::get_object_seams(std::move(layer_perimeters), params.random_seed); break; } case spNearest: { @@ -111,7 +126,8 @@ Params Placer::get_params(const DynamicPrintConfig &config) { params.staggered_inner_seams = config.opt_bool("staggered_inner_seams"); params.max_nearest_detour = 1.0; - params.rear_project_threshold = 0.05; // % + params.rear_tolerance = 0.2; + params.rear_y_offset = 20; params.aligned.jump_visibility_threshold = 0.6; params.max_distance = 5.0; params.perimeter.oversampling_max_distance = 0.2; @@ -128,24 +144,6 @@ Params Placer::get_params(const DynamicPrintConfig &config) { return params; } -ObjectLayerPerimeters sort_to_layers(ObjectShells &&object_shells) { - ObjectLayerPerimeters result; - for (auto &[print_object, shells] : object_shells) { - const std::size_t layer_count{print_object->layer_count()}; - result[print_object] = LayerPerimeters(layer_count); - - for (Shells::Shell<> &shell : shells) { - for (Shells::Slice<> &slice : shell) { - const BoundingBox bounding_box{Geometry::scaled(slice.boundary.positions)}; - result[print_object][slice.layer_index].push_back( - BoundedPerimeter{std::move(slice.boundary), bounding_box} - ); - } - } - } - return result; -} - void Placer::init( SpanOfConstPtrs objects, const Params ¶ms, @@ -160,14 +158,14 @@ void Placer::init( object_painting.emplace(print_object, ModelInfo::Painting{transformation, volumes}); } - ObjectShells seam_data{partition_to_shells(objects, params, object_painting, throw_if_canceled)}; + ObjectLayerPerimeters perimeters{get_perimeters(objects, params, object_painting, throw_if_canceled)}; this->params = params; if (this->params.seam_preference != spNearest) { this->seams_per_object = - precalculate_seams(params, std::move(seam_data), throw_if_canceled); + precalculate_seams(params, std::move(perimeters), throw_if_canceled); } else { - this->perimeters_per_layer = sort_to_layers(std::move(seam_data)); + this->perimeters_per_layer = std::move(perimeters); } BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: init: end"; @@ -275,15 +273,45 @@ Point finalize_seam_position( return scaled(loop_point); } +struct NearestCorner { + Vec2d prefered_position; + + std::optional operator()( + const Perimeters::Perimeter &perimeter, + const Perimeters::PointType point_type, + const Perimeters::PointClassification point_classification + ) const { + std::optional corner_candidate; + double min_distance{std::numeric_limits::infinity()}; + for (std::size_t i{0}; i < perimeter.positions.size(); ++i) { + if (perimeter.point_types[i] == point_type && + perimeter.point_classifications[i] == point_classification && + perimeter.angle_types[i] != Perimeters::AngleType::smooth) { + const Vec2d &point{perimeter.positions[i]}; + if (!corner_candidate) { + corner_candidate = {i, i, point}; + continue; + } + const double distance{(prefered_position - point).norm()}; + if (distance < min_distance) { + corner_candidate = {i, i, point}; + min_distance = distance; + } + } + } + return corner_candidate; + } +}; + std::pair place_seam_near( - const std::vector &layer_perimeters, + const std::vector &layer_perimeters, const ExtrusionLoop &loop, const Point &position, const double max_detour ) { BoundingBoxes choose_from; choose_from.reserve(layer_perimeters.size()); - for (const BoundedPerimeter &perimeter : layer_perimeters) { + for (const Perimeters::BoundedPerimeter &perimeter : layer_perimeters) { choose_from.push_back(perimeter.bounding_box); } @@ -292,11 +320,19 @@ std::pair place_seam_near( const std::size_t choice_index{ Geometry::pick_closest_bounding_box(loop_polygon.bounding_box(), choose_from).first}; - Seams::Aligned::Impl::Nearest nearest{unscaled(position), max_detour}; + const NearestCorner nearest_corner{unscaled(position)}; + const std::optional corner_choice{ + Seams::maybe_choose_seam_point(layer_perimeters[choice_index].perimeter, nearest_corner)}; - const SeamChoice choice{Seams::choose_seam_point(layer_perimeters[choice_index].perimeter, nearest)}; + if (corner_choice) { + return {*corner_choice, choice_index}; + } - return {choice, choice_index}; + const Seams::Aligned::Impl::Nearest nearest{unscaled(position), max_detour}; + const SeamChoice nearest_choice{ + Seams::choose_seam_point(layer_perimeters[choice_index].perimeter, nearest)}; + + return {nearest_choice, choice_index}; } int get_perimeter_count(const Layer *layer){ @@ -329,7 +365,7 @@ Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Po if (this->params.seam_preference == spNearest) { - const std::vector &perimeters{this->perimeters_per_layer.at(po)[layer_index]}; + const std::vector &perimeters{this->perimeters_per_layer.at(po)[layer_index]}; const auto [seam_choice, perimeter_index] = place_seam_near(perimeters, loop, last_pos, this->params.max_nearest_detour); return finalize_seam_position(loop_polygon, seam_choice, perimeters[perimeter_index].perimeter, loop_width, do_staggering); } else { diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 625170944a..843a5ddeac 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -21,20 +21,15 @@ namespace Slic3r::Seams { -struct BoundedPerimeter { - Perimeters::Perimeter perimeter; - BoundingBox bounding_box; -}; - using ObjectSeams = std::unordered_map>>; -using LayerPerimeters = std::vector>; -using ObjectLayerPerimeters = std::unordered_map; +using ObjectLayerPerimeters = std::unordered_map; struct Params { double max_nearest_detour; - double rear_project_threshold; + double rear_tolerance; + double rear_y_offset; Aligned::Params aligned; double max_distance{}; unsigned random_seed{}; diff --git a/src/libslic3r/GCode/SeamRandom.cpp b/src/libslic3r/GCode/SeamRandom.cpp index 27f01ca6be..03fa33e6ed 100644 --- a/src/libslic3r/GCode/SeamRandom.cpp +++ b/src/libslic3r/GCode/SeamRandom.cpp @@ -122,15 +122,33 @@ std::optional Random::operator()( } // namespace Impl std::vector> get_object_seams( - Shells::Shells<> &&shells, const unsigned fixed_seed + Perimeters::LayerPerimeters &&perimeters, const unsigned fixed_seed ) { std::mt19937 random_engine{fixed_seed}; const Impl::Random random{random_engine}; - return Seams::get_object_seams(std::move(shells), [&](const Shells::Shell<> &shell) { - return Seams::get_shell_seam(shell, [&](const Perimeters::Perimeter &perimeter, std::size_t) { - return Seams::choose_seam_point(perimeter, random); - }); - }); + std::vector> result; + + for (std::vector &layer : perimeters) { + result.emplace_back(); + for (Perimeters::BoundedPerimeter &perimeter : layer) { + if (perimeter.perimeter.is_degenerate) { + std::optional seam_choice{ + Seams::choose_degenerate_seam_point(perimeter.perimeter)}; + if (seam_choice) { + result.back().push_back( + SeamPerimeterChoice{*seam_choice, std::move(perimeter.perimeter)} + ); + } else { + result.back().push_back(SeamPerimeterChoice{SeamChoice{}, std::move(perimeter.perimeter)}); + } + } else { + result.back().push_back(SeamPerimeterChoice{ + Seams::choose_seam_point(perimeter.perimeter, random), + std::move(perimeter.perimeter)}); + } + } + } + return result; } } // namespace Slic3r::Seams::Random diff --git a/src/libslic3r/GCode/SeamRandom.hpp b/src/libslic3r/GCode/SeamRandom.hpp index 81c8dc4342..c7eb0f3644 100644 --- a/src/libslic3r/GCode/SeamRandom.hpp +++ b/src/libslic3r/GCode/SeamRandom.hpp @@ -24,6 +24,6 @@ struct Random }; } std::vector> get_object_seams( - Shells::Shells<> &&shells, const unsigned fixed_seed + Perimeters::LayerPerimeters &&perimeters, const unsigned fixed_seed ); } diff --git a/src/libslic3r/GCode/SeamRear.cpp b/src/libslic3r/GCode/SeamRear.cpp index d189b5ad08..bb5ba03fca 100644 --- a/src/libslic3r/GCode/SeamRear.cpp +++ b/src/libslic3r/GCode/SeamRear.cpp @@ -15,111 +15,89 @@ BoundingBoxf get_bounding_box(const Shells::Shell<> &shell) { return result; } -std::optional get_rearest_point( - const Perimeters::Perimeter &perimeter, - const PointType point_type, - const PointClassification point_classification -) { - double max_y{-std::numeric_limits::infinity()}; - std::optional choosen_index; - for (std::size_t i{0}; i < perimeter.positions.size(); ++i) { - const Perimeters::PointType _point_type{perimeter.point_types[i]}; - const Perimeters::PointClassification _point_classification{perimeter.point_classifications[i]}; +struct RearestPointCalculator { + double rear_tolerance; + double rear_y_offset; + BoundingBoxf bounding_box; - if (point_type == _point_type && point_classification == _point_classification) { - const Vec2d &position{perimeter.positions[i]}; - if (position.y() > max_y) { - max_y = position.y(); - choosen_index = i; + std::optional operator()( + const Perimeters::Perimeter &perimeter, + const PointType point_type, + const PointClassification point_classification + ) { + std::vector possible_lines; + for (std::size_t i{0}; i < perimeter.positions.size() - 1; ++i) { + if (perimeter.point_types[i] != point_type) { + continue; } + if (perimeter.point_classifications[i] != point_classification) { + continue; + } + if (perimeter.point_types[i + 1] != point_type) { + continue; + } + if (perimeter.point_classifications[i + 1] != point_classification) { + continue; + } + possible_lines.push_back(PerimeterLine{perimeter.positions[i], perimeter.positions[i+1], i, i + 1}); } - } - if (choosen_index) { - return SeamChoice{*choosen_index, *choosen_index, perimeter.positions[*choosen_index]}; - } - return std::nullopt; -} - -std::optional StraightLine::operator()( - const Perimeters::Perimeter &perimeter, - const PointType point_type, - const PointClassification point_classification -) const { - std::vector possible_lines; - for (std::size_t i{0}; i < perimeter.positions.size() - 1; ++i) { - if (perimeter.point_types[i] != point_type) { - continue; - } - if (perimeter.point_classifications[i] != point_classification) { - continue; - } - if (perimeter.point_types[i + 1] != point_type) { - continue; - } - if (perimeter.point_classifications[i + 1] != point_classification) { - continue; - } - possible_lines.push_back(PerimeterLine{perimeter.positions[i], perimeter.positions[i+1], i, i + 1}); - } - if (possible_lines.empty()) { - return std::nullopt; - } - - const AABBTreeLines::LinesDistancer possible_distancer{possible_lines}; - const BoundingBoxf bounding_box{perimeter.positions}; - - const std::vector> intersections{ - possible_distancer.intersections_with_line(PerimeterLine{ - this->prefered_position, Vec2d{this->prefered_position.x(), bounding_box.min.y()}, - 0, 0})}; - if (!intersections.empty()) { - const auto[position, line_index]{intersections.front()}; - if (position.y() < bounding_box.max.y() - - this->rear_project_threshold * (bounding_box.max.y() - bounding_box.min.y())) { + if (possible_lines.empty()) { return std::nullopt; } - const PerimeterLine &intersected_line{possible_lines[line_index]}; - const SeamChoice intersected_choice{intersected_line.previous_index, intersected_line.next_index, position}; - return intersected_choice; + const BoundingBoxf bounding_box{perimeter.positions}; + const AABBTreeLines::LinesDistancer possible_distancer{possible_lines}; + const double center_x{(bounding_box.max.x() + bounding_box.min.x()) / 2.0}; + const Vec2d prefered_position{center_x, bounding_box.max.y() + rear_y_offset}; + auto [_, line_index, point] = possible_distancer.distance_from_lines_extra(prefered_position); + const Vec2d location_at_bb{center_x, bounding_box.max.y()}; + auto [_d, line_index_at_bb, point_at_bb] = possible_distancer.distance_from_lines_extra(location_at_bb); + const double y_distance{point.y() - point_at_bb.y()}; + + Vec2d result{point}; + if (y_distance < 0) { + result = point_at_bb; + } else if (y_distance <= rear_tolerance) { + const double factor{y_distance / rear_tolerance}; + result = factor * point + (1 - factor) * point_at_bb; + } + return SeamChoice{possible_lines[line_index].previous_index, possible_lines[line_index].next_index, result}; } - return std::nullopt; -} +}; } // namespace Impl std::vector> get_object_seams( - Shells::Shells<> &&shells, - const double rear_project_threshold + std::vector> &&perimeters, + const double rear_tolerance, + const double rear_y_offset ) { - double average_x_center{0.0}; - std::size_t count{0}; - for (const Shells::Shell<> &shell : shells) { - for (const Shells::Slice<> &slice : shell) { - if (slice.boundary.positions.empty()) { - continue; + std::vector> result; + + for (std::vector &layer : perimeters) { + result.emplace_back(); + for (Perimeters::BoundedPerimeter &perimeter : layer) { + if (perimeter.perimeter.is_degenerate) { + std::optional seam_choice{ + Seams::choose_degenerate_seam_point(perimeter.perimeter)}; + if (seam_choice) { + result.back().push_back( + SeamPerimeterChoice{*seam_choice, std::move(perimeter.perimeter)} + ); + } else { + result.back().push_back(SeamPerimeterChoice{SeamChoice{}, std::move(perimeter.perimeter)}); + } + } else { + BoundingBoxf bounding_box{unscaled(perimeter.bounding_box)}; + const SeamChoice seam_choice{Seams::choose_seam_point( + perimeter.perimeter, + Impl::RearestPointCalculator{rear_tolerance, rear_y_offset, bounding_box} + )}; + result.back().push_back( + SeamPerimeterChoice{seam_choice, std::move(perimeter.perimeter)} + ); } - const BoundingBoxf slice_bounding_box{slice.boundary.positions}; - average_x_center += (slice_bounding_box.min.x() + slice_bounding_box.max.x()) / 2.0; - count++; } } - average_x_center /= count; - return Seams::get_object_seams(std::move(shells), [&](const Shells::Shell<> &shell) { - BoundingBoxf bounding_box{Impl::get_bounding_box(shell)}; - const Vec2d back_center{average_x_center, bounding_box.max.y()}; - std::optional> straight_seam { - Seams::maybe_get_shell_seam(shell, [&](const Perimeters::Perimeter &perimeter, std::size_t) { - return Seams::maybe_choose_seam_point( - perimeter, - Impl::StraightLine{back_center, rear_project_threshold} - ); - }) - }; - if (!straight_seam) { - return Seams::get_shell_seam(shell, [&](const Perimeters::Perimeter &perimeter, std::size_t) { - return Seams::choose_seam_point(perimeter, Impl::get_rearest_point); - }); - } - return *straight_seam; - }); + + return result; } } // namespace Slic3r::Seams::Rear diff --git a/src/libslic3r/GCode/SeamRear.hpp b/src/libslic3r/GCode/SeamRear.hpp index a313432780..5ab2fe4269 100644 --- a/src/libslic3r/GCode/SeamRear.hpp +++ b/src/libslic3r/GCode/SeamRear.hpp @@ -16,24 +16,12 @@ struct PerimeterLine using Scalar = Vec2d::Scalar; static const constexpr int Dim = 2; }; - -struct StraightLine -{ - Vec2d prefered_position; - double rear_project_threshold; - - std::optional operator()( - const Perimeters::Perimeter &perimeter, - const Perimeters::PointType point_type, - const Perimeters::PointClassification point_classification - ) const; -}; - } // namespace Impl std::vector> get_object_seams( - Shells::Shells<> &&shells, - const double rear_project_threshold + std::vector> &&perimeters, + const double rear_tolerance, + const double rear_y_offet ); } // namespace Slic3r::Seams::Rear diff --git a/src/libslic3r/GCode/SeamShells.cpp b/src/libslic3r/GCode/SeamShells.cpp index 65e9149c82..b3ee128424 100644 --- a/src/libslic3r/GCode/SeamShells.cpp +++ b/src/libslic3r/GCode/SeamShells.cpp @@ -5,65 +5,17 @@ namespace Slic3r::Seams::Shells::Impl { -BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance) { - BoundedPolygons result; - result.reserve(external_perimeters.size()); - - using std::transform, std::back_inserter; - - transform( - external_perimeters.begin(), external_perimeters.end(), back_inserter(result), - [&](const Geometry::Extrusion &external_perimeter) { - const auto [choosen_index, _]{Geometry::pick_closest_bounding_box( - external_perimeter.bounding_box, - external_perimeter.island_boundary_bounding_boxes - )}; - - const double distance{Geometry::bounding_box_distance( - external_perimeter.island_boundary_bounding_boxes[choosen_index], - external_perimeter.bounding_box - )}; - - if (distance > max_bb_distance) { - Polygons expanded_extrusion{expand(external_perimeter.polygon, external_perimeter.width / 2.0)}; - if (!expanded_extrusion.empty()) { - return BoundedPolygon{ - expanded_extrusion.front(), expanded_extrusion.front().bounding_box(), external_perimeter.polygon.is_clockwise() - }; - } - } - - const bool is_hole{choosen_index != 0}; - const Polygon &adjacent_boundary{ - !is_hole ? external_perimeter.island_boundary.contour : - external_perimeter.island_boundary.holes[choosen_index - 1]}; - return BoundedPolygon{adjacent_boundary, external_perimeter.island_boundary_bounding_boxes[choosen_index], is_hole}; - } - ); - return result; -} - -std::vector project_to_geometry(const std::vector &extrusions, const double max_bb_distance) { - std::vector result(extrusions.size()); - - for (std::size_t layer_index{0}; layer_index < extrusions.size(); ++layer_index) { - result[layer_index] = project_to_geometry(extrusions[layer_index], max_bb_distance); - } - - return result; -} - -Shells map_to_shells( - std::vector &&layers, const Geometry::Mapping &mapping, const std::size_t shell_count +Shells<> map_to_shells( + Perimeters::LayerPerimeters &&layers, const Geometry::Mapping &mapping, const std::size_t shell_count ) { - Shells result(shell_count); + Shells<> result(shell_count); for (std::size_t layer_index{0}; layer_index < layers.size(); ++layer_index) { - BoundedPolygons &perimeters{layers[layer_index]}; + Perimeters::BoundedPerimeters &perimeters{layers[layer_index]}; for (std::size_t perimeter_index{0}; perimeter_index < perimeters.size(); perimeter_index++) { - Polygon &perimeter{perimeters[perimeter_index].polygon}; + Perimeters::Perimeter &perimeter{perimeters[perimeter_index].perimeter}; result[mapping[layer_index][perimeter_index]].push_back( - Slice{std::move(perimeter), layer_index} + Slice<>{std::move(perimeter), layer_index} ); } } @@ -72,30 +24,31 @@ Shells map_to_shells( } // namespace Slic3r::Seams::Shells::Impl namespace Slic3r::Seams::Shells { -Shells create_shells( - const std::vector &extrusions, const double max_distance +Shells<> create_shells( + Perimeters::LayerPerimeters &&perimeters, const double max_distance ) { - std::vector projected{Impl::project_to_geometry(extrusions, max_distance)}; + using Perimeters::BoundedPerimeters; + using Perimeters::BoundedPerimeter; std::vector layer_sizes; - layer_sizes.reserve(projected.size()); - for (const Impl::BoundedPolygons &perimeters : projected) { - layer_sizes.push_back(perimeters.size()); + layer_sizes.reserve(perimeters.size()); + for (const BoundedPerimeters &layer : perimeters) { + layer_sizes.push_back(layer.size()); } const auto &[shell_mapping, shell_count]{Geometry::get_mapping( layer_sizes, [&](const std::size_t layer_index, const std::size_t item_index) -> Geometry::MappingOperatorResult { - const Impl::BoundedPolygons &layer{projected[layer_index]}; - const Impl::BoundedPolygons &next_layer{projected[layer_index + 1]}; + const BoundedPerimeters &layer{perimeters[layer_index]}; + const BoundedPerimeters &next_layer{perimeters[layer_index + 1]}; if (next_layer.empty()) { return std::nullopt; } BoundingBoxes next_layer_bounding_boxes; - for (const Impl::BoundedPolygon &bounded_polygon : next_layer) { - next_layer_bounding_boxes.emplace_back(bounded_polygon.bounding_box); + for (const BoundedPerimeter &bounded_perimeter : next_layer) { + next_layer_bounding_boxes.emplace_back(bounded_perimeter.bounding_box); } const auto [perimeter_index, distance] = Geometry::pick_closest_bounding_box( @@ -109,6 +62,6 @@ Shells create_shells( } )}; - return Impl::map_to_shells(std::move(projected), shell_mapping, shell_count); + return Impl::map_to_shells(std::move(perimeters), shell_mapping, shell_count); } } // namespace Slic3r::Seams::Shells diff --git a/src/libslic3r/GCode/SeamShells.hpp b/src/libslic3r/GCode/SeamShells.hpp index ea81053506..2a5d0ce19b 100644 --- a/src/libslic3r/GCode/SeamShells.hpp +++ b/src/libslic3r/GCode/SeamShells.hpp @@ -4,6 +4,7 @@ #include #include +#include "libslic3r/GCode/SeamPerimeters.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/GCode/SeamGeometry.hpp" @@ -11,23 +12,6 @@ namespace Slic3r { class Layer; } -namespace Slic3r::Seams::Perimeters { -struct Perimeter; -} - -namespace Slic3r::Seams::Shells::Impl { - -struct BoundedPolygon { - Polygon polygon; - BoundingBox bounding_box; - bool is_hole{false}; -}; - -using BoundedPolygons = std::vector; - -BoundedPolygons project_to_geometry(const Geometry::Extrusions &extrusions, const double max_bb_distance); -} - namespace Slic3r::Seams::Shells { template struct Slice { @@ -39,8 +23,22 @@ template using Shell = std::vector> template using Shells = std::vector>; -Shells create_shells( - const std::vector &extrusions, const double max_distance +inline std::size_t get_layer_count( + const Shells<> &shells +) { + std::size_t layer_count{0}; + for (const Shell<> &shell : shells) { + for (const Slice<>& slice : shell) { + if (slice.layer_index >= layer_count) { + layer_count = slice.layer_index + 1; + } + } + } + return layer_count; +} + +Shells<> create_shells( + Perimeters::LayerPerimeters &&perimeters, const double max_distance ); } // namespace Slic3r::Seams::Shells diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2de86a5283..ba3e1075b9 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -28,6 +28,7 @@ #include "Format/3mf.hpp" #include "Format/STEP.hpp" #include "Format/SVG.hpp" +#include "Format/PrintRequest.hpp" #include @@ -140,6 +141,8 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); else if (boost::algorithm::iends_with(input_file, ".svg")) result = load_svg(input_file, model); + else if (boost::ends_with(input_file, ".printRequest")) + result = load_printRequest(input_file.c_str(), &model); else throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."); @@ -148,9 +151,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c if (model.objects.empty()) throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); - - for (ModelObject *o : model.objects) - o->input_file = input_file; + + if (!boost::ends_with(input_file, ".printRequest")) + for (ModelObject *o : model.objects) + o->input_file = input_file; if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7ef98db151..50265cb670 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -913,6 +913,11 @@ public: // Returns 0-based indices of extruders painted by multi-material painting gizmo. std::vector get_extruders_from_multi_material_painting() const; + static size_t get_extruder_color_idx(const ModelVolume& model_volume, const int extruders_count) { + const int extruder_id = model_volume.extruder_id(); + return (extruder_id <= 0 || extruder_id > extruders_count) ? 0 : extruder_id - 1; + } + protected: friend class Print; friend class SLAPrint; diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 085075a611..690c30cda1 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -266,6 +266,16 @@ inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; } +inline bool is_approx(const Matrix3d &m1, const Matrix3d &m2, double epsilon = EPSILON) +{ + for (size_t i = 0; i < 3; i++) + for (size_t j = 0; j < 3; j++) + if (!is_approx(m1(i, j), m2(i, j), epsilon)) + return false; + + return true; +} + inline Point lerp(const Point &a, const Point &b, double t) { assert((t >= -EPSILON) && (t <= 1. + EPSILON)); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 512dff085f..cb5e86fb32 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -618,6 +618,7 @@ static std::vector s_Preset_sla_material_options { "material_print_speed", "area_fill", "default_sla_material_profile", + "zcorrection_layers", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", @@ -1222,6 +1223,20 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string& return alias; } +const std::string& PresetCollection::get_preset_name_by_alias_invisible(const std::string& alias) const +{ + for ( + // Find the 1st profile name with the alias. + auto it = Slic3r::lower_bound_by_predicate(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [&alias](auto& l) { return l.first < alias; }); + // Continue over all profile names with the same alias. + it != m_map_alias_to_profile_name.end() && it->first == alias; ++it) + if (auto it_preset = this->find_preset_internal(it->second); + it_preset != m_presets.end() && it_preset->name == it->second && + it_preset->is_compatible) + return it_preset->name; + return alias; +} + const std::string* PresetCollection::get_preset_name_renamed(const std::string &old_name) const { auto it_renamed = m_map_system_profile_renamed.find(old_name); @@ -1487,13 +1502,13 @@ Preset& PresetCollection::select_preset(size_t idx) return m_presets[idx]; } -bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) +bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force, bool force_invisible /*= false*/) { std::string name = Preset::remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == name && (force_invisible || it->is_visible)) // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 830c0b298e..a2985ac4ec 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -411,6 +411,7 @@ public: PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); } const std::string& get_preset_name_by_alias(const std::string& alias) const; + const std::string& get_preset_name_by_alias_invisible(const std::string& alias) const; const std::string* get_preset_name_renamed(const std::string &old_name) const; // used to update preset_choice from Tab @@ -529,7 +530,8 @@ public: // Select a profile by its name. Return true if the selection changed. // Without force, the selection is only updated if the index changes. // With force, the changes are reverted if the new index is the same as the old index. - bool select_preset_by_name(const std::string &name, bool force); + // With force_invisible, force preset selection even it's invisible. + bool select_preset_by_name(const std::string &name, bool force, bool force_invisible = false); // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f6399dc63f..5198c31c80 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -496,6 +496,20 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p return presets.get_preset_name_by_alias(alias); } +const std::string& PresetBundle::get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const +{ + // there are not aliases for Printers profiles + if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID) + return alias; + + const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints : + preset_type == Preset::TYPE_SLA_PRINT ? sla_prints : + preset_type == Preset::TYPE_FILAMENT ? filaments : + sla_materials; + + return presets.get_preset_name_by_alias_invisible(alias); +} + void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index d7d56e8256..4bdc10ea4d 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -182,6 +182,7 @@ public: void load_installed_printers(const AppConfig &config); const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias, int extruder_id = -1); + const std::string& get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const; // Save current preset of a provided type under a new name. If the name is different from the old one, // Unselected option would be reverted to the beginning values diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 22a3101b75..264454adac 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3403,8 +3403,7 @@ void PrintConfigDef::init_fff_params() def = this->add("prefer_clockwise_movements", coBool); def->label = L("Prefer clockwise movements"); - def->tooltip = L("This setting makes the printer print loops clockwise instead of counterclockwise. " - "Most printers don't need to print loops clockwise."); + def->tooltip = L("This setting makes the printer print loops clockwise instead of counterclockwise."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); @@ -4043,6 +4042,14 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); + def = this->add("zcorrection_layers", coInt); + def->label = L("Z compensation"); + def->category = L("Advanced"); + def->tooltip = L("Number of layers to Z correct to avoid cross layer bleed"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + def = this->add("gamma_correction", coFloat); def->label = L("Printer gamma correction"); def->full_label = L("Printer gamma correction"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 9cf6ae8038..7cd88fd6ff 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1174,6 +1174,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, material_correction_y)) ((ConfigOptionFloat, material_correction_z)) ((ConfigOptionEnum, material_print_speed)) + ((ConfigOptionInt, zcorrection_layers)) + ((ConfigOptionFloatNullable, material_ow_support_pillar_diameter)) ((ConfigOptionFloatNullable, material_ow_branchingsupport_pillar_diameter)) ((ConfigOptionFloatNullable, material_ow_support_head_front_diameter)) diff --git a/src/libslic3r/SLA/ZCorrection.cpp b/src/libslic3r/SLA/ZCorrection.cpp new file mode 100644 index 0000000000..5843d8858b --- /dev/null +++ b/src/libslic3r/SLA/ZCorrection.cpp @@ -0,0 +1,130 @@ +#include "ZCorrection.hpp" + +#include "Execution/ExecutionTBB.hpp" + +#include "libslic3r/ClipperUtils.hpp" + +namespace Slic3r { namespace sla { + +std::vector apply_zcorrection( + const std::vector &slices, size_t layers) +{ + return zcorr_detail::apply_zcorrection(ex_tbb, slices, layers); +} + +std::vector apply_zcorrection(const std::vector &slices, + const std::vector &grid, + float depth) +{ + return zcorr_detail::apply_zcorrection(ex_tbb, slices, grid, depth); +} + +namespace zcorr_detail { + +DepthMap create_depthmap(const std::vector &slices, + const std::vector &grid, + size_t max_depth) +{ + struct DepthPoly { + size_t depth = 0; + ExPolygons contour; + }; + + DepthMap ret; + + if (slices.empty() || slices.size() != grid.size()) + return ret; + + size_t depth_limit = max_depth > 0 ? max_depth : slices.size(); + ret.resize(slices.size()); + + ret.front() = DepthMapLayer{ {size_t{0}, slices.front()} }; + + for (size_t i = 0; i < slices.size() - 1; ++i) { + DepthMapLayer &depths_current = ret[i]; + DepthMapLayer &depths_nxt = ret[i + 1]; + + for (const auto &[depth, cntrs] : depths_current) { + DepthPoly common; + + common.contour = intersection_ex(slices[i + 1], cntrs); + common.depth = std::min(depth_limit, depth + 1); + + DepthPoly overhangs; + overhangs.contour = diff_ex(slices[i + 1], cntrs); + + if (!common.contour.empty()) { + std::copy(common.contour.begin(), common.contour.end(), + std::back_inserter(depths_nxt[common.depth])); + } + + if (!overhangs.contour.empty()) { + std::copy(overhangs.contour.begin(), overhangs.contour.end(), + std::back_inserter(depths_nxt[overhangs.depth])); + } + } + + for(auto &[i, cntrs] : depths_nxt) { + depths_nxt[i] = union_ex(cntrs); + } + } + + return ret; +} + +void apply_zcorrection(DepthMap &dmap, size_t layers) +{ + for (size_t lyr = 0; lyr < dmap.size(); ++lyr) { + size_t threshold = std::min(lyr, layers); + + auto &dlayer = dmap[lyr]; + + for (auto it = dlayer.begin(); it != dlayer.end();) + if (it->first < threshold) + it = dlayer.erase(it); + else + ++it; + } +} + +ExPolygons merged_layer(const DepthMapLayer &dlayer) +{ + using namespace Slic3r; + + ExPolygons out; + for (auto &[i, cntrs] : dlayer) { + std::copy(cntrs.begin(), cntrs.end(), std::back_inserter(out)); + } + + out = union_ex(out); + + return out; +} + +std::vector depthmap_to_slices(const DepthMap &dm) +{ + auto out = reserve_vector(dm.size()); + for (const auto &dlayer : dm) { + out.emplace_back(merged_layer(dlayer)); + } + + return out; +} + +ExPolygons intersect_layers(const std::vector &slices, + size_t layer_from, size_t layers_down) +{ + size_t drill_to = std::min(layer_from, layers_down); + auto drill_to_layer = static_cast(layer_from - drill_to); + + ExPolygons merged_lyr = slices[layer_from]; + for (int i = layer_from; i >= drill_to_layer; --i) + merged_lyr = intersection_ex(merged_lyr, slices[i]); + + return merged_lyr; +} + +} // namespace zcorr_detail + +} // namespace sla +} // namespace Slic3r diff --git a/src/libslic3r/SLA/ZCorrection.hpp b/src/libslic3r/SLA/ZCorrection.hpp new file mode 100644 index 0000000000..45c3e0cd76 --- /dev/null +++ b/src/libslic3r/SLA/ZCorrection.hpp @@ -0,0 +1,84 @@ +#ifndef ZCORRECTION_HPP +#define ZCORRECTION_HPP + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Execution/Execution.hpp" + +namespace Slic3r { +namespace sla { + +std::vector apply_zcorrection(const std::vector &slices, + size_t layers); + +std::vector apply_zcorrection(const std::vector &slices, + const std::vector &grid, + float depth); + +namespace zcorr_detail { + +ExPolygons intersect_layers(const std::vector &slices, + size_t layer_from, + size_t layers_down); + +template +std::vector apply_zcorrection(Ex ep, + const std::vector &slices, + size_t layers) +{ + std::vector output(slices.size()); + + execution::for_each(ep, size_t{0}, slices.size(), + [&output, &slices, layers] (size_t lyr) { + output[lyr] = intersect_layers(slices, lyr, layers); + }, execution::max_concurrency(ep)); + + return output; +} + +inline size_t depth_to_layers(const std::vector &grid, + size_t from_layer, + float depth) +{ + size_t depth_layers = 0; + while (from_layer > depth_layers && + grid[from_layer - depth_layers] > grid[from_layer] - depth) + depth_layers++; + + return depth_layers; +} + +template +std::vector apply_zcorrection(Ex ep, + const std::vector &slices, + const std::vector &grid, + float depth) +{ + std::vector output(slices.size()); + + execution::for_each(ep, size_t{0}, slices.size(), + [&output, &slices, &grid, depth] (size_t lyr) { + output[lyr] = intersect_layers(slices, lyr, + depth_to_layers(grid, lyr, depth)); + }, execution::max_concurrency(ep)); + + return output; +} + +using DepthMapLayer = std::map; +using DepthMap = std::vector; + +DepthMap create_depthmap(const std::vector &slices, + const std::vector &grid, size_t max_depth = 0); + +void apply_zcorrection(DepthMap &dmap, size_t layers); + +ExPolygons merged_layer(const DepthMapLayer &dlayer); + +std::vector depthmap_to_slices(const DepthMap &dm); + +} // namespace zcorr_detail + +} // namespace sla +} // namespace Slic3r + +#endif // ZCORRECTION_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 8116cae2ce..4dbc71b954 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -842,6 +842,7 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector #include #include +#include #include #include @@ -129,6 +130,11 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin if (idx < slices.size()) slices[idx] = elephant_foot_compensation(slices[idx], min_w, efc(i)); } + + if (o == soModel) { // Z correction applies only to the model slices + slices = sla::apply_zcorrection(slices, + m_print->m_material_config.zcorrection_layers.getInt()); + } } indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index b27afd7c3c..21290486ad 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -18,6 +18,8 @@ #include "BitmapCache.hpp" #include "Camera.hpp" +#include "Gizmos/GLGizmoMmuSegmentation.hpp" + #include "libslic3r/BuildVolume.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" @@ -748,16 +750,19 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab if (to_render.empty()) return; - GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); - if (shader == nullptr) - return; - - GLShaderProgram* sink_shader = GUI::wxGetApp().get_shader("flat"); + GLShaderProgram* curr_shader = GUI::wxGetApp().get_current_shader(); + GLShaderProgram* sink_shader = GUI::wxGetApp().get_shader("flat"); #if SLIC3R_OPENGL_ES GLShaderProgram* edges_shader = GUI::wxGetApp().get_shader("dashed_lines"); #else GLShaderProgram* edges_shader = GUI::OpenGLManager::get_gl_info().is_core_profile() ? GUI::wxGetApp().get_shader("dashed_thick_lines") : GUI::wxGetApp().get_shader("flat"); #endif // SLIC3R_OPENGL_ES + GLShaderProgram* mmu_painted_shader = GUI::wxGetApp().get_shader("mm_gouraud"); + if (curr_shader == nullptr || sink_shader == nullptr || edges_shader == nullptr || mmu_painted_shader == nullptr) + return; + + GLShaderProgram* shader = curr_shader; + shader->stop_using(); if (type == ERenderType::Transparent) { glsafe(::glEnable(GL_BLEND)); @@ -769,12 +774,41 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab if (disable_cullface) glsafe(::glDisable(GL_CULL_FACE)); + + // This block is here to render the pained triangles. It is not very nice, but it works. + // There is a cache that holds the OpenGL models of the painted areas to render, one for + // each ModelVolume. The cache is invalidated based on changes in extruder_colors, + // default extruder idx and timestamp of the painted data. The data belonging to objects + // // which no longer exist are removed from the cache periodically. + const ModelObjectPtrs& model_objects = GUI::wxGetApp().model().objects; + const std::vector extruders_colors = GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + const bool is_render_as_mmu_painted_enabled = !model_objects.empty() && !extruders_colors.empty(); + + if (m_mm_paint_cache.extruders_colors != extruders_colors) { + m_mm_paint_cache.extruders_colors = extruders_colors; + m_mm_paint_cache.volume_data.clear(); + } + auto time_now = std::chrono::system_clock::now(); + + + + for (GLVolumeWithIdAndZ& volume : to_render) { - const Transform3d& world_matrix = volume.first->world_matrix(); + if (!volume.first->is_active) + continue; + + const Transform3d world_matrix = volume.first->world_matrix(); + const Matrix3d world_matrix_inv_transp = world_matrix.linear().inverse().transpose(); + const Matrix3d view_normal_matrix = view_matrix.linear() * world_matrix_inv_transp; + const int obj_idx = volume.first->object_idx(); + const int vol_idx = volume.first->volume_idx(); + const bool render_as_mmu_painted = is_render_as_mmu_painted_enabled && !volume.first->selected && + !volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower && obj_idx >= 0 && vol_idx >= 0 && + !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty() && + type != GLVolumeCollection::ERenderType::Transparent; // to filter out shells (not very nice) volume.first->set_render_color(true); // render sinking contours of non-hovered volumes - shader->stop_using(); if (sink_shader != nullptr) { sink_shader->start_using(); if (m_show_sinking_contours) { @@ -785,50 +819,114 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab } sink_shader->stop_using(); } - shader->start_using(); - shader->set_uniform("z_range", m_z_range); - shader->set_uniform("clipping_plane", m_clipping_plane); - shader->set_uniform("use_color_clip_plane", m_use_color_clip_plane); - shader->set_uniform("color_clip_plane", m_color_clip_plane); - shader->set_uniform("uniform_color_clip_plane_1", m_color_clip_plane_colors[0]); - shader->set_uniform("uniform_color_clip_plane_2", m_color_clip_plane_colors[1]); - shader->set_uniform("print_volume.type", static_cast(m_print_volume.type)); - shader->set_uniform("print_volume.xy_data", m_print_volume.data); - shader->set_uniform("print_volume.z_data", m_print_volume.zs); - shader->set_uniform("volume_world_matrix", world_matrix); - shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); - shader->set_uniform("slope.volume_world_normal_matrix", static_cast(world_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast())); - shader->set_uniform("slope.normal_z", m_slope.normal_z); + if (render_as_mmu_painted && shader != mmu_painted_shader) + shader = mmu_painted_shader; + else if (!render_as_mmu_painted && shader != curr_shader) + shader = curr_shader; + + if (render_as_mmu_painted) { + shader->start_using(); + const std::array clp_data = { 0.0f, 0.0f, 1.0f, FLT_MAX }; + const std::array z_range = { -FLT_MAX, FLT_MAX }; + const bool is_left_handed = volume.first->is_left_handed(); + shader->set_uniform("volume_world_matrix", world_matrix); + shader->set_uniform("volume_mirrored", is_left_handed); + shader->set_uniform("clipping_plane", clp_data); + shader->set_uniform("z_range", z_range); + shader->set_uniform("view_model_matrix", view_matrix * world_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; + const size_t extruder_idx = ModelVolume::get_extruder_color_idx(model_volume, GUI::wxGetApp().extruders_edited_cnt()); + + + // This block retrieves the painted geometry from the cache or adds it to it. + ObjectID vol_id = model_volume.id(); + auto it = m_mm_paint_cache.volume_data.find(vol_id); + GUI::TriangleSelectorMmGui* ts = nullptr; + uint64_t timestamp = model_volume.mm_segmentation_facets.timestamp(); + if (it == m_mm_paint_cache.volume_data.end() || it->second.extruder_id != extruder_idx || timestamp != it->second.mm_timestamp) { + auto ts_uptr = std::make_unique(model_volume.mesh(), m_mm_paint_cache.extruders_colors, m_mm_paint_cache.extruders_colors[extruder_idx]); + ts = ts_uptr.get(); + ts->deserialize(model_volume.mm_segmentation_facets.get_data(), true); + ts->request_update_render_data(); + m_mm_paint_cache.volume_data[vol_id] = MMPaintCachePerVolume{ extruder_idx, std::move(ts_uptr), std::chrono::system_clock::now(), timestamp }; + } + else { + ts = it->second.triangle_selector_mm.get(); + it->second.time_used = time_now; + } + + + ts->render(nullptr, world_matrix); + + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + + shader->stop_using(); + } + else { + shader->start_using(); + shader->set_uniform("z_range", m_z_range); + shader->set_uniform("clipping_plane", m_clipping_plane); + shader->set_uniform("use_color_clip_plane", m_use_color_clip_plane); + shader->set_uniform("color_clip_plane", m_color_clip_plane); + shader->set_uniform("uniform_color_clip_plane_1", m_color_clip_plane_colors[0]); + shader->set_uniform("uniform_color_clip_plane_2", m_color_clip_plane_colors[1]); + shader->set_uniform("print_volume.type", static_cast(m_print_volume.type)); + shader->set_uniform("print_volume.xy_data", m_print_volume.data); + shader->set_uniform("print_volume.z_data", m_print_volume.zs); + shader->set_uniform("volume_world_matrix", world_matrix); + shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); + shader->set_uniform("slope.volume_world_normal_matrix", static_cast(world_matrix_inv_transp.cast())); + shader->set_uniform("slope.normal_z", m_slope.normal_z); #if ENABLE_ENVIRONMENT_MAP - unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id(); - bool use_environment_texture = environment_texture_id > 0 && GUI::wxGetApp().app_config->get_bool("use_environment_map"); - shader->set_uniform("use_environment_tex", use_environment_texture); - if (use_environment_texture) - glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id)); + unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id(); + bool use_environment_texture = environment_texture_id > 0 && GUI::wxGetApp().app_config->get_bool("use_environment_map"); + shader->set_uniform("use_environment_tex", use_environment_texture); + if (use_environment_texture) + glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id)); #endif // ENABLE_ENVIRONMENT_MAP - glcheck(); + glcheck(); - volume.first->model.set_color(volume.first->render_color); - const Transform3d model_matrix = world_matrix; - shader->set_uniform("view_model_matrix", view_matrix * model_matrix); - shader->set_uniform("projection_matrix", projection_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_normal_matrix", view_normal_matrix); - volume.first->render(); + volume.first->model.set_color(volume.first->render_color); + shader->set_uniform("view_model_matrix", view_matrix * world_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + volume.first->render(); #if ENABLE_ENVIRONMENT_MAP - if (use_environment_texture) - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + if (use_environment_texture) + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); #endif // ENABLE_ENVIRONMENT_MAP - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + shader->stop_using(); + } } + + // Purge the painted triangles cache from everything that was not used for some time. + // Only do this occasionally (once a second). + using namespace std::chrono_literals; + static auto time_since_last_check = time_now; + if (time_now - time_since_last_check > 1000ms) + for (auto it = m_mm_paint_cache.volume_data.begin(); it != m_mm_paint_cache.volume_data.end(); ) { + auto it_delete = it; // The iterator to the deleted element will be invalidated, the others will not. + ++it; + if (time_now - it_delete->second.time_used > 5000ms) + m_mm_paint_cache.volume_data.erase(it_delete); + } + + if (m_show_sinking_contours) { - shader->stop_using(); if (sink_shader != nullptr) { sink_shader->start_using(); for (GLVolumeWithIdAndZ& volume : to_render) { @@ -840,12 +938,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDepthFunc(GL_LESS)); } } - sink_shader->start_using(); + sink_shader->stop_using(); } - shader->start_using(); } - shader->stop_using(); if (edges_shader != nullptr) { edges_shader->start_using(); if (m_show_non_manifold_edges && GUI::wxGetApp().app_config->get_bool("non_manifold_edges")) { @@ -855,7 +951,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab } edges_shader->stop_using(); } - shader->start_using(); + + curr_shader->start_using(); if (disable_cullface) glsafe(::glEnable(GL_CULL_FACE)); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 8c895fe51a..4edfaa2cab 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -19,6 +19,9 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Color.hpp" +#include "libslic3r/ObjectID.hpp" + +#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "GLModel.hpp" #include "MeshUtils.hpp" @@ -392,6 +395,18 @@ private: bool m_show_non_manifold_edges{ true }; bool m_use_raycasters{ true }; + struct MMPaintCachePerVolume { + size_t extruder_id; + std::unique_ptr triangle_selector_mm; + std::chrono::system_clock::time_point time_used; + uint64_t mm_timestamp; + }; + struct MMPaintCache { + std::vector extruders_colors; + std::map volume_data; + }; + mutable MMPaintCache m_mm_paint_cache; + public: GLVolumePtrs volumes; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 992fb38bac..d3094f79bd 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -914,6 +914,10 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); + html_window->Bind(wxEVT_HTML_LINK_CLICKED, [](wxHtmlLinkEvent& event) { + wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref()); + event.Skip(false); + }); append(html_window, 0, wxEXPAND); list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { @@ -1003,9 +1007,19 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector(\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); } else { + bool has_medical = false; + for (const Preset *printer : materials->printers) { + if (printer->vendor && printer->vendor->id == "PrusaProMedical") { + has_medical = true; + } + } + // TRN PrusaSlicer-Medical ConfigWizard: Materials" + wxString zero_line = _L("The list of validated workflows for Medical One can be found in this article. Profiles for other materials are not verified by the material manufacturer and therefore may not correspond to the current version of the material."); + if (!has_medical) { + zero_line.Clear(); + } // TRN ConfigWizard: Materials : "%1%" = "Filaments"/"SLA materials" wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - if (all_printers) { // TRN ConfigWizard: Materials : "%1%" = "filament"/"SLA material" wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); @@ -1016,12 +1030,14 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector" "" "" - "%s

%s" + "" + "%s

%s

%s" "
" "" "" , bgr_clr_str , text_clr_str + , zero_line , first_line , second_line ); @@ -1039,13 +1055,16 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector" "" "" - "%s

%s" + "" + "%s

%s

%s" "" "" , bgr_clr_str , text_clr_str + , zero_line , first_line - , second_line); + , second_line + ); for (size_t i = 0; i < printer_names.size(); ++i) { text += wxString::Format("", boost::nowide::widen(printer_names[i])); diff --git a/src/slic3r/GUI/FrequentlyChangedParameters.cpp b/src/slic3r/GUI/FrequentlyChangedParameters.cpp index b7b74b2c2c..def49b0e9b 100644 --- a/src/slic3r/GUI/FrequentlyChangedParameters.cpp +++ b/src/slic3r/GUI/FrequentlyChangedParameters.cpp @@ -175,7 +175,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) const bool use_custom_matrix = (project_config.option("wiping_volumes_use_custom_matrix"))->value; const std::vector &init_matrix = (project_config.option("wiping_volumes_matrix"))->values; - const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_color_strings_from_plater_config(); // Extract the relevant config options, even values from possibly modified presets. const double default_purge = static_cast(preset_bundle->printers.get_edited_preset().config.option("multimaterial_purging"))->value; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5a84b6b84b..549c8d05ae 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4674,7 +4674,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const camera.apply_projection(volumes_box, near_z, far_z); const ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects; - std::vector extruders_colors = get_extruders_colors(); + std::vector extruders_colors = GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); const bool is_enabled_painted_thumbnail = !model_objects.empty() && !extruders_colors.empty(); if (thumbnail_params.transparent_background) @@ -4724,7 +4724,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const if (render_as_painted) { const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; - const size_t extruder_idx = get_extruder_color_idx(model_volume, extruders_count); + const size_t extruder_idx = ModelVolume::get_extruder_color_idx(model_volume, extruders_count); TriangleSelectorMmGui ts(model_volume.mesh(), extruders_colors, extruders_colors[extruder_idx]); ts.deserialize(model_volume.mm_segmentation_facets.get_data(), true); ts.request_update_render_data(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index f927171927..a9968afe69 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -419,7 +419,7 @@ void Preview::create_sliders() }); m_layers_slider->set_callback_on_get_extruder_colors([]() -> std::vector { - return wxGetApp().plater()->get_extruder_colors_from_plater_config(); + return wxGetApp().plater()->get_extruder_color_strings_from_plater_config(); }); m_layers_slider->set_callback_on_get_print([]() -> const Print& { @@ -603,7 +603,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee check_layers_slider_values(ticks_info_from_model.gcodes, layers_z); //first of all update extruder colors to avoid crash, when we are switching printer preset from MM to SM - m_layers_slider->SetExtruderColors(plater->get_extruder_colors_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result)); + m_layers_slider->SetExtruderColors(plater->get_extruder_color_strings_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result)); m_layers_slider->SetSliderValues(layers_z); assert(m_layers_slider->GetMinPos() == 0); @@ -933,12 +933,12 @@ void Preview::load_print_as_fff(bool keep_z_range) const bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); const bool is_pregcode_preview = !gcode_preview_data_valid && wxGetApp().is_editor(); - const std::vector tool_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result); + const std::vector tool_colors = wxGetApp().plater()->get_extruder_color_strings_from_plater_config(m_gcode_result); const std::vector& color_print_values = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_gcode_result->custom_gcode_per_print_z; std::vector color_print_colors; if (!color_print_values.empty()) { - color_print_colors = wxGetApp().plater()->get_colors_for_color_print(m_gcode_result); + color_print_colors = wxGetApp().plater()->get_color_strings_for_color_print(m_gcode_result); color_print_colors.push_back("#808080"); // gray color for pause print or custom G-code } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 286c0969a3..66bc232b9c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1345,18 +1345,38 @@ bool GLGizmoEmboss::process(bool make_snapshot) void GLGizmoEmboss::close() { - // remove volume when text is empty if (m_volume != nullptr && - m_volume->text_configuration.has_value() && - is_text_empty(m_text)) { - Plater &p = *wxGetApp().plater(); - // is the text object? - if (m_volume->is_the_only_one_part()) { - // delete whole object - p.remove(m_parent.get_selection().get_object_idx()); - } else { - // delete text volume - p.remove_selected(); + m_volume->text_configuration.has_value() ){ + + // remove volume when text is empty + if (is_text_empty(m_text)) { + Plater &p = *wxGetApp().plater(); + // is the text object? + if (m_volume->is_the_only_one_part()) { + // delete whole object + p.remove(m_parent.get_selection().get_object_idx()); + } else { + // delete text volume + p.remove_selected(); + } + } + + // Fix phanthom transformation + // appear when right click into scene during edit Rotation in input (click "Edit" button) + const GLVolume *gl_volume_ptr = m_parent.get_selection().get_first_volume(); + if (gl_volume_ptr != nullptr) { + const Transform3d &v_tr = m_volume->get_matrix(); + const Transform3d &gl_v_tr = gl_volume_ptr->get_volume_transformation().get_matrix(); + + const Matrix3d &v_rot = v_tr.linear(); + const Matrix3d &gl_v_rot = gl_v_tr.linear(); + const Vec3d &v_move = v_tr.translation(); + const Vec3d &gl_v_move = gl_v_tr.translation(); + if (!is_approx(v_rot, gl_v_rot)) { + m_parent.do_rotate(rotation_snapshot_name); + } else if (!is_approx(v_move, gl_v_move)){ + m_parent.do_move(move_snapshot_name); + } } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 2880c9ea42..6c0031d3f2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -62,14 +62,6 @@ bool GLGizmoMmuSegmentation::on_is_activable() const return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1; } -std::vector get_extruders_colors() -{ - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - std::vector ret; - decode_colors(colors, ret); - return ret; -} - static std::vector get_extruders_names() { size_t extruders_count = wxGetApp().extruders_edited_cnt(); @@ -98,7 +90,7 @@ static std::vector get_extruder_id_for_volumes(const ModelObject &model_obj void GLGizmoMmuSegmentation::init_extruders_data() { m_original_extruders_names = get_extruders_names(); - m_original_extruders_colors = get_extruders_colors(); + m_original_extruders_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); m_modified_extruders_colors = m_original_extruders_colors; m_first_selected_extruder_idx = 0; m_second_selected_extruder_idx = 1; @@ -160,7 +152,7 @@ void GLGizmoMmuSegmentation::data_changed(bool is_serializing) ModelObject *model_object = m_c->selection_info()->model_object(); if (int prev_extruders_count = int(m_original_extruders_colors.size()); - prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) { + prev_extruders_count != wxGetApp().extruders_edited_cnt() || wxGetApp().plater()->get_extruder_colors_from_plater_config() != m_original_extruders_colors) { if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)) show_notification_extruders_limit_exceeded(); @@ -544,7 +536,7 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors() // This mesh does not account for the possible Z up SLA offset. const TriangleMesh *mesh = &mv->mesh(); - size_t extruder_idx = get_extruder_color_idx(*mv, extruders_count); + const size_t extruder_idx = ModelVolume::get_extruder_color_idx(*mv, extruders_count); m_triangle_selectors.emplace_back(std::make_unique(*mesh, m_modified_extruders_colors, m_original_extruders_colors[extruder_idx])); // Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize(). m_triangle_selectors.back()->deserialize(mv->mm_segmentation_facets.get_data(), false); @@ -560,7 +552,7 @@ void GLGizmoMmuSegmentation::update_from_model_object() // Extruder colors need to be reloaded before calling init_model_triangle_selectors to render painted triangles // using colors from loaded 3MF and not from printer profile in Slicer. if (int prev_extruders_count = int(m_original_extruders_colors.size()); - prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) + prev_extruders_count != wxGetApp().extruders_edited_cnt() || wxGetApp().plater()->get_extruder_colors_from_plater_config() != m_original_extruders_colors) this->init_extruders_data(); this->init_model_triangle_selectors(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index ad4931058d..7bec4c78b6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -152,16 +152,6 @@ private: std::map m_desc; }; -std::vector get_extruders_colors(); - -inline size_t get_extruder_color_idx(const ModelVolume &model_volume, const int extruders_count) -{ - if (const int extruder_id = model_volume.extruder_id(); extruder_id <= 0 || extruder_id > extruders_count) - return 0; - else - return extruder_id - 1; -} - } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 50118376ca..7b55a99041 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -331,6 +331,37 @@ void GLGizmoSVG::volume_transformation_changed() calculate_scale(); } +void GLGizmoSVG::on_mouse_confirm_edit(const wxMouseEvent &mouse_event) { + // Fix phanthom transformation + // appear when mouse click into scene during edit Rotation in input (click "Edit" button) + // this must happen just before unselect selection (to find current volume) + static bool was_dragging = true; + if ((mouse_event.LeftUp() || mouse_event.RightUp()) && + m_parent.get_first_hover_volume_idx() < 0 && + !was_dragging && + m_volume != nullptr && + m_volume->is_svg() ) { + // current volume + const GLVolume *gl_volume_ptr = m_parent.get_selection().get_first_volume(); + assert(gl_volume_ptr->geometry_id.first == m_volume->id().id); + if (gl_volume_ptr != nullptr) { + const Transform3d &v_tr = m_volume->get_matrix(); + const Transform3d &gl_v_tr = gl_volume_ptr->get_volume_transformation().get_matrix(); + + const Matrix3d &v_rot = v_tr.linear(); + const Matrix3d &gl_v_rot = gl_v_tr.linear(); + const Vec3d &v_move = v_tr.translation(); + const Vec3d &gl_v_move = gl_v_tr.translation(); + if (!is_approx(v_rot, gl_v_rot)) { + m_parent.do_rotate(rotation_snapshot_name); + } else if (!is_approx(v_move, gl_v_move)) { + m_parent.do_move(move_snapshot_name); + } + } + } + was_dragging = mouse_event.Dragging(); +} + bool GLGizmoSVG::on_mouse(const wxMouseEvent &mouse_event) { // not selected volume @@ -340,7 +371,7 @@ bool GLGizmoSVG::on_mouse(const wxMouseEvent &mouse_event) if (on_mouse_for_rotation(mouse_event)) return true; if (on_mouse_for_translate(mouse_event)) return true; - + on_mouse_confirm_edit(mouse_event); return false; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 103051c0fa..42cd3fc007 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -129,6 +129,7 @@ private: // process mouse event bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); bool on_mouse_for_translate(const wxMouseEvent &mouse_event); + void on_mouse_confirm_edit(const wxMouseEvent &mouse_event); void volume_transformation_changed(); diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 77c9714b94..ea689905c0 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -105,8 +105,8 @@ void SLAImportJob::prepare() { reset(); - auto path = p->import_dlg->get_path(); - auto nm = wxFileName(path); + const std::string path = p->import_dlg->get_path(); + auto nm = wxFileName(from_u8(path)); p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); if (p->path.empty()) { p->err = _u8L("The file does not exist."); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 90decf492e..aa8a840c2e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -302,6 +302,7 @@ struct Plater::priv static const std::regex pattern_any_amf; static const std::regex pattern_prusa; static const std::regex pattern_zip; + static const std::regex pattern_printRequest; priv(Plater *q, MainFrame *main_frame); ~priv(); @@ -521,7 +522,7 @@ struct Plater::priv void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); void show_action_buttons(const bool is_ready_to_slice) const; - + bool can_show_upload_to_connect() const; // Set the bed shape to a single closed 2D polygon(array of two element arrays), // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. @@ -599,6 +600,7 @@ const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::ica const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase); const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase); const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase); +const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase); Plater::priv::priv(Plater *q, MainFrame *main_frame) : q(q) @@ -895,6 +897,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) { + // There are multiple handlers and we want to notify all + evt.Skip(); std::string username; if (user_account->on_user_id_success(evt.data, username)) { std::string text = format(_u8L("Logged to Prusa Account as %1%."), username); @@ -991,6 +995,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->close_notification_of_type(NotificationType::SelectFilamentFromConnect); this->notification_manager->push_notification(NotificationType::SelectFilamentFromConnect, NotificationManager::NotificationLevel::WarningNotificationLevel, msg); }); + + this->q->Bind(EVT_UA_REFRESH_TIME, [this](UserAccountTimeEvent& evt) { + this->user_account->set_refresh_time(evt.data); + }); } wxGetApp().other_instance_message_handler()->init(this->q); @@ -1199,6 +1207,14 @@ std::vector Plater::priv::load_files(const std::vector& input_ const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf); const bool type_prusa = std::regex_match(path.string(), pattern_prusa); + const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest); + + if (type_printRequest && printer_technology != ptSLA) { + Slic3r::GUI::show_info(nullptr, + _L("PrintRequest can only be loaded if an SLA printer is selected."), + _L("Error!")); + continue; + } Slic3r::Model model; bool is_project_file = type_prusa; @@ -1371,7 +1387,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ convert_model_if(model, answer_convert_from_imperial_units == wxID_YES); } - if (model.looks_like_multipart_object()) { + if (!type_printRequest && model.looks_like_multipart_object()) { if (answer_consider_as_multi_part_objects == wxOK_DEFAULT) { RichMessageDialog dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" @@ -1410,6 +1426,58 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (!model_object->instances.empty()) model_object->ensure_on_bed(is_project_file); + if (type_printRequest) { + for (ModelInstance* obj_instance : model_object->instances) { + obj_instance->set_offset(obj_instance->get_offset() + Slic3r::to_3d(this->bed.build_volume().bed_center(), -model_object->origin_translation(2))); + } + } + } + if (type_printRequest) { + assert(model.materials.size()); + + for (const auto& material : model.materials) { + std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, + Preset::remove_suffix_modified(material.first)); + Preset* prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); + if (!prst) { //did not find compatible profile + // try find alias of material comaptible with another print profile - if exists, use the print profile + auto& prints = wxGetApp().preset_bundle->sla_prints; + std::string edited_print_name = prints.get_edited_preset().name; + bool found = false; + for (auto it = prints.begin(); it != prints.end(); ++it) + { + if (it->name != edited_print_name) { + BOOST_LOG_TRIVIAL(error) << it->name; + wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(it->name, false); + preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, + Preset::remove_suffix_modified(material.first)); + prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); + if (prst) { + found = true; + break; + } + } + } + if (!found) { + // return to original print profile + wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(edited_print_name, false); + std::string notif_text = into_u8(_L("Material preset was not loaded:")); + notif_text += "\n - " + preset_name; + q->get_notification_manager()->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, notif_text); + break; + } + } + + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + if (preset_bundle->sla_materials.get_selected_preset_name() != preset_name) { + preset_bundle->sla_materials.select_preset_by_name(preset_name, false, true); + preset_bundle->tmp_installed_presets = { preset_name }; + q->notify_about_installed_presets(); + wxGetApp().load_current_presets(false);// For this case we shouldn't check printer_presets + } + break; + } } if (one_by_one) { @@ -3556,6 +3624,24 @@ bool Plater::priv::can_layers_editing() const return layers_height_allowed(); } +bool Plater::priv::can_show_upload_to_connect() const +{ + if (!user_account->is_logged()) { + return false; + } + const Preset& selected_printer = wxGetApp().preset_bundle->printers.get_selected_preset(); + std::string vendor_id; + if (selected_printer.vendor ) { + vendor_id = selected_printer.vendor->id; + } else if (std::string inherits = selected_printer.inherits(); !inherits.empty()) { + const Preset* parent = wxGetApp().preset_bundle->printers.find_preset(inherits); + if (parent && parent->vendor) { + vendor_id = parent->vendor->id; + } + } + return vendor_id.compare(0, 5, "Prusa") == 0; +} + void Plater::priv::show_action_buttons(const bool ready_to_slice_) const { // Cache this value, so that the callbacks from the RemovableDriveManager may repeat that value when calling show_action_buttons(). @@ -3566,7 +3652,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); const auto print_host_opt = selected_printer_config ? selected_printer_config->option("print_host") : nullptr; const bool send_gcode_shown = print_host_opt != nullptr && !print_host_opt->value.empty(); - const bool connect_gcode_shown = print_host_opt == nullptr && user_account->is_logged(); + const bool connect_gcode_shown = print_host_opt == nullptr && can_show_upload_to_connect(); // when a background processing is ON, export_btn and/or send_btn are showing if (get_config_bool("background_processing")) { @@ -4797,7 +4883,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect) bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*=false*/) { - const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp|zip)", std::regex::icase); + const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp|zip|printRequest)", std::regex::icase); const std::regex pattern_gcode_drop(".*[.](gcode|g|bgcode|bgc)", std::regex::icase); std::vector paths; @@ -5917,26 +6003,29 @@ void Plater::connect_gcode() */ const Preset* selected_printer_preset = &wxGetApp().preset_bundle->printers.get_selected_preset(); - const std::string set_ready = p->user_account->get_keyword_from_json(dialog_msg, "set_ready"); - const std::string position = p->user_account->get_keyword_from_json(dialog_msg, "position"); - const std::string wait_until = p->user_account->get_keyword_from_json(dialog_msg, "wait_until"); const std::string filename = p->user_account->get_keyword_from_json(dialog_msg, "filename"); - const std::string printer_uuid = p->user_account->get_keyword_from_json(dialog_msg, "printer_uuid"); const std::string team_id = p->user_account->get_keyword_from_json(dialog_msg, "team_id"); + std::string data_subtree = p->user_account->get_print_data_from_json(dialog_msg, "data"); + if (filename.empty() || team_id.empty() || data_subtree.empty()) { + std::string msg = _u8L("Failed to read response from Connect server. Upload is canceled."); + BOOST_LOG_TRIVIAL(error) << msg; + BOOST_LOG_TRIVIAL(error) << "Response: " << dialog_msg; + show_error(this, msg); + return; + } + + PhysicalPrinter ph_printer("connect_temp_printer", wxGetApp().preset_bundle->physical_printers.default_config(), *selected_printer_preset); ph_printer.config.set_key_value("host_type", new ConfigOptionEnum(htPrusaConnectNew)); // use existing structures to pass data ph_printer.config.opt_string("printhost_apikey") = team_id; - ph_printer.config.opt_string("print_host") = printer_uuid; DynamicPrintConfig* physical_printer_config = &ph_printer.config; PrintHostJob upload_job(physical_printer_config); assert(!upload_job.empty()); - upload_job.upload_data.set_ready = set_ready; - upload_job.upload_data.position = position; - upload_job.upload_data.wait_until = wait_until; + upload_job.upload_data.data_json = data_subtree; upload_job.upload_data.upload_path = boost::filesystem::path(filename); p->export_gcode(fs::path(), false, std::move(upload_job)); @@ -6315,7 +6404,7 @@ void Plater::on_activate(bool active) } // Get vector of extruder colors considering filament color, if extruder color is undefined. -std::vector Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result) const +std::vector Plater::get_extruder_color_strings_from_plater_config(const GCodeProcessorResult* const result) const { if (wxGetApp().is_gcode_viewer() && result != nullptr) return result->extruder_colors; @@ -6341,9 +6430,9 @@ std::vector Plater::get_extruder_colors_from_plater_config(const GC /* Get vector of colors used for rendering of a Preview scene in "Color print" mode * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z */ -std::vector Plater::get_colors_for_color_print(const GCodeProcessorResult* const result) const +std::vector Plater::get_color_strings_for_color_print(const GCodeProcessorResult* const result) const { - std::vector colors = get_extruder_colors_from_plater_config(result); + std::vector colors = get_extruder_color_strings_from_plater_config(result); colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size()); if (wxGetApp().is_gcode_viewer() && result != nullptr) { @@ -6362,6 +6451,22 @@ std::vector Plater::get_colors_for_color_print(const GCodeProcessor return colors; } +std::vector Plater::get_extruder_colors_from_plater_config() const +{ + std::vector colors = get_extruder_color_strings_from_plater_config(); + std::vector ret; + decode_colors(colors, ret); + return ret; +} + +std::vector Plater::get_colors_for_color_print() const +{ + std::vector colors = get_color_strings_for_color_print(); + std::vector ret; + decode_colors(colors, ret); + return ret; +} + wxString Plater::get_project_filename(const wxString& extension) const { return p->get_project_filename(extension); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 7d21846724..356592ee16 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -255,8 +255,11 @@ public: void force_print_bed_update(); // On activating the parent window. void on_activate(bool active); - std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; - std::vector get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const; + + std::vector get_extruder_color_strings_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_color_strings_for_color_print(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_extruder_colors_from_plater_config() const; + std::vector get_colors_for_color_print() const; void update_menus(); void show_action_buttons(const bool is_ready_to_slice) const; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 657123fd3c..fb084e14b7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -5341,7 +5341,16 @@ void TabSLAMaterial::build() opt.opt.label = axis; line.append_option(opt); } + optgroup->append_line(line); + optgroup->append_single_option_line("zcorrection_layers"); + + line = Line{ "", "" }; + line.full_width = 1; + // line.label_path = category_path + "recommended-thin-wall-thickness"; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_z_correction_to_mm_description); + }; optgroup->append_line(line); add_material_overrides_page(); @@ -5491,7 +5500,19 @@ void TabSLAMaterial::update() // m_update_cnt--; // // if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); + wxGetApp().mainframe->on_config_changed(m_config); +} + +void TabSLAMaterial::update_description_lines() +{ + if (m_active_page && m_active_page->title() == "Material" && m_z_correction_to_mm_description) { + auto cfg = m_preset_bundle->full_config(); + double lh = cfg.opt_float("layer_height"); + int zlayers = cfg.opt_int("zcorrection_layers"); + m_z_correction_to_mm_description->SetText(GUI::format_wxstr(_L("Current Z correction depth is: %1% mm"), zlayers * lh)); + } + + Tab::update_description_lines(); } void TabSLAMaterial::update_sla_prusa_specific_visibility() @@ -5519,6 +5540,8 @@ void TabSLAMaterial::clear_pages() for (auto& over_opt : m_overrides_options) over_opt.second = nullptr; + + m_z_correction_to_mm_description = nullptr; } void TabSLAMaterial::msw_rescale() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 277c72cb85..d6f536a8f1 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -571,6 +571,8 @@ class TabSLAMaterial : public Tab void update_material_overrides_page(); std::map m_overrides_options; + ogStaticText* m_z_correction_to_mm_description = nullptr; + public: TabSLAMaterial(wxBookCtrlBase* parent) : Tab(parent, _L("Materials"), Slic3r::Preset::TYPE_SLA_MATERIAL) {} @@ -586,6 +588,7 @@ public: void sys_color_changed() override; bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } void update_sla_prusa_specific_visibility() override; + void update_description_lines() override; }; class TabSLAPrint : public Tab diff --git a/src/slic3r/GUI/UserAccount.cpp b/src/slic3r/GUI/UserAccount.cpp index f8a5c6ac7c..8574ef9386 100644 --- a/src/slic3r/GUI/UserAccount.cpp +++ b/src/slic3r/GUI/UserAccount.cpp @@ -400,6 +400,43 @@ std::string UserAccount::get_keyword_from_json(const std::string& json, const st return out; } +std::string UserAccount::get_print_data_from_json(const std::string &json, const std::string &keyword) const +{ + // copy subtree string f.e. + // { "": {"param1": "something", "filename":"abcd.gcode", "param3":true}, "something_else" : 0 } + // into: {"param1": "something", "filename":"%1%", "param3":true, "size":%2%} + // yes there will be 2 placeholders for later format + + // this will fail if not flat subtree + size_t start_of_keyword = json.find("\""+keyword+"\""); + if (start_of_keyword == std::string::npos) + return {}; + size_t start_of_sub = json.find('{', start_of_keyword); + if (start_of_sub == std::string::npos) + return {}; + size_t end_of_sub = json.find('}', start_of_sub); + if (end_of_sub == std::string::npos) + return {}; + size_t start_of_filename = json.find("\"filename\"", start_of_sub); + if (start_of_filename == std::string::npos) + return {}; + size_t filename_doubledot = json.find(':', start_of_filename); + if (filename_doubledot == std::string::npos) + return {}; + size_t start_of_filename_data = json.find('\"', filename_doubledot); + if (start_of_filename_data == std::string::npos) + return {}; + size_t end_of_filename_data = json.find('\"', start_of_filename_data + 1); + if (end_of_filename_data == std::string::npos) + return {}; + size_t size = json.size(); + std::string result = json.substr(start_of_sub, start_of_filename_data - start_of_sub + 1); + result += "%1%"; + result += json.substr(end_of_filename_data, end_of_sub - end_of_filename_data); + result += ",\"size\":%2%}"; + return result; +} + void UserAccount::fill_supported_printer_models_from_json(const std::string& json, std::vector& result) const { try { diff --git a/src/slic3r/GUI/UserAccount.hpp b/src/slic3r/GUI/UserAccount.hpp index 533fdff970..d75d1d325f 100644 --- a/src/slic3r/GUI/UserAccount.hpp +++ b/src/slic3r/GUI/UserAccount.hpp @@ -69,6 +69,7 @@ public: // standalone utility methods std::string get_nozzle_from_json(const std::string& message) const; std::string get_keyword_from_json(const std::string& json, const std::string& keyword) const; + std::string get_print_data_from_json(const std::string &json, const std::string &keyword) const; void fill_supported_printer_models_from_json(const std::string& json, std::vector& result) const; void fill_material_from_json(const std::string& json, std::vector& result) const; @@ -78,6 +79,8 @@ public: std::string get_current_printer_uuid_from_connect(const std::string& selected_printer_id) const; void set_current_printer_data(const std::string& data) { m_current_printer_data_json_from_connect = data; } + + void set_refresh_time(int seconds) { m_communication->set_refresh_time(seconds); } private: void set_username(const std::string& username); diff --git a/src/slic3r/GUI/UserAccountCommunication.cpp b/src/slic3r/GUI/UserAccountCommunication.cpp index a3c4a74402..65dc3d2c64 100644 --- a/src/slic3r/GUI/UserAccountCommunication.cpp +++ b/src/slic3r/GUI/UserAccountCommunication.cpp @@ -133,22 +133,47 @@ bool load_secret(const std::string& opt, std::string& usr, std::string& psswd) } UserAccountCommunication::UserAccountCommunication(wxEvtHandler* evt_handler, AppConfig* app_config) - : m_evt_handler(evt_handler) + : wxEvtHandler() + , m_evt_handler(evt_handler) , m_app_config(app_config) + , m_polling_timer(new wxTimer(this)) + , m_token_timer(new wxTimer(this)) { - std::string access_token, refresh_token, shared_session_key; + Bind(wxEVT_TIMER, &UserAccountCommunication::on_token_timer, this, m_token_timer->GetId()); + Bind(wxEVT_TIMER, &UserAccountCommunication::on_polling_timer, this, m_polling_timer->GetId()); + + std::string access_token, refresh_token, shared_session_key, next_timeout; if (is_secret_store_ok()) { - std::string key0, key1; - load_secret("access_token", key0, access_token); - load_secret("refresh_token", key1, refresh_token); - assert(key0 == key1); + std::string key0, key1, key2, tokens; + if (load_secret("tokens", key0, tokens)) { + std::vector token_list; + boost::split(token_list, tokens, boost::is_any_of("|"), boost::token_compress_off); + assert(token_list.empty() || token_list.size() == 3); + access_token = token_list.size() > 0 ? token_list[0] : std::string(); + refresh_token = token_list.size() > 1 ? token_list[1] : std::string(); + next_timeout = token_list.size() > 2 ? token_list[2] : std::string(); + } else { + load_secret("access_token", key0, access_token); + load_secret("refresh_token", key1, refresh_token); + load_secret("access_token_timeout", key2, next_timeout); + assert(key0 == key1); + } shared_session_key = key0; + } else { access_token = m_app_config->get("access_token"); refresh_token = m_app_config->get("refresh_token"); shared_session_key = m_app_config->get("shared_session_key"); + next_timeout = m_app_config->get("access_token_timeout"); } - bool has_token = !access_token.empty() && !refresh_token.empty(); + long long next = next_timeout.empty() ? 0 : std::stoll(next_timeout); + long long remain_time = next - std::time(nullptr); + if (remain_time <= 0) { + access_token.clear(); + } else { + set_refresh_time((int)remain_time); + } + bool has_token = !refresh_token.empty(); m_session = std::make_unique(evt_handler, access_token, refresh_token, shared_session_key, m_app_config->get_bool("connect_polling")); init_session_thread(); // perform login at the start, but only with tokens @@ -159,6 +184,8 @@ UserAccountCommunication::UserAccountCommunication(wxEvtHandler* evt_handler, Ap UserAccountCommunication::~UserAccountCommunication() { + m_token_timer->Stop(); + m_polling_timer->Stop(); if (m_thread.joinable()) { // Stop the worker thread, if running. { @@ -167,7 +194,7 @@ UserAccountCommunication::~UserAccountCommunication() m_thread_stop = true; } m_thread_stop_condition.notify_all(); - // Wait for the worker thread to stop. + // Wait for the worker thread to stop m_thread.join(); } } @@ -178,13 +205,19 @@ void UserAccountCommunication::set_username(const std::string& username) { std::lock_guard lock(m_session_mutex); if (is_secret_store_ok()) { - save_secret("access_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_access_token() : std::string()); - save_secret("refresh_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_refresh_token() : std::string()); + std::string tokens; + if (m_remember_session) { + tokens = m_session->get_access_token() + + "|" + m_session->get_refresh_token() + + "|" + std::to_string(m_session->get_next_token_timeout()); + } + save_secret("tokens", m_session->get_shared_session_key(), tokens); } else { m_app_config->set("access_token", m_remember_session ? m_session->get_access_token() : std::string()); m_app_config->set("refresh_token", m_remember_session ? m_session->get_refresh_token() : std::string()); m_app_config->set("shared_session_key", m_remember_session ? m_session->get_shared_session_key() : std::string()); + m_app_config->set("access_token_timeout", m_remember_session ? GUI::format("%1%", m_session->get_next_token_timeout()) : "0"); } } } @@ -273,6 +306,7 @@ void UserAccountCommunication::do_clear() m_session->clear(); } set_username({}); + m_token_timer->Stop(); } void UserAccountCommunication::on_login_code_recieved(const std::string& url_message) @@ -348,16 +382,28 @@ void UserAccountCommunication::enqueue_printer_data_action(const std::string& uu } wakeup_session_thread(); } - +void UserAccountCommunication::enqueue_refresh() +{ + { + std::lock_guard lock(m_session_mutex); + if (!m_session->is_initialized()) { + BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in."; + return; + } + m_session->enqueue_refresh({}); + } + wakeup_session_thread(); +} void UserAccountCommunication::init_session_thread() { + assert(m_polling_timer); + m_polling_timer->Start(10000); m_thread = std::thread([this]() { for (;;) { - // Wait for 5 seconds or wakeup call { std::unique_lock lck(m_thread_stop_mutex); - m_thread_stop_condition.wait_for(lck, std::chrono::seconds(10), [this] { return m_thread_stop || m_thread_wakeup; }); + m_thread_stop_condition.wait_for(lck, std::chrono::seconds(88888), [this] { return m_thread_stop || m_thread_wakeup; }); } if (m_thread_stop) // Stop the worker thread. @@ -377,8 +423,10 @@ void UserAccountCommunication::init_session_thread() void UserAccountCommunication::on_activate_window(bool active) { - std::lock_guard lck(m_thread_stop_mutex); - m_window_is_active = active; + { + std::lock_guard lck(m_thread_stop_mutex); + m_window_is_active = active; + } } void UserAccountCommunication::wakeup_session_thread() @@ -390,6 +438,26 @@ void UserAccountCommunication::wakeup_session_thread() m_thread_stop_condition.notify_all(); } +void UserAccountCommunication::set_refresh_time(int seconds) +{ + assert(m_token_timer); + m_token_timer->Stop(); + int miliseconds = std::max(seconds * 1000 - 66666, 60000); + m_token_timer->StartOnce(miliseconds); +} + +void UserAccountCommunication::on_token_timer(wxTimerEvent& evt) +{ + enqueue_refresh(); +} +void UserAccountCommunication::on_polling_timer(wxTimerEvent& evt) +{ + if (!m_window_is_active) { + return; + } + wakeup_session_thread(); +} + std::string CodeChalengeGenerator::generate_chalenge(const std::string& verifier) { std::string code_challenge; diff --git a/src/slic3r/GUI/UserAccountCommunication.hpp b/src/slic3r/GUI/UserAccountCommunication.hpp index b630b95ec2..133d2c8ca0 100644 --- a/src/slic3r/GUI/UserAccountCommunication.hpp +++ b/src/slic3r/GUI/UserAccountCommunication.hpp @@ -27,7 +27,8 @@ private: std::string sha256(const std::string& input); }; -class UserAccountCommunication { +class UserAccountCommunication : public wxEvtHandler +{ public: UserAccountCommunication(wxEvtHandler* evt_handler, AppConfig* app_config); ~UserAccountCommunication(); @@ -44,6 +45,7 @@ public: void enqueue_avatar_action(const std::string& url); void enqueue_test_connection(); void enqueue_printer_data_action(const std::string& uuid); + void enqueue_refresh(); // Callbacks - called from UI after receiving Event from Session thread. Some might use Session thread. // @@ -64,6 +66,10 @@ public: void set_polling_enabled(bool enabled); // we have map of uuids and printer_models - set polling action to lightweight STATUS action void on_uuid_map_success(); + + void set_refresh_time(int seconds); + void on_token_timer(wxTimerEvent& evt); + void on_polling_timer(wxTimerEvent& evt); private: std::unique_ptr m_session; std::thread m_thread; @@ -73,6 +79,8 @@ private: bool m_thread_stop { false }; bool m_thread_wakeup{ false }; bool m_window_is_active{ true }; + wxTimer* m_polling_timer; + std::string m_code_verifier; wxEvtHandler* m_evt_handler; AppConfig* m_app_config; @@ -80,11 +88,14 @@ private: std::string m_username; bool m_remember_session { true }; // if default is true, on every login Remember me will be checked. + wxTimer* m_token_timer; + wxEvtHandler* m_timer_evt_handler; + void wakeup_session_thread(); void init_session_thread(); void login_redirect(); std::string client_id() const { return "oamhmhZez7opFosnwzElIgE2oGgI2iJORSkw587O"; } - + }; diff --git a/src/slic3r/GUI/UserAccountSession.cpp b/src/slic3r/GUI/UserAccountSession.cpp index 69c974001f..2f6287380a 100644 --- a/src/slic3r/GUI/UserAccountSession.cpp +++ b/src/slic3r/GUI/UserAccountSession.cpp @@ -29,6 +29,7 @@ wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, UserAccountSuccessEvent wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_RESET, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); +wxDEFINE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent); void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) const { @@ -124,6 +125,7 @@ void UserAccountSession::token_success_callback(const std::string& body) { // Data we need std::string access_token, refresh_token, shared_session_key; + int expires_in = 300; try { std::stringstream ss(body); pt::ptree ptree; @@ -132,6 +134,7 @@ void UserAccountSession::token_success_callback(const std::string& body) const auto access_token_optional = ptree.get_optional("access_token"); const auto refresh_token_optional = ptree.get_optional("refresh_token"); const auto shared_session_key_optional = ptree.get_optional("shared_session_key"); + const auto expires_in_optional = ptree.get_optional("expires_in"); if (access_token_optional) access_token = *access_token_optional; @@ -139,6 +142,9 @@ void UserAccountSession::token_success_callback(const std::string& body) refresh_token = *refresh_token_optional; if (shared_session_key_optional) shared_session_key = *shared_session_key_optional; + assert(expires_in_optional); + if (expires_in_optional) + expires_in = *expires_in_optional; } catch (const std::exception&) { std::string msg = "Could not parse server response after code exchange."; @@ -163,7 +169,9 @@ void UserAccountSession::token_success_callback(const std::string& body) m_access_token = access_token; m_refresh_token = refresh_token; m_shared_session_key = shared_session_key; + m_next_token_timeout = std::time(nullptr) + expires_in; enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID, nullptr, nullptr, {}); + wxQueueEvent(p_evt_handler, new UserAccountTimeEvent(EVT_UA_REFRESH_TIME, expires_in)); } void UserAccountSession::code_exchange_fail_callback(const std::string& body) @@ -181,6 +189,7 @@ void UserAccountSession::enqueue_test_with_refresh() m_priority_action_queue.push({ UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN, nullptr, std::bind(&UserAccountSession::enqueue_refresh, this, std::placeholders::_1), {} }); } + void UserAccountSession::enqueue_refresh(const std::string& body) { assert(!m_refresh_token.empty()); diff --git a/src/slic3r/GUI/UserAccountSession.hpp b/src/slic3r/GUI/UserAccountSession.hpp index 8aaf161458..77aa9e29bc 100644 --- a/src/slic3r/GUI/UserAccountSession.hpp +++ b/src/slic3r/GUI/UserAccountSession.hpp @@ -16,6 +16,7 @@ namespace GUI { using OpenPrusaAuthEvent = Event; using UserAccountSuccessEvent = Event; using UserAccountFailEvent = Event; +using UserAccountTimeEvent = Event; wxDECLARE_EVENT(EVT_OPEN_PRUSAAUTH, OpenPrusaAuthEvent); wxDECLARE_EVENT(EVT_UA_LOGGEDOUT, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_ID_USER_SUCCESS, UserAccountSuccessEvent); @@ -27,7 +28,7 @@ wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, UserAccountSuccessEven wxDECLARE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); // Soft fail - clears only after some number of fails wxDECLARE_EVENT(EVT_UA_RESET, UserAccountFailEvent); // Hard fail - clears all wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); // Failed to get data for printer to select, soft fail, action does not repeat - +wxDECLARE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent); typedef std::function UserActionSuccessFn; typedef std::function UserActionFailFn; @@ -144,18 +145,19 @@ public: void enqueue_action(UserAccountActionID id, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input); // Special enques, that sets callbacks. void enqueue_test_with_refresh(); + void enqueue_refresh(const std::string& body); void process_action_queue(); bool is_initialized() { return !m_access_token.empty() || !m_refresh_token.empty(); } std::string get_access_token() const { return m_access_token; } std::string get_refresh_token() const { return m_refresh_token; } std::string get_shared_session_key() const { return m_shared_session_key; } - + long long get_next_token_timeout() const {return m_next_token_timeout; } + //void set_polling_enabled(bool enabled) {m_polling_action = enabled ? UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTER_MODELS : UserAccountActionID::USER_ACCOUNT_ACTION_DUMMY; } void set_polling_action(UserAccountActionID action) { m_polling_action = action; } private: - - void enqueue_refresh(const std::string& body); + void refresh_fail_callback(const std::string& body); void cancel_queue(); void code_exchange_fail_callback(const std::string& body); @@ -172,6 +174,7 @@ private: std::string m_access_token; std::string m_refresh_token; std::string m_shared_session_key; + long long m_next_token_timeout; std::queue m_action_queue; std::queue m_priority_action_queue; diff --git a/src/slic3r/GUI/WebView.cpp b/src/slic3r/GUI/WebView.cpp index 5a97802506..d9b5c55e85 100644 --- a/src/slic3r/GUI/WebView.cpp +++ b/src/slic3r/GUI/WebView.cpp @@ -22,7 +22,7 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, std::v if (webView) { wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI(); -#ifdef __WIN32_ +#ifdef __WIN32__ webView->SetUserAgent(SLIC3R_APP_FULL_NAME); webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize); //We register the wxfs:// protocol for testing purposes diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 3804ec4ea3..fafbaaa1d1 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -15,6 +15,11 @@ #include #include +// if set to 1 the fetch() JS function gets override to include JWT in authorization header +// if set to 0, the /slicer/login is invoked from WebKit (passing JWT token only to this request) +// to set authorization cookie for all WebKit requests to Connect +#define AUTH_VIA_FETCH_OVERRIDE 0 + namespace pt = boost::property_tree; @@ -102,6 +107,7 @@ WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const // Connect the webview events Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::on_navigation_request, this, m_browser->GetId()); #ifdef DEBUG_URL_PANEL // Connect the button events @@ -248,6 +254,10 @@ void WebViewPanel::on_script_message(wxWebViewEvent& evt) { } +void WebViewPanel::on_navigation_request(wxWebViewEvent &evt) +{ +} + /** * Invoked when user selects the "View Source" menu item */ @@ -463,13 +473,11 @@ SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) : ConnectRequestHandler::ConnectRequestHandler() { - m_actions["REQUEST_ACCESS_TOKEN"] = std::bind(&ConnectRequestHandler::on_connect_action_request_access_token, this); - m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_connect_action_request_config, this); - m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::on_connect_action_webapp_ready, this); - m_actions["SELECT_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_select_printer, this); - m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this); - // obsolete - //m_actions["REQUEST_SELECTED_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this); + m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_connect_action_request_config, this, std::placeholders::_1); + m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::on_connect_action_webapp_ready,this, std::placeholders::_1); + m_actions["SELECT_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_select_printer, this, std::placeholders::_1); + m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this, std::placeholders::_1); + m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1); } ConnectRequestHandler::~ConnectRequestHandler() { @@ -484,9 +492,8 @@ void ConnectRequestHandler::handle_message(const std::string& message) {"action":"REQUEST_ACCESS_TOKEN"} */ std::string action_string; - m_message_data = message; try { - std::stringstream ss(m_message_data); + std::stringstream ss(message); pt::ptree ptree; pt::read_json(ss, ptree); // v1: @@ -505,23 +512,16 @@ void ConnectRequestHandler::handle_message(const std::string& message) } assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling. if (m_actions.find(action_string) != m_actions.end()) { - m_actions[action_string](); + m_actions[action_string](message); } } void ConnectRequestHandler::resend_config() { - on_connect_action_request_config(); + on_connect_action_request_config({}); } -void ConnectRequestHandler::on_connect_action_request_access_token() -{ - std::string token = wxGetApp().plater()->get_user_account()->get_access_token(); - wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'%1%\')", token); - run_script_bridge(script); -} - -void ConnectRequestHandler::on_connect_action_request_config() +void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data) { /* accessToken?: string; @@ -535,15 +535,104 @@ void ConnectRequestHandler::on_connect_action_request_config() const std::string dark_mode = wxGetApp().dark_mode() ? "DARK" : "LIGHT"; wxString language = GUI::wxGetApp().current_language_code(); language = language.SubString(0, 1); - const std::string init_options = GUI::format("{\"accessToken\": \"%1%\" , \"clientVersion\": \"%2%\", \"colorMode\": \"%3%\", \"language\": \"%4%\"}", token, SLIC3R_VERSION, dark_mode, language); + const std::string init_options = GUI::format("{\"accessToken\": \"%4%\",\"clientVersion\": \"%1%\", \"colorMode\": \"%2%\", \"language\": \"%3%\"}", SLIC3R_VERSION, dark_mode, language, token ); wxString script = GUI::format_wxstr("window._prusaConnect_v1.init(%1%)", init_options); run_script_bridge(script); } +void ConnectRequestHandler::on_connect_action_request_open_in_browser(const std::string& message_data) +{ + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto url = ptree.get_optional("url"); url) { + wxGetApp().open_browser_with_warning_dialog(GUI::from_u8(*url)); + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what(); + return; + } +} ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) : WebViewPanel(parent, L"https://connect.prusa3d.com/", { "_prusaSlicer" }, "connect_loading") { + //m_browser->RegisterHandler(wxSharedPtr(new WebViewHandler("https"))); + + Plater* plater = wxGetApp().plater(); + m_browser->AddUserScript(wxString::Format( + +#if AUTH_VIA_FETCH_OVERRIDE + /* + * Notes: + * - The fetch() function has two distinct prototypes (i.e. input args): + * 1. fetch(url: string, options: object | undefined) + * 2. fetch(req: Request, options: object | undefined) + * - For some reason I can't explain the headers can be extended only via Request object + * i.e. the fetch prototype (2). So we need to convert (1) call into (2) before + * + */ + R"( + if (window.__fetch === undefined) { + window.__fetch = fetch; + window.fetch = function(req, opts = {}) { + if (typeof req === 'string') { + req = new Request(req, opts); + opts = {}; + } + if (window.__access_token && (req.url[0] == '/' || req.url.indexOf('prusa3d.com') > 0)) { + req.headers.set('Authorization', 'Bearer ' + window.__access_token); + console.log('Header updated: ', req.headers.get('Authorization')); + console.log('AT Version: ', __access_token_version); + } + //console.log('Injected fetch used', req, opts); + return __fetch(req, opts); + }; + } + window.__access_token = '%s'; + window.__access_token_version = 0; + )", +#else + R"( + console.log('Preparing login'); + window.fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer %s'}}) + .then((resp) => { + console.log('Login resp', resp); + resp.text().then((json) => console.log('Login resp body', json)); + }); + )", +#endif + plater->get_user_account()->get_access_token() + )); + plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); +} + +ConnectWebViewPanel::~ConnectWebViewPanel() +{ + m_browser->Unbind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); +} + +void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) +{ + e.Skip(); + wxString javascript = wxString::Format( +#if AUTH_VIA_FETCH_OVERRIDE + "window.__access_token = '%s';window.__access_token_version = (window.__access_token_version || 0) + 1;console.log('Updated Auth token', window.__access_token);", +#else + R"( + console.log('Preparing login'); + window.fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer %s'}}) + .then((resp) => { + console.log('Login resp', resp); + resp.text().then((json) => console.log('Login resp body', json)); + }); + )", +#endif + wxGetApp().plater()->get_user_account()->get_access_token() + ); + //m_browser->AddUserScript(javascript, wxWEBVIEW_INJECT_AT_DOCUMENT_END); + m_browser->RunScriptAsync(javascript); } void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt) @@ -551,11 +640,37 @@ void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt) BOOST_LOG_TRIVIAL(debug) << "recieved message from PrusaConnect FE: " << evt.GetString(); handle_message(into_u8(evt.GetString())); } - +void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt) +{ + if (evt.GetURL() == m_default_url) { + m_reached_default_url = true; + return; + } + if (evt.GetURL() == (GUI::format_wxstr("file:///%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string()))) { + return; + } + if (m_reached_default_url && !evt.GetURL().StartsWith(m_default_url)) { + BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; + evt.Veto(); + } +} void ConnectWebViewPanel::logout() { wxString script = L"window._prusaConnect_v1.logout()"; run_script(script); + + Plater* plater = wxGetApp().plater(); + m_browser->RunScript(wxString::Format( + R"( + console.log('Preparing login'); + window.fetch('/slicer/logout', {method: 'POST', headers: {Authorization: 'Bearer %s'}}) + .then((resp) => { + console.log('Login resp', resp); + resp.text().then((json) => console.log('Login resp body', json)); + }); + )", + plater->get_user_account()->get_access_token() + )); } void ConnectWebViewPanel::sys_color_changed() @@ -563,12 +678,12 @@ void ConnectWebViewPanel::sys_color_changed() resend_config(); } -void ConnectWebViewPanel::on_connect_action_select_printer() +void ConnectWebViewPanel::on_connect_action_select_printer(const std::string& message_data) { - assert(!m_message_data.empty()); - wxGetApp().handle_connect_request_printer_select(m_message_data); + assert(!message_data.empty()); + wxGetApp().handle_connect_request_printer_select(message_data); } -void ConnectWebViewPanel::on_connect_action_print() +void ConnectWebViewPanel::on_connect_action_print(const std::string& message_data) { // PRINT request is not defined for ConnectWebViewPanel assert(true); @@ -1065,18 +1180,18 @@ void PrinterPickWebViewDialog::on_script_message(wxWebViewEvent& evt) handle_message(into_u8(evt.GetString())); } -void PrinterPickWebViewDialog::on_connect_action_select_printer() +void PrinterPickWebViewDialog::on_connect_action_select_printer(const std::string& message_data) { // SELECT_PRINTER request is not defined for PrinterPickWebViewDialog assert(true); } -void PrinterPickWebViewDialog::on_connect_action_print() +void PrinterPickWebViewDialog::on_connect_action_print(const std::string& message_data) { - m_ret_val = m_message_data; + m_ret_val = message_data; this->EndModal(wxID_OK); } -void PrinterPickWebViewDialog::on_connect_action_webapp_ready() +void PrinterPickWebViewDialog::on_connect_action_webapp_ready(const std::string& message_data) { if (Preset::printer_technology(wxGetApp().preset_bundle->printers.get_selected_preset().config) == ptFFF) { diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 6b95ff8975..715c645c9e 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -1,18 +1,24 @@ #ifndef slic3r_WebViewDialog_hpp_ #define slic3r_WebViewDialog_hpp_ +//#define DEBUG_URL_PANEL + #include #include #include +#include "UserAccountSession.hpp" + +#ifdef DEBUG_URL_PANEL +#include +#endif + class wxWebView; class wxWebViewEvent; namespace Slic3r { namespace GUI { -//#define DEBUG_URL_PANEL - class WebViewPanel : public wxPanel { public: @@ -47,6 +53,7 @@ public: void on_select_all(wxCommandEvent& evt); void On_enable_context_menu(wxCommandEvent& evt); void On_enable_dev_tools(wxCommandEvent& evt); + virtual void on_navigation_request(wxWebViewEvent &evt); wxString get_default_url() const { return m_default_url; } void set_default_url(const wxString& url) { m_default_url = url; } @@ -167,30 +174,33 @@ public: void resend_config(); protected: // action callbacs stored in m_actions - virtual void on_connect_action_request_access_token(); - virtual void on_connect_action_request_config(); - virtual void on_connect_action_select_printer() = 0; - virtual void on_connect_action_print() = 0; - virtual void run_script_bridge(const wxString& script) = 0; - virtual void on_connect_action_webapp_ready() = 0; - - std::map> m_actions; - std::string m_message_data; + virtual void on_connect_action_request_config(const std::string& message_data); + virtual void on_connect_action_request_open_in_browser(const std::string& message_data); + virtual void on_connect_action_select_printer(const std::string& message_data) = 0; + virtual void on_connect_action_print(const std::string& message_data) = 0; + virtual void on_connect_action_webapp_ready(const std::string& message_data) = 0; + virtual void run_script_bridge(const wxString &script) = 0; + std::map> m_actions; }; class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler { public: ConnectWebViewPanel(wxWindow* parent); + ~ConnectWebViewPanel() override; void on_script_message(wxWebViewEvent& evt) override; void logout(); void sys_color_changed() override; + void on_navigation_request(wxWebViewEvent &evt) override; protected: - void on_connect_action_select_printer() override; - void on_connect_action_print() override; - void on_connect_action_webapp_ready() override {} + void on_connect_action_select_printer(const std::string& message_data) override; + void on_connect_action_print(const std::string& message_data) override; + void on_connect_action_webapp_ready(const std::string& message_data) override {} void run_script_bridge(const wxString& script) override {run_script(script); } +private: + void on_user_token(UserAccountSuccessEvent& e); + bool m_reached_default_url {false}; }; class PrinterWebViewPanel : public WebViewPanel @@ -220,9 +230,9 @@ public: void on_show(wxShowEvent& evt) override; void on_script_message(wxWebViewEvent& evt) override; protected: - void on_connect_action_select_printer() override; - void on_connect_action_print() override; - void on_connect_action_webapp_ready() override; + void on_connect_action_select_printer(const std::string& message_data) override; + void on_connect_action_print(const std::string& message_data) override; + void on_connect_action_webapp_ready(const std::string& message_data) override; void request_compatible_printers_FFF(); void request_compatible_printers_SLA(); void run_script_bridge(const wxString& script) override { run_script(script); } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 4ecf3d93cf..a4bfe74f38 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -329,7 +329,7 @@ std::vector get_extruder_color_icons(bool thin_icon/* = false*/ { // Create the bitmap with color bars. std::vector bmps; - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_color_strings_from_plater_config(); if (colors.empty()) return bmps; diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index f7b3a5175c..43fd737d90 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -42,9 +42,7 @@ struct PrintHostUpload PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None }; - std::string set_ready; - std::string position; - std::string wait_until; + std::string data_json; }; class PrintHost diff --git a/src/slic3r/Utils/PrusaConnect.cpp b/src/slic3r/Utils/PrusaConnect.cpp index 19da1cb832..63fd95f2f4 100644 --- a/src/slic3r/Utils/PrusaConnect.cpp +++ b/src/slic3r/Utils/PrusaConnect.cpp @@ -88,25 +88,30 @@ bool PrusaConnectNew::init_upload(PrintHostUpload upload_data, std::string& out) boost::system::error_code ec; boost::uintmax_t size = boost::filesystem::file_size(upload_data.source_path, ec); const std::string name = get_name(); - const std::string file_size = std::to_string(size); const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token(); - //const std::string upload_path = upload_data.upload_path.generic_string(); const std::string upload_filename = upload_data.upload_path.filename().string(); std::string url = GUI::format("%1%/app/users/teams/%2%/uploads", get_host(), m_team_id); - const std::string request_body_json = GUI::format( - "{" - "\"filename\": \"%1%\", " - "\"size\": %2%, " - "\"path\": \"%3%\", " - "\"force\": true, " - "\"printer_uuid\": \"%4%\"" - "}" - , upload_filename - , file_size - , upload_data.upload_path.generic_string() - , m_uuid - ); - + std::string request_body_json = upload_data.data_json; + // GUI::format( + // "{" + // "\"filename\": \"%1%\", " + // "\"size\": %2%, " + // "\"path\": \"%3%\", " + // "\"force\": true, " + // "\"printer_uuid\": \"%4%\"" + // "}" + // , upload_filename + // , file_size + // , upload_data.upload_path.generic_string() + // , m_uuid + //); + + // replace plaholder filename + assert(request_body_json.find("%1%") != std::string::npos); + assert(request_body_json.find("%2%") != std::string::npos); + request_body_json = GUI::format(request_body_json, upload_filename, size); + + BOOST_LOG_TRIVIAL(info) << "Register upload to "<< name<<". Url: " << url << "\nBody: " << request_body_json; Http http = Http::post(std::move(url)); http.header("Authorization", "Bearer " + access_token) @@ -156,20 +161,20 @@ bool PrusaConnectNew::upload(PrintHostUpload upload_data, ProgressFn progress_fn } const std::string name = get_name(); const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token(); - const std::string escaped_upload_path = upload_data.storage + "/" + escape_path_by_element(upload_data.upload_path.string()); - const std::string set_ready = upload_data.set_ready.empty() ? "" : "&set_ready=" + upload_data.set_ready; - const std::string position = upload_data.position.empty() ? "" : "&position=" + upload_data.position; - const std::string wait_until = upload_data.wait_until.empty() ? "" : "&wait_until=" + upload_data.wait_until; +// const std::string escaped_upload_path = upload_data.storage + "/" + escape_path_by_element(upload_data.upload_path.string()); +// const std::string set_ready = upload_data.set_ready.empty() ? "" : "&set_ready=" + upload_data.set_ready; +// const std::string position = upload_data.position.empty() ? "" : "&position=" + upload_data.position; +// const std::string wait_until = upload_data.wait_until.empty() ? "" : "&wait_until=" + upload_data.wait_until; const std::string url = GUI::format( "%1%/app/teams/%2%/files/raw" "?upload_id=%3%" - "&force=true" - "&printer_uuid=%4%" - "&path=%5%" - "%6%" - "%7%" - "%8%" - , get_host(), m_team_id, upload_id, m_uuid, escaped_upload_path, set_ready, position, wait_until); + // "&force=true" + // "&printer_uuid=%4%" + // "&path=%5%" + // "%6%" + // "%7%" + // "%8%" + , get_host(), m_team_id, upload_id/*, m_uuid, escaped_upload_path, set_ready, position, wait_until*/); bool res = true; BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%") diff --git a/tests/fff_print/benchmark_seams.cpp b/tests/fff_print/benchmark_seams.cpp index 337c730a6d..41f5242067 100644 --- a/tests/fff_print/benchmark_seams.cpp +++ b/tests/fff_print/benchmark_seams.cpp @@ -12,8 +12,18 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm }; using namespace Slic3r::Seams; + BENCHMARK_ADVANCED("Create shells benchy")(Catch::Benchmark::Chronometer meter) { - meter.measure([&] { return Shells::create_shells(extrusions, params.max_distance); }); + std::vector inputs; + inputs.reserve(meter.runs()); + std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() { + return Slic3r::Seams::Perimeters::create_perimeters( + projected, layer_infos, painting, params.perimeter + ); + }); + meter.measure([&](const int i) { + return Shells::create_shells(std::move(inputs[i]), params.max_distance); + }); }; @@ -27,7 +37,7 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm BENCHMARK_ADVANCED("Create perimeters benchy")(Catch::Benchmark::Chronometer meter) { meter.measure([&] { - return Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter); + return Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter); }); }; @@ -35,8 +45,12 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm std::vector> inputs; inputs.reserve(meter.runs()); std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() { - return Perimeters::create_perimeters( - shell_polygons, layer_infos, painting, params.perimeter + Slic3r::Seams::Perimeters::LayerPerimeters perimeters{ + Slic3r::Seams::Perimeters::create_perimeters( + projected, layer_infos, painting, params.perimeter + )}; + return Shells::create_shells( + std::move(perimeters), params.max_distance ); }); meter.measure([&](const int i) { @@ -55,24 +69,24 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm }; BENCHMARK_ADVANCED("Generate rear seam benchy")(Catch::Benchmark::Chronometer meter) { - std::vector> inputs; + std::vector inputs; inputs.reserve(meter.runs()); std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() { - return create_perimeters( - shell_polygons, layer_infos, painting, params.perimeter + return Slic3r::Seams::Perimeters::create_perimeters( + projected, layer_infos, painting, params.perimeter ); }); meter.measure([&](const int i) { - return Rear::get_object_seams(std::move(inputs[i]), params.rear_project_threshold); + return Rear::get_object_seams(std::move(inputs[i]), params.rear_tolerance, params.rear_y_offset); }); }; BENCHMARK_ADVANCED("Generate random seam benchy")(Catch::Benchmark::Chronometer meter) { - std::vector> inputs; + std::vector inputs; inputs.reserve(meter.runs()); std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() { - return Perimeters::create_perimeters( - shell_polygons, layer_infos, painting, params.perimeter + return Slic3r::Seams::Perimeters::create_perimeters( + projected, layer_infos, painting, params.perimeter ); }); meter.measure([&](const int i) { diff --git a/tests/fff_print/test_data.hpp b/tests/fff_print/test_data.hpp index 61e1b92d42..939d1a7631 100644 --- a/tests/fff_print/test_data.hpp +++ b/tests/fff_print/test_data.hpp @@ -219,10 +219,8 @@ struct SeamsFixture const Seams::Perimeters::LayerInfos layer_infos{Seams::Perimeters::get_layer_infos( print_object->layers(), params.perimeter.elephant_foot_compensation )}; - Seams::Shells::Shells shell_polygons{ - Seams::Shells::create_shells(extrusions, params.max_distance)}; - - const std::size_t shell_index{15}; + const std::vector projected{ + Seams::Geometry::project_to_geometry(extrusions, params.max_distance)}; const ModelInfo::Visibility visibility{transformation, volumes, params.visibility, [](){}}; Seams::Aligned::VisibilityCalculator diff --git a/tests/fff_print/test_seam_aligned.cpp b/tests/fff_print/test_seam_aligned.cpp index 1ab755cfaf..33080e749e 100644 --- a/tests/fff_print/test_seam_aligned.cpp +++ b/tests/fff_print/test_seam_aligned.cpp @@ -115,13 +115,13 @@ TEST_CASE_METHOD(PickSeamOptionFixture, "Least visible point", "[Seams][SeamAlig } TEST_CASE_METHOD(Test::SeamsFixture, "Generate aligned seam", "[Seams][SeamAligned][Integration]") { - Shells::Shells<> perimeters{ - Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)}; - Shells::Shells<> shell_perimeters; - shell_perimeters.push_back(std::move(perimeters[shell_index])); + Seams::Perimeters::LayerPerimeters perimeters{ + Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; + Seams::Shells::Shells<> shells{ + Seams::Shells::create_shells(std::move(perimeters), params.max_distance)}; + const std::vector> seam{ - Aligned::get_object_seams(std::move(shell_perimeters), visibility_calculator, params.aligned)}; - REQUIRE(seam.size() == 125); + Aligned::get_object_seams(std::move(shells), visibility_calculator, params.aligned)}; if constexpr (debug_files) { std::ofstream csv{"aligned_seam.csv"}; @@ -133,9 +133,13 @@ TEST_CASE_METHOD(Test::SeamsFixture, "Calculate visibility", "[Seams][SeamAligne if constexpr (debug_files) { std::ofstream csv{"visibility.csv"}; csv << "x,y,z,visibility,total_visibility" << std::endl; - Shells::Shells<> perimeters{ - Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)}; - for (const Shells::Shell<> &shell : perimeters) { + + Seams::Perimeters::LayerPerimeters perimeters{ + Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; + + Seams::Shells::Shells<> shells{ + Seams::Shells::create_shells(std::move(perimeters), params.max_distance)}; + for (const Shells::Shell<> &shell : shells) { for (const Shells::Slice<> &slice : shell) { for (std::size_t index{0}; index < slice.boundary.positions.size(); ++index) { const Vec2d &position{slice.boundary.positions[index]}; diff --git a/tests/fff_print/test_seam_perimeters.cpp b/tests/fff_print/test_seam_perimeters.cpp index 405eb3731d..36e4f67b66 100644 --- a/tests/fff_print/test_seam_perimeters.cpp +++ b/tests/fff_print/test_seam_perimeters.cpp @@ -167,15 +167,14 @@ void serialize_shell(std::ostream &output, const Shells::Shell perimeters{ - create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)}; + Seams::Perimeters::LayerPerimeters perimeters{ + Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; - const Shells::Shell<> &shell{perimeters[shell_index]}; + Seams::Shells::Shells<> shells{ + Seams::Shells::create_shells(std::move(perimeters), params.max_distance)}; if constexpr (debug_files) { std::ofstream csv{"perimeters.csv"}; - serialize_shell(csv, shell); + serialize_shell(csv, shells[0]); } - - REQUIRE(shell.size() == 54); } diff --git a/tests/fff_print/test_seam_random.cpp b/tests/fff_print/test_seam_random.cpp index 8a69c6b66a..cd9514619e 100644 --- a/tests/fff_print/test_seam_random.cpp +++ b/tests/fff_print/test_seam_random.cpp @@ -84,16 +84,13 @@ TEST_CASE("Random respects point type", "[Seams][SeamRandom]") { } TEST_CASE_METHOD(Test::SeamsFixture, "Generate random seam", "[Seams][SeamRandom][Integration]") { - Shells::Shells<> perimeters{ - Seams::Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)}; - Shells::Shells<> shell_perimeters; - shell_perimeters.push_back(std::move(perimeters[shell_index])); - const std::vector> seam{ - Random::get_object_seams(std::move(shell_perimeters), params.random_seed)}; - REQUIRE(seam.size() == 125); + Seams::Perimeters::LayerPerimeters perimeters{ + Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; + const std::vector> seams{ + Random::get_object_seams(std::move(perimeters), params.random_seed)}; if constexpr (debug_files) { std::ofstream csv{"random_seam.csv"}; - Test::serialize_seam(csv, seam); + Test::serialize_seam(csv, seams); } } diff --git a/tests/fff_print/test_seam_rear.cpp b/tests/fff_print/test_seam_rear.cpp index c6ee01c5b3..fc183a3ce7 100644 --- a/tests/fff_print/test_seam_rear.cpp +++ b/tests/fff_print/test_seam_rear.cpp @@ -34,27 +34,14 @@ Perimeters::Perimeter get_perimeter() { } } // namespace RearTest -TEST_CASE("StraightLine operator places seam point near the prefered position", "[Seams][SeamRear]") { - const Rear::Impl::StraightLine rearest{Vec2d{0.7, 2.0}}; - std::optional choice{rearest(RearTest::get_perimeter(), Perimeters::PointType::common, Perimeters::PointClassification::common)}; - - REQUIRE(choice); - CHECK(scaled(choice->position) == scaled(Vec2d{0.7, 1.0})); - CHECK(choice->previous_index == 2); - CHECK(choice->next_index == 3); -} - TEST_CASE_METHOD(Test::SeamsFixture, "Generate rear seam", "[Seams][SeamRear][Integration]") { - Shells::Shells<> perimeters{ - Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)}; - Shells::Shells<> shell_perimeters; - shell_perimeters.push_back(std::move(perimeters[shell_index])); - const std::vector> seam{ - Rear::get_object_seams(std::move(shell_perimeters), params.rear_project_threshold)}; - REQUIRE(seam.size() == 125); + Seams::Perimeters::LayerPerimeters perimeters{ + Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; + const std::vector> seams{ + Rear::get_object_seams(std::move(perimeters), params.rear_tolerance, params.rear_y_offset)}; if constexpr (debug_files) { std::ofstream csv{"rear_seam.csv"}; - Test::serialize_seam(csv, seam); + Test::serialize_seam(csv, seams); } } diff --git a/tests/fff_print/test_seam_shells.cpp b/tests/fff_print/test_seam_shells.cpp index b514304fbc..56bd273780 100644 --- a/tests/fff_print/test_seam_shells.cpp +++ b/tests/fff_print/test_seam_shells.cpp @@ -33,7 +33,7 @@ TEST_CASE_METHOD(ProjectionFixture, "Project to geometry matches", "[Seams][Seam boundary_polygon.scale(1.0 + extrusion_width / 2.0 + 0.1); island_boundary.contour = boundary_polygon; - Shells::Impl::BoundedPolygons result{Shells::Impl::project_to_geometry(extrusions, 5.0)}; + Seams::Geometry::BoundedPolygons result{Seams::Geometry::project_to_geometry(extrusions, 5.0)}; REQUIRE(result.size() == 1); REQUIRE(result[0].polygon.size() == 4); // Boundary polygon is picked. @@ -48,7 +48,7 @@ TEST_CASE_METHOD(ProjectionFixture, "Project to geometry does not match", "[Seam island_boundary.contour = boundary_polygon; - Shells::Impl::BoundedPolygons result{Shells::Impl::project_to_geometry(extrusions, 1.0)}; + Seams::Geometry::BoundedPolygons result{Seams::Geometry::project_to_geometry(extrusions, 1.0)}; REQUIRE(result.size() == 1); REQUIRE(result[0].polygon.size() == 4); @@ -57,35 +57,3 @@ TEST_CASE_METHOD(ProjectionFixture, "Project to geometry does not match", "[Seam // The extrusion is expanded and returned. CHECK(result[0].polygon == expanded); } - -void serialize_shells( - std::ostream &out, const Shells::Shells &shells, const double layer_height -) { - out << "x,y,z,layer_index,slice_id,shell_id" << std::endl; - for (std::size_t shell_id{}; shell_id < shells.size(); ++shell_id) { - const Shells::Shell &shell{shells[shell_id]}; - for (std::size_t slice_id{}; slice_id < shell.size(); ++slice_id) { - const Shells::Slice &slice{shell[slice_id]}; - for (const Point &point : slice.boundary) { - // clang-format off - out - << point.x() << "," - << point.y() << "," - << slice.layer_index * 1e6 * layer_height << "," - << slice.layer_index << "," - << slice_id << "," - << shell_id << std::endl; - // clang-format on - } - } - } -} - -TEST_CASE_METHOD(Test::SeamsFixture, "Create shells", "[Seams][SeamShells][Integration]") { - if constexpr (debug_files) { - std::ofstream csv{"shells.csv"}; - serialize_shells(csv, shell_polygons, print->full_print_config().opt_float("layer_height")); - } - - CHECK(shell_polygons.size() == 36); -} diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 3a5d96c7ac..b244e76ac8 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -5,7 +5,8 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp sla_supptreeutils_tests.cpp - sla_archive_readwrite_tests.cpp) + sla_archive_readwrite_tests.cpp + sla_zcorrection_tests.cpp) # mold linker for successful linking needs also to link TBB library and link it before libslic3r. target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb TBB::tbbmalloc libslic3r) diff --git a/tests/sla_print/sla_zcorrection_tests.cpp b/tests/sla_print/sla_zcorrection_tests.cpp new file mode 100644 index 0000000000..69734c253b --- /dev/null +++ b/tests/sla_print/sla_zcorrection_tests.cpp @@ -0,0 +1,123 @@ +#include +#include + +#include + +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/SLA/ZCorrection.hpp" +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/SVG.hpp" + +void print_depthmap(std::string_view prefix, + const Slic3r::BoundingBox &bb, + const Slic3r::sla::zcorr_detail::DepthMap &dm) +{ + using namespace Slic3r; + + size_t cnt = 0; + for (const sla::zcorr_detail::DepthMapLayer &layer : dm) { + SVG svg(std::string(prefix) + std::to_string(cnt++) + ".svg", bb); + for (const auto &[depth, dpolys] : layer) { + svg.draw_outline(dpolys); + svg.draw(dpolys, "green", 1. + depth / 10.f); + } + } +} + +TEST_CASE("Number of layers should be equal after z correction", "[ZCorr]") +{ + using namespace Slic3r; + + const size_t Layers = random_value(size_t{1}, size_t{100}); + INFO("Layers = " << Layers); + + float zcorr_depth = GENERATE(0.f, random_value(0.01f, 10.f)); + + std::vector slices(Layers); + std::vector hgrid = grid(0.f, Layers * 1.f, 1.f); + + std::vector output = sla::apply_zcorrection(slices, hgrid, zcorr_depth); + + REQUIRE(slices.size() == output.size()); +} + +TEST_CASE("Testing DepthMap for a cube", "[ZCorr]") +{ + using namespace Slic3r; + + TriangleMesh mesh = load_model("20mm_cube.obj"); + auto bb = bounding_box(mesh); + bb.offset(-0.1); + + std::vector hgrid = grid(bb.min.z(), bb.max.z(), 1.f); + + std::vector slices = slice_mesh_ex(mesh.its, hgrid, {}); + + sla::zcorr_detail::DepthMap dmap = sla::zcorr_detail::create_depthmap(slices, hgrid); + + REQUIRE(dmap.size() == slices.size()); + + for (size_t i = 0; i < slices.size(); ++i) { + const auto &dlayer = dmap[i]; + const ExPolygons &slayer = slices[i]; + REQUIRE(dlayer.size() == 1); + REQUIRE(dlayer.begin()->first == i); + double ad = area(dlayer.begin()->second); + double as = area(slayer); + REQUIRE(ad == Approx(as).margin(EPSILON)); + } +} + +TEST_CASE("Testing DepthMap for arbitrary shapes", "[ZCorr]") +{ + using namespace Slic3r; + + auto modelname = GENERATE("V_standing.obj", "A_upsidedown.obj"); + + TriangleMesh mesh = load_model(modelname); + auto bb = bounding_box(mesh); + bb.offset(-0.1); + + std::vector hgrid = grid(bb.min.z(), bb.max.z(), 0.5f); + + std::vector slices = slice_mesh_ex(mesh.its, hgrid, {}); + + size_t zcorr_layers = GENERATE(size_t{0}, random_value(size_t{1}, size_t{10})); + + sla::zcorr_detail::DepthMap dmap = + sla::zcorr_detail::create_depthmap(slices, hgrid, zcorr_layers); + +#ifndef NDEBUG + print_depthmap("debug_dmap", scaled(to_2d(bb)), dmap); +#endif + + REQUIRE(dmap.size() == slices.size()); + + auto corrslices_fast = sla::apply_zcorrection(slices, zcorr_layers); + sla::zcorr_detail::apply_zcorrection(dmap, zcorr_layers); + + for (size_t i = 0; i < corrslices_fast.size(); ++i) { + ExPolygons dlayer = sla::zcorr_detail::merged_layer(dmap[i]); + const ExPolygons &slayer = corrslices_fast[i]; + double ad = area(dlayer); + double as = area(slayer); + REQUIRE(ad == Approx(as).margin(EPSILON)); + } +} + +TEST_CASE("Test depth to layers calculation", "[ZCorr]") { + using namespace Slic3r; + + float layer_h = 0.5f; + std::vector hgrid = grid(0.f, 100.f, layer_h); + + float depth = GENERATE(0.f, + random_value(0.01f, 0.499f), + 0.5f, + random_value(0.501f, 10.f)); + + for (size_t i = 0; i < hgrid.size(); ++i) { + auto expected_lyrs = std::min(i, static_cast(std::ceil(depth/layer_h))); + REQUIRE(sla::zcorr_detail::depth_to_layers(hgrid, i, depth) == expected_lyrs); + } +}
%s