mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 00:36:25 +08:00
Merge branch 'master' into dk_archive_db_rebased
This commit is contained in:
commit
df914f01d1
@ -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
|
||||
|
161
src/libslic3r/Format/PrintRequest.cpp
Normal file
161
src/libslic3r/Format/PrintRequest.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
#include "PrintRequest.hpp"
|
||||
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fast_float/fast_float.h>
|
||||
|
||||
#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<std::string>& 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<std::string>& 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<std::string>& 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<std::string> 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
|
12
src/libslic3r/Format/PrintRequest.hpp
Normal file
12
src/libslic3r/Format/PrintRequest.hpp
Normal file
@ -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
|
@ -261,6 +261,29 @@ struct SeamCandidate {
|
||||
std::vector<double> visibilities;
|
||||
};
|
||||
|
||||
std::vector<SeamChoice> get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<SeamChoice(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
) {
|
||||
std::vector<SeamChoice> 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<SeamChoice> 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<double> choice_visibilities(shell.size(), 1.0);
|
||||
std::vector<SeamChoice> 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}
|
||||
)};
|
||||
|
@ -47,65 +47,4 @@ std::optional<SeamChoice> choose_degenerate_seam_point(const Perimeters::Perimet
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::vector<SeamChoice>> maybe_get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<std::optional<SeamChoice>(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
) {
|
||||
std::vector<SeamChoice> 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<SeamChoice> seam_choice{
|
||||
choose_degenerate_seam_point(slice.boundary)}) {
|
||||
result.push_back(*seam_choice);
|
||||
} else {
|
||||
result.emplace_back();
|
||||
}
|
||||
} else {
|
||||
const std::optional<SeamChoice> choice{chooser(slice.boundary, i)};
|
||||
if (!choice) {
|
||||
return std::nullopt;
|
||||
}
|
||||
result.push_back(*choice);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<SeamChoice> get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<SeamChoice(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
) {
|
||||
std::optional<std::vector<SeamChoice>> 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<SeamChoice>(shell.size());
|
||||
}
|
||||
return *seam;
|
||||
}
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const std::function<std::vector<SeamChoice>(const Shells::Shell<>&)> &get_shell_seam
|
||||
) {
|
||||
std::vector<std::vector<SeamPerimeterChoice>> layer_seams(get_layer_count(shells));
|
||||
|
||||
for (Shells::Shell<> &shell : shells) {
|
||||
std::vector<SeamChoice> 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
|
||||
|
@ -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<SeamChoice> choose_degenerate_seam_point(const Perimeters::Perimeter &perimeter);
|
||||
|
||||
std::optional<std::vector<SeamChoice>> maybe_get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<std::optional<SeamChoice>(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
);
|
||||
|
||||
std::vector<SeamChoice> get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<SeamChoice(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
);
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const std::function<std::vector<SeamChoice>(const Shells::Shell<> &)> &get_shell_seam
|
||||
);
|
||||
|
||||
} // namespace Slic3r::Seams
|
||||
|
||||
#endif // libslic3r_SeamChoice_hpp_
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include <fstream>
|
||||
@ -188,6 +189,54 @@ std::vector<Extrusions> get_extrusions(tcb::span<const Slic3r::Layer *const> 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<BoundedPolygons> project_to_geometry(const std::vector<Geometry::Extrusions> &extrusions, const double max_bb_distance) {
|
||||
std::vector<BoundedPolygons> 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<Vec2d> 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::size_t>(std::ceil(total_distance / max_distance)) + 1};
|
||||
|
@ -43,6 +43,17 @@ using Extrusions = std::vector<Extrusion>;
|
||||
|
||||
std::vector<Extrusions> get_extrusions(tcb::span<const Slic3r::Layer *const> object_layers);
|
||||
|
||||
struct BoundedPolygon {
|
||||
Polygon polygon;
|
||||
BoundingBox bounding_box;
|
||||
bool is_hole{false};
|
||||
};
|
||||
|
||||
using BoundedPolygons = std::vector<BoundedPolygon>;
|
||||
|
||||
BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance);
|
||||
std::vector<BoundedPolygons> project_to_geometry(const std::vector<Geometry::Extrusions> &extrusions, const double max_bb_distance);
|
||||
|
||||
Vec2d get_polygon_normal(
|
||||
const std::vector<Vec2d> &points, const std::size_t index, const double min_arm_length
|
||||
);
|
||||
|
@ -384,26 +384,32 @@ Perimeter Perimeter::create(
|
||||
std::move(angle_types)};
|
||||
}
|
||||
|
||||
Shells::Shells<> create_perimeters(
|
||||
const std::vector<Shells::Shell<Polygon>> &shells,
|
||||
LayerPerimeters create_perimeters(
|
||||
const std::vector<Geometry::BoundedPolygons> &polygons,
|
||||
const std::vector<LayerInfo> &layer_infos,
|
||||
const ModelInfo::Painting &painting,
|
||||
const PerimeterParams ¶ms
|
||||
) {
|
||||
std::vector<Shells::Shell<>> result;
|
||||
result.reserve(shells.size());
|
||||
LayerPerimeters result;
|
||||
result.reserve(polygons.size());
|
||||
|
||||
std::transform(
|
||||
shells.begin(), shells.end(), std::back_inserter(result),
|
||||
[](const Shells::Shell<Polygon> &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<Polygon> &shell{shells[shell_index]};
|
||||
const Shells::Slice<Polygon>& 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
|
||||
|
@ -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<const Vec2d> positions): positions(positions) {}
|
||||
IndexToCoord() = default;
|
||||
double operator()(const size_t index, size_t dim) const;
|
||||
|
||||
tcb::span<const Vec2d> positions;
|
||||
@ -162,32 +164,25 @@ struct Perimeter
|
||||
PointTrees blocked_points{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a Perimeter for each polygon in each of the shells.
|
||||
*/
|
||||
Shells::Shells<Perimeter> create_perimeters(
|
||||
const std::vector<Shells::Shell<Polygon>> &shells,
|
||||
using Perimeters = std::vector<Perimeter>;
|
||||
|
||||
struct BoundedPerimeter {
|
||||
Perimeter perimeter;
|
||||
BoundingBox bounding_box;
|
||||
};
|
||||
|
||||
using BoundedPerimeters = std::vector<BoundedPerimeter>;
|
||||
using LayerPerimeters = std::vector<BoundedPerimeters>;
|
||||
|
||||
LayerPerimeters create_perimeters(
|
||||
const std::vector<Geometry::BoundedPolygons> &polygons,
|
||||
const std::vector<LayerInfo> &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<Vec2d> extract_points(
|
||||
const Perimeters::Perimeter &perimeter, const Perimeters::PointType point_type
|
||||
const Perimeter &perimeter, const PointType point_type
|
||||
) {
|
||||
std::vector<Vec2d> result;
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
|
@ -7,8 +7,10 @@
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#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<std::pair<const PrintObject *, Shells::Shells<>>>;
|
||||
using ObjectPainting = std::map<const PrintObject*, ModelInfo::Painting>;
|
||||
|
||||
ObjectShells partition_to_shells(
|
||||
ObjectLayerPerimeters get_perimeters(
|
||||
SpanOfConstPtrs<PrintObject> objects,
|
||||
const Params ¶ms,
|
||||
const ObjectPainting& object_painting,
|
||||
const std::function<void(void)> &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<Polygon> shell_polygons{
|
||||
Shells::create_shells(extrusions, params.max_distance)};
|
||||
const std::vector<Geometry::BoundedPolygons> 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<void(void)> &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<PrintObject> 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<SeamChoice> operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Perimeters::PointType point_type,
|
||||
const Perimeters::PointClassification point_classification
|
||||
) const {
|
||||
std::optional<SeamChoice> corner_candidate;
|
||||
double min_distance{std::numeric_limits<double>::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<SeamChoice, std::size_t> place_seam_near(
|
||||
const std::vector<BoundedPerimeter> &layer_perimeters,
|
||||
const std::vector<Perimeters::BoundedPerimeter> &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<SeamChoice, std::size_t> 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<SeamChoice> 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<BoundedPerimeter> &perimeters{this->perimeters_per_layer.at(po)[layer_index]};
|
||||
const std::vector<Perimeters::BoundedPerimeter> &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 {
|
||||
|
@ -21,20 +21,15 @@
|
||||
|
||||
namespace Slic3r::Seams {
|
||||
|
||||
struct BoundedPerimeter {
|
||||
Perimeters::Perimeter perimeter;
|
||||
BoundingBox bounding_box;
|
||||
};
|
||||
|
||||
using ObjectSeams =
|
||||
std::unordered_map<const PrintObject *, std::vector<std::vector<SeamPerimeterChoice>>>;
|
||||
using LayerPerimeters = std::vector<std::vector<BoundedPerimeter>>;
|
||||
using ObjectLayerPerimeters = std::unordered_map<const PrintObject *, LayerPerimeters>;
|
||||
using ObjectLayerPerimeters = std::unordered_map<const PrintObject *, Perimeters::LayerPerimeters>;
|
||||
|
||||
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{};
|
||||
|
@ -122,15 +122,33 @@ std::optional<SeamChoice> Random::operator()(
|
||||
} // namespace Impl
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> 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<std::vector<SeamPerimeterChoice>> result;
|
||||
|
||||
for (std::vector<Perimeters::BoundedPerimeter> &layer : perimeters) {
|
||||
result.emplace_back();
|
||||
for (Perimeters::BoundedPerimeter &perimeter : layer) {
|
||||
if (perimeter.perimeter.is_degenerate) {
|
||||
std::optional<Seams::SeamChoice> 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
|
||||
|
@ -24,6 +24,6 @@ struct Random
|
||||
};
|
||||
}
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells, const unsigned fixed_seed
|
||||
Perimeters::LayerPerimeters &&perimeters, const unsigned fixed_seed
|
||||
);
|
||||
}
|
||||
|
@ -15,111 +15,89 @@ BoundingBoxf get_bounding_box(const Shells::Shell<> &shell) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<SeamChoice> get_rearest_point(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) {
|
||||
double max_y{-std::numeric_limits<double>::infinity()};
|
||||
std::optional<std::size_t> 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<SeamChoice> operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) {
|
||||
std::vector<PerimeterLine> 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<SeamChoice> StraightLine::operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) const {
|
||||
std::vector<PerimeterLine> 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<PerimeterLine> possible_distancer{possible_lines};
|
||||
const BoundingBoxf bounding_box{perimeter.positions};
|
||||
|
||||
const std::vector<std::pair<Vec2d, std::size_t>> intersections{
|
||||
possible_distancer.intersections_with_line<true>(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<PerimeterLine> 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<false>(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<false>(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<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const double rear_project_threshold
|
||||
std::vector<std::vector<Perimeters::BoundedPerimeter>> &&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<std::vector<SeamPerimeterChoice>> result;
|
||||
|
||||
for (std::vector<Perimeters::BoundedPerimeter> &layer : perimeters) {
|
||||
result.emplace_back();
|
||||
for (Perimeters::BoundedPerimeter &perimeter : layer) {
|
||||
if (perimeter.perimeter.is_degenerate) {
|
||||
std::optional<Seams::SeamChoice> 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<std::vector<SeamChoice>> 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
|
||||
|
@ -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<SeamChoice> operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Perimeters::PointType point_type,
|
||||
const Perimeters::PointClassification point_classification
|
||||
) const;
|
||||
};
|
||||
|
||||
} // namespace Impl
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const double rear_project_threshold
|
||||
std::vector<std::vector<Perimeters::BoundedPerimeter>> &&perimeters,
|
||||
const double rear_tolerance,
|
||||
const double rear_y_offet
|
||||
);
|
||||
} // namespace Slic3r::Seams::Rear
|
||||
|
||||
|
@ -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<BoundedPolygons> project_to_geometry(const std::vector<Geometry::Extrusions> &extrusions, const double max_bb_distance) {
|
||||
std::vector<BoundedPolygons> 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<Polygon> map_to_shells(
|
||||
std::vector<BoundedPolygons> &&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<Polygon> 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<Polygon>{std::move(perimeter), layer_index}
|
||||
Slice<>{std::move(perimeter), layer_index}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -72,30 +24,31 @@ Shells<Polygon> map_to_shells(
|
||||
} // namespace Slic3r::Seams::Shells::Impl
|
||||
|
||||
namespace Slic3r::Seams::Shells {
|
||||
Shells<Polygon> create_shells(
|
||||
const std::vector<Geometry::Extrusions> &extrusions, const double max_distance
|
||||
Shells<> create_shells(
|
||||
Perimeters::LayerPerimeters &&perimeters, const double max_distance
|
||||
) {
|
||||
std::vector<Impl::BoundedPolygons> projected{Impl::project_to_geometry(extrusions, max_distance)};
|
||||
using Perimeters::BoundedPerimeters;
|
||||
using Perimeters::BoundedPerimeter;
|
||||
|
||||
std::vector<std::size_t> 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<Polygon> 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
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <tcbspan/span.hpp>
|
||||
|
||||
#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<BoundedPolygon>;
|
||||
|
||||
BoundedPolygons project_to_geometry(const Geometry::Extrusions &extrusions, const double max_bb_distance);
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::Shells {
|
||||
template<typename T = Perimeters::Perimeter> struct Slice
|
||||
{
|
||||
@ -39,8 +23,22 @@ template<typename T = Perimeters::Perimeter> using Shell = std::vector<Slice<T>>
|
||||
|
||||
template<typename T = Perimeters::Perimeter> using Shells = std::vector<Shell<T>>;
|
||||
|
||||
Shells<Polygon> create_shells(
|
||||
const std::vector<Geometry::Extrusions> &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
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "Format/3mf.hpp"
|
||||
#include "Format/STEP.hpp"
|
||||
#include "Format/SVG.hpp"
|
||||
#include "Format/PrintRequest.hpp"
|
||||
|
||||
#include <float.h>
|
||||
|
||||
@ -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();
|
||||
|
@ -913,6 +913,11 @@ public:
|
||||
// Returns 0-based indices of extruders painted by multi-material painting gizmo.
|
||||
std::vector<size_t> 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;
|
||||
|
@ -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));
|
||||
|
@ -618,6 +618,7 @@ static std::vector<std::string> 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 {
|
||||
|
@ -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;
|
||||
|
@ -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<std::string>& unselected_options)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -1174,6 +1174,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, material_correction_y))
|
||||
((ConfigOptionFloat, material_correction_z))
|
||||
((ConfigOptionEnum<SLAMaterialSpeed>, 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))
|
||||
|
130
src/libslic3r/SLA/ZCorrection.cpp
Normal file
130
src/libslic3r/SLA/ZCorrection.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
#include "ZCorrection.hpp"
|
||||
|
||||
#include "Execution/ExecutionTBB.hpp"
|
||||
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
std::vector<ExPolygons> apply_zcorrection(
|
||||
const std::vector<ExPolygons> &slices, size_t layers)
|
||||
{
|
||||
return zcorr_detail::apply_zcorrection(ex_tbb, slices, layers);
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> apply_zcorrection(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> &grid,
|
||||
float depth)
|
||||
{
|
||||
return zcorr_detail::apply_zcorrection(ex_tbb, slices, grid, depth);
|
||||
}
|
||||
|
||||
namespace zcorr_detail {
|
||||
|
||||
DepthMap create_depthmap(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> &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<ExPolygons> depthmap_to_slices(const DepthMap &dm)
|
||||
{
|
||||
auto out = reserve_vector<ExPolygons>(dm.size());
|
||||
for (const auto &dlayer : dm) {
|
||||
out.emplace_back(merged_layer(dlayer));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
ExPolygons intersect_layers(const std::vector<ExPolygons> &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<int>(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
|
84
src/libslic3r/SLA/ZCorrection.hpp
Normal file
84
src/libslic3r/SLA/ZCorrection.hpp
Normal file
@ -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<ExPolygons> apply_zcorrection(const std::vector<ExPolygons> &slices,
|
||||
size_t layers);
|
||||
|
||||
std::vector<ExPolygons> apply_zcorrection(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> &grid,
|
||||
float depth);
|
||||
|
||||
namespace zcorr_detail {
|
||||
|
||||
ExPolygons intersect_layers(const std::vector<ExPolygons> &slices,
|
||||
size_t layer_from,
|
||||
size_t layers_down);
|
||||
|
||||
template<class Ex>
|
||||
std::vector<ExPolygons> apply_zcorrection(Ex ep,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
size_t layers)
|
||||
{
|
||||
std::vector<ExPolygons> 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<float> &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<class Ex>
|
||||
std::vector<ExPolygons> apply_zcorrection(Ex ep,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> &grid,
|
||||
float depth)
|
||||
{
|
||||
std::vector<ExPolygons> 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<size_t, ExPolygons>;
|
||||
using DepthMap = std::vector<DepthMapLayer>;
|
||||
|
||||
DepthMap create_depthmap(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> &grid, size_t max_depth = 0);
|
||||
|
||||
void apply_zcorrection(DepthMap &dmap, size_t layers);
|
||||
|
||||
ExPolygons merged_layer(const DepthMapLayer &dlayer);
|
||||
|
||||
std::vector<ExPolygons> depthmap_to_slices(const DepthMap &dm);
|
||||
|
||||
} // namespace zcorr_detail
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ZCORRECTION_HPP
|
@ -842,6 +842,7 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
||||
"absolute_correction"sv,
|
||||
"elefant_foot_compensation"sv,
|
||||
"elefant_foot_min_width"sv,
|
||||
"zcorrection_layers"sv,
|
||||
"gamma_correction"sv,
|
||||
};
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <libslic3r/Execution/ExecutionTBB.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/SupportPointGenerator.hpp>
|
||||
#include <libslic3r/SLA/ZCorrection.hpp>
|
||||
|
||||
#include <libslic3r/ElephantFootCompensation.hpp>
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
@ -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(
|
||||
|
@ -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<ColorRGBA> 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<int>(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<Matrix3f>(world_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
|
||||
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<float, 4> clp_data = { 0.0f, 0.0f, 1.0f, FLT_MAX };
|
||||
const std::array<float, 2> 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<GUI::TriangleSelectorMmGui>(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<int>(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<Matrix3f>(world_matrix_inv_transp.cast<float>()));
|
||||
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));
|
||||
|
@ -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<GUI::TriangleSelectorMmGui> triangle_selector_mm;
|
||||
std::chrono::system_clock::time_point time_used;
|
||||
uint64_t mm_timestamp;
|
||||
};
|
||||
struct MMPaintCache {
|
||||
std::vector<ColorRGBA> extruders_colors;
|
||||
std::map<ObjectID, MMPaintCachePerVolume> volume_data;
|
||||
};
|
||||
mutable MMPaintCache m_mm_paint_cache;
|
||||
|
||||
public:
|
||||
GLVolumePtrs volumes;
|
||||
|
||||
|
@ -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<std::s
|
||||
// TRN ConfigWizard: Materials : "%1%" = "Filaments"/"SLA materials"
|
||||
text = format_wxstr(_L("%1% visible for <b>(\"Template\")</b> 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 <a href=\"prusa.io/m1-validation\">article</a>. 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 <b>*</b> are <b>not</b> 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<std::s
|
||||
"</style>"
|
||||
"<body bgcolor= %s>"
|
||||
"<font color=%s>"
|
||||
"%s<br /><br />%s"
|
||||
"<font size=\"3\">"
|
||||
"%s<br /><br />%s<br /><br />%s"
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>"
|
||||
, 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<std::s
|
||||
"</style>"
|
||||
"<body bgcolor= %s>"
|
||||
"<font color=%s>"
|
||||
"%s<br /><br />%s"
|
||||
"<font size=\"3\">"
|
||||
"%s<br /><br />%s<br /><br />%s"
|
||||
"<table>"
|
||||
"<tr>"
|
||||
, 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("<td>%s</td>", boost::nowide::widen(printer_names[i]));
|
||||
|
@ -175,7 +175,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent)
|
||||
const bool use_custom_matrix = (project_config.option<ConfigOptionBool>("wiping_volumes_use_custom_matrix"))->value;
|
||||
const std::vector<double> &init_matrix = (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
|
||||
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
const std::vector<std::string> 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<const ConfigOptionFloat*>(preset_bundle->printers.get_edited_preset().config.option("multimaterial_purging"))->value;
|
||||
|
@ -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<ColorRGBA> extruders_colors = get_extruders_colors();
|
||||
std::vector<ColorRGBA> 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();
|
||||
|
@ -419,7 +419,7 @@ void Preview::create_sliders()
|
||||
});
|
||||
|
||||
m_layers_slider->set_callback_on_get_extruder_colors([]() -> std::vector<std::string> {
|
||||
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<double>& 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<std::string> tool_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result);
|
||||
const std::vector<std::string> tool_colors = wxGetApp().plater()->get_extruder_color_strings_from_plater_config(m_gcode_result);
|
||||
const std::vector<CustomGCode::Item>& 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<std::string> 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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,14 +62,6 @@ bool GLGizmoMmuSegmentation::on_is_activable() const
|
||||
return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1;
|
||||
}
|
||||
|
||||
std::vector<ColorRGBA> get_extruders_colors()
|
||||
{
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
std::vector<ColorRGBA> ret;
|
||||
decode_colors(colors, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::vector<std::string> get_extruders_names()
|
||||
{
|
||||
size_t extruders_count = wxGetApp().extruders_edited_cnt();
|
||||
@ -98,7 +90,7 @@ static std::vector<int> 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<TriangleSelectorMmGui>(*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();
|
||||
|
@ -152,16 +152,6 @@ private:
|
||||
std::map<std::string, std::string> m_desc;
|
||||
};
|
||||
|
||||
std::vector<ColorRGBA> 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
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<ConfigOptionString>("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<fs::path> 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<PrintHostType>(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<std::string> Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result) const
|
||||
std::vector<std::string> 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<std::string> 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<std::string> Plater::get_colors_for_color_print(const GCodeProcessorResult* const result) const
|
||||
std::vector<std::string> Plater::get_color_strings_for_color_print(const GCodeProcessorResult* const result) const
|
||||
{
|
||||
std::vector<std::string> colors = get_extruder_colors_from_plater_config(result);
|
||||
std::vector<std::string> 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<std::string> Plater::get_colors_for_color_print(const GCodeProcessor
|
||||
return colors;
|
||||
}
|
||||
|
||||
std::vector<ColorRGBA> Plater::get_extruder_colors_from_plater_config() const
|
||||
{
|
||||
std::vector<std::string> colors = get_extruder_color_strings_from_plater_config();
|
||||
std::vector<ColorRGBA> ret;
|
||||
decode_colors(colors, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<ColorRGBA> Plater::get_colors_for_color_print() const
|
||||
{
|
||||
std::vector<std::string> colors = get_color_strings_for_color_print();
|
||||
std::vector<ColorRGBA> ret;
|
||||
decode_colors(colors, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
wxString Plater::get_project_filename(const wxString& extension) const
|
||||
{
|
||||
return p->get_project_filename(extension);
|
||||
|
@ -255,8 +255,11 @@ public:
|
||||
void force_print_bed_update();
|
||||
// On activating the parent window.
|
||||
void on_activate(bool active);
|
||||
std::vector<std::string> get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const;
|
||||
std::vector<std::string> get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const;
|
||||
|
||||
std::vector<std::string> get_extruder_color_strings_from_plater_config(const GCodeProcessorResult* const result = nullptr) const;
|
||||
std::vector<std::string> get_color_strings_for_color_print(const GCodeProcessorResult* const result = nullptr) const;
|
||||
std::vector<ColorRGBA> get_extruder_colors_from_plater_config() const;
|
||||
std::vector<ColorRGBA> get_colors_for_color_print() const;
|
||||
|
||||
void update_menus();
|
||||
void show_action_buttons(const bool is_ready_to_slice) const;
|
||||
|
@ -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()
|
||||
|
@ -571,6 +571,8 @@ class TabSLAMaterial : public Tab
|
||||
void update_material_overrides_page();
|
||||
|
||||
std::map<std::string, wxWindow*> 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
|
||||
|
@ -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.
|
||||
// { "<keyword>": {"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<std::string>& result) const
|
||||
{
|
||||
try {
|
||||
|
@ -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<std::string>& result) const;
|
||||
void fill_material_from_json(const std::string& json, std::vector<std::string>& 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);
|
||||
|
||||
|
@ -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<std::string> 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<UserAccountSession>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lck(m_thread_stop_mutex);
|
||||
m_window_is_active = active;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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;
|
||||
|
@ -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<UserAccountSession> 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"; }
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
@ -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<std::string>("access_token");
|
||||
const auto refresh_token_optional = ptree.get_optional<std::string>("refresh_token");
|
||||
const auto shared_session_key_optional = ptree.get_optional<std::string>("shared_session_key");
|
||||
const auto expires_in_optional = ptree.get_optional<int>("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());
|
||||
|
@ -16,6 +16,7 @@ namespace GUI {
|
||||
using OpenPrusaAuthEvent = Event<wxString>;
|
||||
using UserAccountSuccessEvent = Event<std::string>;
|
||||
using UserAccountFailEvent = Event<std::string>;
|
||||
using UserAccountTimeEvent = Event<int>;
|
||||
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<void(const std::string& body)> UserActionSuccessFn;
|
||||
typedef std::function<void(const std::string& body)> 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<ActionQueueData> m_action_queue;
|
||||
std::queue<ActionQueueData> m_priority_action_queue;
|
||||
|
@ -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
|
||||
|
@ -15,6 +15,11 @@
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
// 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<std::string>("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<wxWebViewHandler>(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) {
|
||||
|
@ -1,18 +1,24 @@
|
||||
#ifndef slic3r_WebViewDialog_hpp_
|
||||
#define slic3r_WebViewDialog_hpp_
|
||||
|
||||
//#define DEBUG_URL_PANEL
|
||||
|
||||
#include <map>
|
||||
#include <wx/wx.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "UserAccountSession.hpp"
|
||||
|
||||
#ifdef DEBUG_URL_PANEL
|
||||
#include <wx/infobar.h>
|
||||
#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<std::string, std::function<void(void)>> 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<std::string, std::function<void(const std::string&)>> 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); }
|
||||
|
@ -329,7 +329,7 @@ std::vector<wxBitmapBundle*> get_extruder_color_icons(bool thin_icon/* = false*/
|
||||
{
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmapBundle*> bmps;
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_color_strings_from_plater_config();
|
||||
|
||||
if (colors.empty())
|
||||
return bmps;
|
||||
|
@ -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
|
||||
|
@ -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%")
|
||||
|
@ -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<Perimeters::LayerPerimeters> 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<Shells::Shells<>> 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<Shells::Shells<>> inputs;
|
||||
std::vector<Perimeters::LayerPerimeters> 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<Shells::Shells<>> inputs;
|
||||
std::vector<Perimeters::LayerPerimeters> 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) {
|
||||
|
@ -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<Polygon> shell_polygons{
|
||||
Seams::Shells::create_shells(extrusions, params.max_distance)};
|
||||
|
||||
const std::size_t shell_index{15};
|
||||
const std::vector<Seams::Geometry::BoundedPolygons> projected{
|
||||
Seams::Geometry::project_to_geometry(extrusions, params.max_distance)};
|
||||
|
||||
const ModelInfo::Visibility visibility{transformation, volumes, params.visibility, [](){}};
|
||||
Seams::Aligned::VisibilityCalculator
|
||||
|
@ -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<std::vector<SeamPerimeterChoice>> 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]};
|
||||
|
@ -167,15 +167,14 @@ void serialize_shell(std::ostream &output, const Shells::Shell<Perimeters::Perim
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeters][Integration]") {
|
||||
const Shells::Shells<> 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);
|
||||
}
|
||||
|
@ -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<std::vector<SeamPerimeterChoice>> 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<std::vector<SeamPerimeterChoice>> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<SeamChoice> 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<std::vector<SeamPerimeterChoice>> 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<std::vector<SeamPerimeterChoice>> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Polygon> &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<Polygon> &shell{shells[shell_id]};
|
||||
for (std::size_t slice_id{}; slice_id < shell.size(); ++slice_id) {
|
||||
const Shells::Slice<Polygon> &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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
123
tests/sla_print/sla_zcorrection_tests.cpp
Normal file
123
tests/sla_print/sla_zcorrection_tests.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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<ExPolygons> slices(Layers);
|
||||
std::vector<float> hgrid = grid(0.f, Layers * 1.f, 1.f);
|
||||
|
||||
std::vector<ExPolygons> 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<float> hgrid = grid<float>(bb.min.z(), bb.max.z(), 1.f);
|
||||
|
||||
std::vector<ExPolygons> 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<float> hgrid = grid<float>(bb.min.z(), bb.max.z(), 0.5f);
|
||||
|
||||
std::vector<ExPolygons> 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<float> 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<size_t>(std::ceil(depth/layer_h)));
|
||||
REQUIRE(sla::zcorr_detail::depth_to_layers(hgrid, i, depth) == expected_lyrs);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user