Merge branch 'master' into dk_archive_db_rebased

This commit is contained in:
Lukas Matena 2024-06-14 15:53:06 +02:00
commit df914f01d1
68 changed files with 1747 additions and 666 deletions

View File

@ -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

View 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

View 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

View File

@ -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}
)};

View File

@ -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

View File

@ -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_

View File

@ -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};

View File

@ -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
);

View File

@ -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 &params
) {
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

View File

@ -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 &params
);
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) {

View File

@ -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 &params,
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 &params,
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 &params,
@ -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 {

View File

@ -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{};

View File

@ -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

View File

@ -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
);
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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));

View File

@ -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 {

View File

@ -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;

View File

@ -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)
{

View File

@ -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

View File

@ -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");

View File

@ -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))

View 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

View 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

View File

@ -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,
};

View File

@ -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(

View File

@ -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));

View File

@ -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;

View File

@ -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]));

View File

@ -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;

View File

@ -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();

View File

@ -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
}

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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

View File

@ -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;
}

View File

@ -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();

View File

@ -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.");

View File

@ -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);

View File

@ -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;

View File

@ -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()

View File

@ -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

View File

@ -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 {

View File

@ -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);

View File

@ -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;

View File

@ -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"; }
};

View File

@ -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());

View File

@ -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;

View File

@ -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

View File

@ -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) {

View File

@ -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); }

View File

@ -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;

View File

@ -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

View File

@ -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%")

View File

@ -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) {

View File

@ -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

View File

@ -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]};

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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)

View 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);
}
}