From 2144f81bf1d890e214a7db7a98148b1c61d73464 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Oct 2022 13:18:02 +0200 Subject: [PATCH 001/206] Useful backend improvements from sla volumes branch --- src/libslic3r/AABBMesh.cpp | 3 - src/libslic3r/AABBMesh.hpp | 4 +- src/libslic3r/CMakeLists.txt | 7 +- src/libslic3r/CSGMesh/CSGMesh.hpp | 76 ++++ src/libslic3r/CSGMesh/ModelToCSGMesh.hpp | 58 +++ .../CSGMesh/PerformCSGMeshBooleans.hpp | 19 + src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 56 +++ src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 85 +++++ src/libslic3r/MTUtils.hpp | 83 +++++ src/libslic3r/MeshSplitImpl.hpp | 18 +- src/libslic3r/OpenVDBUtils.cpp | 243 +++++++++--- src/libslic3r/OpenVDBUtils.hpp | 67 ++-- src/libslic3r/OpenVDBUtilsLegacy.hpp | 100 +++++ src/libslic3r/SLA/Hollowing.cpp | 351 +++++++++++++----- src/libslic3r/SLA/Hollowing.hpp | 77 +++- src/libslic3r/SLA/SupportTree.hpp | 3 +- src/libslic3r/SLAPrint.cpp | 25 +- src/libslic3r/SLAPrint.hpp | 4 + src/libslic3r/SLAPrintSteps.cpp | 202 +--------- src/libslic3r/SlicesToTriangleMesh.cpp | 19 +- src/libslic3r/TreeSupport.cpp | 4 +- src/libslic3r/TriangleMesh.hpp | 28 +- src/libslic3r/libslic3r.h | 9 +- src/slic3r/GUI/MeshUtils.hpp | 12 +- tests/sla_print/sla_test_utils.cpp | 2 +- 25 files changed, 1126 insertions(+), 429 deletions(-) create mode 100644 src/libslic3r/CSGMesh/CSGMesh.hpp create mode 100644 src/libslic3r/CSGMesh/ModelToCSGMesh.hpp create mode 100644 src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp create mode 100644 src/libslic3r/CSGMesh/SliceCSGMesh.hpp create mode 100644 src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp create mode 100644 src/libslic3r/OpenVDBUtilsLegacy.hpp diff --git a/src/libslic3r/AABBMesh.cpp b/src/libslic3r/AABBMesh.cpp index 835df2ebe..b6dc68aae 100644 --- a/src/libslic3r/AABBMesh.cpp +++ b/src/libslic3r/AABBMesh.cpp @@ -69,7 +69,6 @@ public: template void AABBMesh::init(const M &mesh, bool calculate_epsilon) { BoundingBoxf3 bb = bounding_box(mesh); - m_ground_level += bb.min(Z); // Build the AABB accelaration tree m_aabb->init(*m_tm, calculate_epsilon); @@ -97,7 +96,6 @@ AABBMesh::~AABBMesh() {} AABBMesh::AABBMesh(const AABBMesh &other) : m_tm(other.m_tm) - , m_ground_level(other.m_ground_level) , m_aabb(new AABBImpl(*other.m_aabb)) , m_vfidx{other.m_vfidx} , m_fnidx{other.m_fnidx} @@ -106,7 +104,6 @@ AABBMesh::AABBMesh(const AABBMesh &other) AABBMesh &AABBMesh::operator=(const AABBMesh &other) { m_tm = other.m_tm; - m_ground_level = other.m_ground_level; m_aabb.reset(new AABBImpl(*other.m_aabb)); m_vfidx = other.m_vfidx; m_fnidx = other.m_fnidx; diff --git a/src/libslic3r/AABBMesh.hpp b/src/libslic3r/AABBMesh.hpp index 6a08c4303..179bf8c4c 100644 --- a/src/libslic3r/AABBMesh.hpp +++ b/src/libslic3r/AABBMesh.hpp @@ -28,7 +28,7 @@ class AABBMesh { class AABBImpl; const indexed_triangle_set* m_tm; - double m_ground_level = 0/*, m_gnd_offset = 0*/; +// double m_ground_level = 0/*, m_gnd_offset = 0*/; std::unique_ptr m_aabb; VertexFaceIndex m_vfidx; // vertex-face index @@ -57,7 +57,7 @@ public: ~AABBMesh(); - inline double ground_level() const { return m_ground_level /*+ m_gnd_offset*/; } +// inline double ground_level() const { return m_ground_level /*+ m_gnd_offset*/; } // inline void ground_level_offset(double o) { m_gnd_offset = o; } // inline double ground_level_offset() const { return m_gnd_offset; } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index bee7ceb62..e15848603 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -11,7 +11,7 @@ endif () set(OpenVDBUtils_SOURCES "") if (TARGET OpenVDB::openvdb) - set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp) + set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp OpenVDBUtilsLegacy.hpp) endif() set(SLIC3R_SOURCES @@ -39,6 +39,11 @@ set(SLIC3R_SOURCES Color.hpp Config.cpp Config.hpp + CSGMesh/CSGMesh.hpp + CSGMesh/SliceCSGMesh.hpp + CSGMesh/ModelToCSGMesh.hpp + CSGMesh/PerformCSGMeshBooleans.hpp + CSGMesh/VoxelizeCSGMesh.hpp EdgeGrid.cpp EdgeGrid.hpp ElephantFootCompensation.cpp diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp new file mode 100644 index 000000000..4655f684a --- /dev/null +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -0,0 +1,76 @@ +#ifndef CSGMESH_HPP +#define CSGMESH_HPP + +#include // for AnyPtr +#include + +namespace Slic3r { namespace csg { + +// A CSGPartT should be an object that can provide at least a mesh + trafo and an +// associated csg operation. A collection of CSGPartT objects can then +// be interpreted as one model and used in various contexts. It can be assembled +// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to +// deal with various parts of it according to the supported CSG types... +// +// A few simple templated interface functions are provided here and a default +// CSGPart class that implements the necessary means to be usable as a +// CSGPartT object. + +// Supported CSG operation types +enum class CSGType { Union, Difference, Intersection }; + +// Get the CSG operation of the part. Can be overriden for any type +template CSGType get_operation(const CSGPartT &part) +{ + return part.operation; +} + +// Get the mesh for the part. Can be overriden for any type +template +const indexed_triangle_set *get_mesh(const CSGPartT &part) +{ + return part.its_ptr.get(); +} + +// Get the transformation associated with the mesh inside a CSGPartT object. +// Can be overriden for any type. +template +Transform3f get_transform(const CSGPartT &part) +{ + return part.trafo; +} + +// Provide default overloads for indexed_triangle_set to be usable as a plain +// CSGPart with an implicit union operation + +inline CSGType get_operation(const indexed_triangle_set &part) +{ + return CSGType::Union; +} + +inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part) +{ + return ∂ +} + +inline Transform3f get_transform(const indexed_triangle_set &part) +{ + return Transform3f::Identity(); +} + +// Default implementation +struct CSGPart { + AnyPtr its_ptr; + Transform3f trafo; + CSGType operation; + + CSGPart(AnyPtr ptr = {}, + CSGType op = CSGType::Union, + const Transform3f &tr = Transform3f::Identity()) + : its_ptr{std::move(ptr)}, operation{op}, trafo{tr} + {} +}; + +}} // namespace Slic3r::csg + +#endif // CSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp new file mode 100644 index 000000000..f7247ea70 --- /dev/null +++ b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp @@ -0,0 +1,58 @@ +#ifndef MODELTOCSGMESH_HPP +#define MODELTOCSGMESH_HPP + +#include "CSGMesh.hpp" + +#include "libslic3r/Model.hpp" +#include "libslic3r/SLA/Hollowing.hpp" + +namespace Slic3r { namespace csg { + +enum ModelParts { + mpartsPositive = 1, + mpartsNegative = 2, + mpartsDrillHoles = 4 +}; + +template +void model_to_csgmesh(const ModelObject &mo, + const Transform3d &trafo, + OutIt out, + // values of ModelParts ORed + int parts_to_include = mpartsPositive + ) +{ + bool do_positives = parts_to_include & mpartsPositive; + bool do_negatives = parts_to_include & mpartsNegative; + bool do_drillholes = parts_to_include & mpartsDrillHoles; + + for (const ModelVolume *vol : mo.volumes) { + if (vol && vol->mesh_ptr() && + ((do_positives && vol->is_model_part()) || + (do_negatives && vol->is_negative_volume()))) { + CSGPart part{&(vol->mesh().its), + vol->is_model_part() ? CSGType::Union : CSGType::Difference, + (trafo * vol->get_matrix()).cast()}; + + *out = std::move(part); + ++out; + } + } + + if (do_drillholes) { + sla::DrainHoles drainholes = sla::transformed_drainhole_points(mo, trafo); + + for (const sla::DrainHole &dhole : drainholes) { + CSGPart part{std::make_unique( + dhole.to_mesh()), + CSGType::Difference}; + + *out = std::move(part); + ++out; + } + } +} + +}} // namespace Slic3r::csg + +#endif // MODELTOCSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp new file mode 100644 index 000000000..5c64c65e0 --- /dev/null +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -0,0 +1,19 @@ +#ifndef PERFORMCSGMESHBOOLEANS_HPP +#define PERFORMCSGMESHBOOLEANS_HPP + +#include "CSGMesh.hpp" + +#include "libslic3r/MeshBoolean.hpp" + +namespace Slic3r { + +template +void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMesh &cgalm, + const Range &csg) +{ + +} + +} // namespace Slic3r + +#endif // PERFORMCSGMESHBOOLEANS_HPP diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp new file mode 100644 index 000000000..95035b5ef --- /dev/null +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -0,0 +1,56 @@ +#ifndef SLICECSGMESH_HPP +#define SLICECSGMESH_HPP + +#include "CSGMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/ClipperUtils.hpp" + +namespace Slic3r { namespace csg { + +template +std::vector slice_csgmesh_ex( + const Range &csg, + const std::vector &slicegrid, + const MeshSlicingParamsEx ¶ms, + const std::function &throw_on_cancel = [] {}) +{ + std::vector ret(slicegrid.size()); + + MeshSlicingParamsEx params_cpy = params; + auto trafo = params.trafo; + for (const auto &m : csg) { + const indexed_triangle_set *its = csg::get_mesh(m); + if (!its) + continue; + + params_cpy.trafo = trafo * csg::get_transform(m).template cast(); + std::vector slices = slice_mesh_ex(*its, + slicegrid, params_cpy, + throw_on_cancel); + + assert(slices.size() == slicegrid.size()); + + for (size_t i = 0; i < slicegrid.size(); ++i) { + switch(get_operation(m)) { + case CSGType::Union: + for (ExPolygon &expoly : slices[i]) + ret[i].emplace_back(std::move(expoly)); + + ret[i] = union_ex(ret[i]); + break; + case CSGType::Difference: + ret[i] = diff_ex(ret[i], slices[i]); + break; + case CSGType::Intersection: + ret[i] = intersection_ex(ret[i], slices[i]); + break; + } + } + } + + return ret; +} + +}} // namespace Slic3r::csg + +#endif // SLICECSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp new file mode 100644 index 000000000..9d72e3ea8 --- /dev/null +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -0,0 +1,85 @@ +#ifndef VOXELIZECSGMESH_HPP +#define VOXELIZECSGMESH_HPP + +#include "CSGMesh.hpp" +#include "libslic3r/OpenVDBUtils.hpp" + +namespace Slic3r { namespace csg { + +class VoxelizeParams { + float m_voxel_scale = 1.f; + float m_exterior_bandwidth = 3.0f; + float m_interior_bandwidth = 3.0f; + +public: + float voxel_scale() const noexcept { return m_voxel_scale; } + float exterior_bandwidth() const noexcept { return m_exterior_bandwidth; } + float interior_bandwidth() const noexcept { return m_interior_bandwidth; } + + VoxelizeParams &voxel_scale(float val) noexcept + { + m_voxel_scale = val; + return *this; + } + VoxelizeParams &exterior_bandwidth(float val) noexcept + { + m_exterior_bandwidth = val; + return *this; + } + VoxelizeParams &interior_bandwidth(float val) noexcept + { + m_interior_bandwidth = val; + return *this; + } +}; + +// This method can be overriden when a specific CSGPart type supports caching +// of the voxel grid +template +VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, const VoxelizeParams ¶ms) +{ + const indexed_triangle_set *its = csg::get_mesh(csgpart); + VoxelGridPtr ret; + + if (its) + ret = mesh_to_grid(*csg::get_mesh(csgpart), + csg::get_transform(csgpart), + params.voxel_scale(), + params.exterior_bandwidth(), + params.interior_bandwidth()); + + return ret; +} + +template +VoxelGridPtr voxelize_csgmesh(const Range &csgrange, + const VoxelizeParams ¶ms = {}) +{ + VoxelGridPtr ret; + + for (auto &csgpart : csgrange) { + VoxelGridPtr partgrid = get_voxelgrid(csgpart, params); + + if (!ret && get_operation(csgpart) == CSGType::Union) { + ret = std::move(partgrid); + } else if (ret) { + switch (get_operation(csgpart)) { + case CSGType::Union: + grid_union(*ret, *partgrid); + break; + case CSGType::Difference: + grid_difference(*ret, *partgrid); + break; + case CSGType::Intersection: + grid_intersection(*ret, *partgrid); + break; + } + } + } + + return ret; +} + +}} // namespace Slic3r::csg + +#endif // VOXELIZECSGMESH_HPP diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index ab99ea5f6..74c44aa5e 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "libslic3r.h" @@ -136,6 +137,88 @@ inline std::vector> grid(const T &start, return vals; } +// A general purpose pointer holder that can hold any type of smart pointer +// or raw pointer which can own or not own any object they point to. +// In case a raw pointer is stored, it is not destructed so ownership is +// assumed to be foreign. +// +// The stored pointer is not checked for being null when dereferenced. +// +// This is a movable only object due to the fact that it can possibly hold +// a unique_ptr which a non-copy. +template +class AnyPtr { + enum { RawPtr, UPtr, ShPtr, WkPtr }; + + boost::variant, std::shared_ptr, std::weak_ptr> ptr; + + template static T *get_ptr(Self &&s) + { + switch (s.ptr.which()) { + case RawPtr: return boost::get(s.ptr); + case UPtr: return boost::get>(s.ptr).get(); + case ShPtr: return boost::get>(s.ptr).get(); + case WkPtr: { + auto shptr = boost::get>(s.ptr).lock(); + return shptr.get(); + } + } + + return nullptr; + } + +public: + template>> + AnyPtr(TT *p = nullptr) : ptr{p} + {} + template>> + AnyPtr(std::unique_ptr p) : ptr{std::unique_ptr(std::move(p))} + {} + template>> + AnyPtr(std::shared_ptr p) : ptr{std::shared_ptr(std::move(p))} + {} + template>> + AnyPtr(std::weak_ptr p) : ptr{std::weak_ptr(std::move(p))} + {} + + ~AnyPtr() = default; + + AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {} + AnyPtr(const AnyPtr &other) = delete; + + AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; } + AnyPtr &operator=(const AnyPtr &other) = delete; + + AnyPtr &operator=(T *p) { ptr = p; return *this; } + AnyPtr &operator=(std::unique_ptr p) { ptr = std::move(p); return *this; } + AnyPtr &operator=(std::shared_ptr p) { ptr = p; return *this; } + AnyPtr &operator=(std::weak_ptr p) { ptr = std::move(p); return *this; } + + const T &operator*() const { return *get_ptr(*this); } + T &operator*() { return *get_ptr(*this); } + + T *operator->() { return get_ptr(*this); } + const T *operator->() const { return get_ptr(*this); } + + T *get() { return get_ptr(*this); } + const T *get() const { return get_ptr(*this); } + + operator bool() const + { + switch (ptr.which()) { + case RawPtr: return bool(boost::get(ptr)); + case UPtr: return bool(boost::get>(ptr)); + case ShPtr: return bool(boost::get>(ptr)); + case WkPtr: { + auto shptr = boost::get>(ptr).lock(); + return bool(shptr); + } + } + + return false; + } +}; + } // namespace Slic3r #endif // MTUTILS_HPP diff --git a/src/libslic3r/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index c9df648fb..0f1fe44c3 100644 --- a/src/libslic3r/MeshSplitImpl.hpp +++ b/src/libslic3r/MeshSplitImpl.hpp @@ -108,6 +108,21 @@ template struct ItsNeighborsWrapper const auto& get_index() const noexcept { return index_ref; } }; +// Can be used as the second argument to its_split to apply a functor on each +// part, instead of collecting them into a container. +template +struct SplitOutputFn { + + Fn fn; + + SplitOutputFn(Fn f): fn{std::move(f)} {} + + SplitOutputFn &operator *() { return *this; } + void operator=(indexed_triangle_set &&its) { fn(std::move(its)); } + void operator=(indexed_triangle_set &its) { fn(its); } + SplitOutputFn& operator++() { return *this; }; +}; + // Splits a mesh into multiple meshes when possible. template void its_split(const Its &m, OutputIt out_it) @@ -155,7 +170,8 @@ void its_split(const Its &m, OutputIt out_it) mesh.indices.emplace_back(new_face); } - out_it = std::move(mesh); + *out_it = std::move(mesh); + ++out_it; } } diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 2c207bb6a..8ea110000 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -6,6 +6,7 @@ #pragma warning(push) #pragma warning(disable : 4146) #endif // _MSC_VER +#include #include #ifdef _MSC_VER #pragma warning(pop) @@ -16,14 +17,44 @@ #include #include -//#include "MTUtils.hpp" - namespace Slic3r { +struct VoxelGrid +{ + openvdb::FloatGrid grid; + + mutable std::optional accessor; + + template + VoxelGrid(Args &&...args): grid{std::forward(args)...} {} +}; + +void VoxelGridDeleter::operator()(VoxelGrid *ptr) { delete ptr; } + +// Similarly to std::make_unique() +template +VoxelGridPtr make_voxelgrid(Args &&...args) +{ + VoxelGrid *ptr = nullptr; + try { + ptr = new VoxelGrid(std::forward(args)...); + } catch(...) { + delete ptr; + } + + return VoxelGridPtr{ptr}; +} + +template VoxelGridPtr make_voxelgrid<>(); + +inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; } +inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast(); } +inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } + class TriangleMeshDataAdapter { public: const indexed_triangle_set &its; - float voxel_scale; + Transform3d trafo; size_t polygonCount() const { return its.indices.size(); } size_t pointCount() const { return its.vertices.size(); } @@ -35,19 +66,19 @@ public: void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const { auto vidx = size_t(its.indices[n](Eigen::Index(v))); - Slic3r::Vec3d p = its.vertices[vidx].cast() * voxel_scale; + Slic3r::Vec3d p = trafo * its.vertices[vidx].cast(); pos = {p.x(), p.y(), p.z()}; } - TriangleMeshDataAdapter(const indexed_triangle_set &m, float voxel_sc = 1.f) - : its{m}, voxel_scale{voxel_sc} {}; + TriangleMeshDataAdapter(const indexed_triangle_set &m, const Transform3d tr = Transform3d::Identity()) + : its{m}, trafo{tr} {} }; -openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh, - const openvdb::math::Transform &tr, - float voxel_scale, - float exteriorBandWidth, - float interiorBandWidth) +VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, + const Transform3f &tr, + float voxel_scale, + float exteriorBandWidth, + float interiorBandWidth) { // Might not be needed but this is now proven to be working openvdb::initialize(); @@ -55,51 +86,47 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh, std::vector meshparts = its_split(mesh); auto it = std::remove_if(meshparts.begin(), meshparts.end(), - [](auto &m) { return its_volume(m) < EPSILON; }); + [](auto &m) { + return its_volume(m) < EPSILON; + }); meshparts.erase(it, meshparts.end()); + Transform3d trafo = tr.cast(); + trafo.prescale(voxel_scale); + openvdb::FloatGrid::Ptr grid; for (auto &m : meshparts) { auto subgrid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{m, voxel_scale}, tr, 1.f, 1.f); + TriangleMeshDataAdapter{m, trafo}, {}, + exteriorBandWidth, interiorBandWidth); - if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); - else if (subgrid) grid = std::move(subgrid); + if (grid && subgrid) + openvdb::tools::csgUnion(*grid, *subgrid); + else if (subgrid) + grid = std::move(subgrid); } - if (meshparts.size() > 1) { - // This is needed to avoid various artefacts on multipart meshes. - // TODO: replace with something faster - grid = openvdb::tools::levelSetRebuild(*grid, 0., 1.f, 1.f); - } - if(meshparts.empty()) { + if (meshparts.empty()) { // Splitting failed, fall back to hollow the original mesh grid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{mesh}, tr, 1.f, 1.f); + TriangleMeshDataAdapter{mesh, trafo}, {}, exteriorBandWidth, + interiorBandWidth); } - constexpr int DilateIterations = 1; - - grid = openvdb::tools::dilateSdf( - *grid, interiorBandWidth, openvdb::tools::NN_FACE_EDGE, - DilateIterations, - openvdb::tools::FastSweepingDomain::SWEEP_LESS_THAN_ISOVALUE); - - grid = openvdb::tools::dilateSdf( - *grid, exteriorBandWidth, openvdb::tools::NN_FACE_EDGE, - DilateIterations, - openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE); + grid->transform().preScale(1./voxel_scale); grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); - return grid; + VoxelGridPtr ret = make_voxelgrid(std::move(*grid)); + + return ret; } -indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid, - double isovalue, - double adaptivity, - bool relaxDisorientedTriangles) +indexed_triangle_set grid_to_mesh(const VoxelGrid &vgrid, + double isovalue, + double adaptivity, + bool relaxDisorientedTriangles) { openvdb::initialize(); @@ -107,51 +134,147 @@ indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid, std::vector triangles; std::vector quads; + auto &grid = vgrid.grid; + openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, adaptivity, relaxDisorientedTriangles); - float scale = 1.; - try { - scale = grid.template metaValue("voxel_scale"); - } catch (...) { } - indexed_triangle_set ret; ret.vertices.reserve(points.size()); ret.indices.reserve(triangles.size() + quads.size() * 2); - for (auto &v : points) ret.vertices.emplace_back(to_vec3f(v) / scale); + for (auto &v : points) ret.vertices.emplace_back(to_vec3f(v) /*/ scale*/); for (auto &v : triangles) ret.indices.emplace_back(to_vec3i(v)); for (auto &quad : quads) { - ret.indices.emplace_back(quad(0), quad(1), quad(2)); - ret.indices.emplace_back(quad(2), quad(3), quad(0)); + ret.indices.emplace_back(quad(2), quad(1), quad(0)); + ret.indices.emplace_back(quad(0), quad(3), quad(2)); } return ret; } -openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, - double iso, - double er, - double ir) +VoxelGridPtr dilate_grid(const VoxelGrid &vgrid, + float exteriorBandWidth, + float interiorBandWidth) { - auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso), - float(er), float(ir)); + constexpr int DilateIterations = 1; + + openvdb::FloatGrid::Ptr new_grid; + + float scale = get_voxel_scale(vgrid); + + if (interiorBandWidth > 0.f) + new_grid = openvdb::tools::dilateSdf( + vgrid.grid, scale * interiorBandWidth, openvdb::tools::NN_FACE_EDGE, + DilateIterations, + openvdb::tools::FastSweepingDomain::SWEEP_LESS_THAN_ISOVALUE); + + auto &arg = new_grid? *new_grid : vgrid.grid; + + if (exteriorBandWidth > 0.f) + new_grid = openvdb::tools::dilateSdf( + arg, scale * exteriorBandWidth, openvdb::tools::NN_FACE_EDGE, + DilateIterations, + openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE); + + VoxelGridPtr ret; + + if (new_grid) + ret = make_voxelgrid(std::move(*new_grid)); + else + ret = make_voxelgrid(vgrid.grid); // Copies voxel_scale metadata, if it exists. - new_grid->insertMeta(*grid.deepCopyMeta()); + ret->grid.insertMeta(*vgrid.grid.deepCopyMeta()); - return new_grid; + return ret; } -openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, - double iso) +VoxelGridPtr redistance_grid(const VoxelGrid &vgrid, + float iso, + float er, + float ir) { - auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso)); + auto new_grid = openvdb::tools::levelSetRebuild(vgrid.grid, iso, er, ir); - // Copies voxel_scale metadata, if it exists. - new_grid->insertMeta(*grid.deepCopyMeta()); + auto ret = make_voxelgrid(std::move(*new_grid)); - return new_grid; + // Copies voxel_scale metadata, if it exists. + ret->grid.insertMeta(*vgrid.grid.deepCopyMeta()); + + return ret; +} + +VoxelGridPtr redistance_grid(const VoxelGrid &vgrid, float iso) +{ + auto new_grid = openvdb::tools::levelSetRebuild(vgrid.grid, iso); + + auto ret = make_voxelgrid(std::move(*new_grid)); + + // Copies voxel_scale metadata, if it exists. + ret->grid.insertMeta(*vgrid.grid.deepCopyMeta()); + + return ret; +} + +void grid_union(VoxelGrid &grid, VoxelGrid &arg) +{ + openvdb::tools::csgUnion(grid.grid, arg.grid); +} + +void grid_difference(VoxelGrid &grid, VoxelGrid &arg) +{ + openvdb::tools::csgDifference(grid.grid, arg.grid); +} + +void grid_intersection(VoxelGrid &grid, VoxelGrid &arg) +{ + openvdb::tools::csgIntersection(grid.grid, arg.grid); +} + +void reset_accessor(const VoxelGrid &vgrid) +{ + vgrid.accessor = vgrid.grid.getConstAccessor(); +} + +double get_distance_raw(const Vec3f &p, const VoxelGrid &vgrid) +{ + if (!vgrid.accessor) + reset_accessor(vgrid); + + auto v = (p).cast(); + auto grididx = vgrid.grid.transform().worldToIndexCellCentered( + {v.x(), v.y(), v.z()}); + + return vgrid.accessor->getValue(grididx) ; +} + +float get_voxel_scale(const VoxelGrid &vgrid) +{ + float scale = 1.; + try { + scale = vgrid.grid.template metaValue("voxel_scale"); + } catch (...) { } + + return scale; +} + +VoxelGridPtr clone(const VoxelGrid &grid) +{ + return make_voxelgrid(grid); +} + +void rescale_grid(VoxelGrid &grid, float scale) +{/* + float old_scale = get_voxel_scale(grid); + + float nscale = scale / old_scale;*/ +// auto tr = openvdb::math::Transform::createLinearTransform(scale); + grid.grid.transform().preScale(scale); + +// grid.grid.insertMeta("voxel_scale", openvdb::FloatMetadata(nscale)); + +// grid.grid.setTransform(tr); } } // namespace Slic3r diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index 254ae3583..959cd854d 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -3,21 +3,25 @@ #include -#ifdef _MSC_VER -// Suppress warning C4146 in include/gmp.h(2177,31): unary minus operator applied to unsigned type, result still unsigned -#pragma warning(push) -#pragma warning(disable : 4146) -#endif // _MSC_VER -#include -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER - namespace Slic3r { -inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; } -inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast(); } -inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } +struct VoxelGrid; +struct VoxelGridDeleter { void operator()(VoxelGrid *ptr); }; +using VoxelGridPtr = std::unique_ptr; + +// This is like std::make_unique for a voxelgrid +template VoxelGridPtr make_voxelgrid(Args &&...args); + +// Default constructed voxelgrid can be obtained this way. +extern template VoxelGridPtr make_voxelgrid<>(); + +void reset_accessor(const VoxelGrid &vgrid); + +double get_distance_raw(const Vec3f &p, const VoxelGrid &interior); + +float get_voxel_scale(const VoxelGrid &grid); + +VoxelGridPtr clone(const VoxelGrid &grid); // Here voxel_scale defines the scaling of voxels which affects the voxel count. // 1.0 value means a voxel for every unit cube. 2 means the model is scaled to @@ -26,24 +30,33 @@ inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1 // achievable through the Transform parameter. (TODO: or is it?) // The resulting grid will contain the voxel_scale in its metadata under the // "voxel_scale" key to be used in grid_to_mesh function. -openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh, - const openvdb::math::Transform &tr = {}, - float voxel_scale = 1.f, - float exteriorBandWidth = 3.0f, - float interiorBandWidth = 3.0f); +VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, + const Transform3f &tr = Transform3f::Identity(), + float voxel_scale = 1.f, + float exteriorBandWidth = 3.0f, + float interiorBandWidth = 3.0f); -indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid, - double isovalue = 0.0, - double adaptivity = 0.0, +indexed_triangle_set grid_to_mesh(const VoxelGrid &grid, + double isovalue = 0.0, + double adaptivity = 0.0, bool relaxDisorientedTriangles = true); -openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, - double iso); +VoxelGridPtr dilate_grid(const VoxelGrid &grid, + float exteriorBandWidth = 3.0f, + float interiorBandWidth = 3.0f); -openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, - double iso, - double ext_range, - double int_range); +VoxelGridPtr redistance_grid(const VoxelGrid &grid, float iso); + +VoxelGridPtr redistance_grid(const VoxelGrid &grid, + float iso, + float ext_range, + float int_range); + +void rescale_grid(VoxelGrid &grid, float scale); + +void grid_union(VoxelGrid &grid, VoxelGrid &arg); +void grid_difference(VoxelGrid &grid, VoxelGrid &arg); +void grid_intersection(VoxelGrid &grid, VoxelGrid &arg); } // namespace Slic3r diff --git a/src/libslic3r/OpenVDBUtilsLegacy.hpp b/src/libslic3r/OpenVDBUtilsLegacy.hpp new file mode 100644 index 000000000..f292af1c8 --- /dev/null +++ b/src/libslic3r/OpenVDBUtilsLegacy.hpp @@ -0,0 +1,100 @@ +#ifndef OPENVDBUTILSLEGACY_HPP +#define OPENVDBUTILSLEGACY_HPP + +#include "libslic3r/TriangleMesh.hpp" + +#ifdef _MSC_VER +// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned +#pragma warning(push) +#pragma warning(disable : 4146) +#endif // _MSC_VER +#include +#include +#include +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +namespace Slic3r { + +openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh, + const openvdb::math::Transform &tr, + float voxel_scale, + float exteriorBandWidth, + float interiorBandWidth) +{ + class TriangleMeshDataAdapter { + public: + const indexed_triangle_set &its; + float voxel_scale; + + size_t polygonCount() const { return its.indices.size(); } + size_t pointCount() const { return its.vertices.size(); } + size_t vertexCount(size_t) const { return 3; } + + // Return position pos in local grid index space for polygon n and vertex v + // The actual mesh will appear to openvdb as scaled uniformly by voxel_size + // And the voxel count per unit volume can be affected this way. + void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const + { + auto vidx = size_t(its.indices[n](Eigen::Index(v))); + Slic3r::Vec3d p = its.vertices[vidx].cast() * voxel_scale; + pos = {p.x(), p.y(), p.z()}; + } + + TriangleMeshDataAdapter(const indexed_triangle_set &m, float voxel_sc = 1.f) + : its{m}, voxel_scale{voxel_sc} {}; + }; + + // Might not be needed but this is now proven to be working + openvdb::initialize(); + + std::vector meshparts = its_split(mesh); + + auto it = std::remove_if(meshparts.begin(), meshparts.end(), + [](auto &m) { return its_volume(m) < EPSILON; }); + + meshparts.erase(it, meshparts.end()); + + openvdb::FloatGrid::Ptr grid; + for (auto &m : meshparts) { + auto subgrid = openvdb::tools::meshToVolume( + TriangleMeshDataAdapter{m, voxel_scale}, tr, 1.f, 1.f); + + if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); + else if (subgrid) grid = std::move(subgrid); + } + + if (meshparts.size() > 1) { + // This is needed to avoid various artefacts on multipart meshes. + // TODO: replace with something faster + grid = openvdb::tools::levelSetRebuild(*grid, 0., 1.f, 1.f); + } + if(meshparts.empty()) { + // Splitting failed, fall back to hollow the original mesh + grid = openvdb::tools::meshToVolume( + TriangleMeshDataAdapter{mesh}, tr, 1.f, 1.f); + } + + constexpr int DilateIterations = 1; + + grid = openvdb::tools::dilateSdf( + *grid, interiorBandWidth, openvdb::tools::NN_FACE_EDGE, + DilateIterations, + openvdb::tools::FastSweepingDomain::SWEEP_LESS_THAN_ISOVALUE); + + grid = openvdb::tools::dilateSdf( + *grid, exteriorBandWidth, openvdb::tools::NN_FACE_EDGE, + DilateIterations, + openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE); + + grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); + + return grid; +} + +} // namespace Slic3r + +#endif // OPENVDBUTILSLEGACY_HPP diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 74eb07695..3cc8ea340 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -10,11 +10,10 @@ #include #include #include +#include #include -#include - #include #include @@ -27,19 +26,17 @@ namespace sla { struct Interior { indexed_triangle_set mesh; - openvdb::FloatGrid::Ptr gridptr; - mutable std::optional accessor; + VoxelGridPtr gridptr; double iso_surface = 0.; double thickness = 0.; - double voxel_scale = 1.; double full_narrowb = 2.; void reset_accessor() const // This resets the accessor and its cache // Not a thread safe call! { if (gridptr) - accessor = gridptr->getConstAccessor(); + Slic3r::reset_accessor(*gridptr); } }; @@ -58,29 +55,30 @@ const indexed_triangle_set &get_mesh(const Interior &interior) return interior.mesh; } -static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, - const JobController &ctl, - double min_thickness, - double voxel_scale, - double closing_dist) +const VoxelGrid &get_grid(const Interior &interior) { - double offset = voxel_scale * min_thickness; - double D = voxel_scale * closing_dist; + return *interior.gridptr; +} + +VoxelGrid &get_grid(Interior &interior) +{ + return *interior.gridptr; +} + +InteriorPtr generate_interior(const VoxelGrid &vgrid, + const HollowingConfig &hc, + const JobController &ctl) +{ + double offset = hc.min_thickness; + double D = hc.closing_distance; float in_range = 1.1f * float(offset + D); - auto narrowb = 1.; + auto narrowb = 3.f / get_voxel_scale(vgrid); float out_range = narrowb; if (ctl.stopcondition()) return {}; else ctl.statuscb(0, L("Hollowing")); - auto gridptr = mesh_to_grid(mesh.its, {}, voxel_scale, out_range, in_range); - - assert(gridptr); - - if (!gridptr) { - BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL"; - return {}; - } + auto gridptr = dilate_grid(vgrid, out_range, in_range); if (ctl.stopcondition()) return {}; else ctl.statuscb(30, L("Hollowing")); @@ -90,12 +88,7 @@ static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, in_range = narrowb; gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, in_range); - constexpr int DilateIterations = 1; - - gridptr = openvdb::tools::dilateSdf( - *gridptr, std::ceil(iso_surface), - openvdb::tools::NN_FACE_EDGE_VERTEX, DilateIterations, - openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE); + gridptr = dilate_grid(*gridptr, std::ceil(iso_surface), 0.f); out_range = iso_surface; } else { @@ -109,68 +102,14 @@ static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, InteriorPtr interior = InteriorPtr{new Interior{}}; interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); - interior->gridptr = gridptr; + interior->gridptr = std::move(gridptr); if (ctl.stopcondition()) return {}; else ctl.statuscb(100, L("Hollowing")); interior->iso_surface = iso_surface; interior->thickness = offset; - interior->voxel_scale = voxel_scale; - interior->full_narrowb = out_range + in_range; - - return interior; -} - -InteriorPtr generate_interior(const TriangleMesh & mesh, - const HollowingConfig &hc, - const JobController & ctl) -{ - static constexpr double MIN_SAMPLES_IN_WALL = 3.5; - static constexpr double MAX_OVERSAMPL = 8.; - static constexpr double UNIT_VOLUME = 500000; // empiric - - // I can't figure out how to increase the grid resolution through openvdb - // API so the model will be scaled up before conversion and the result - // scaled down. Voxels have a unit size. If I set voxelSize smaller, it - // scales the whole geometry down, and doesn't increase the number of - // voxels. - // - // First an allowed range for voxel scale is determined from an initial - // range of . The final voxel scale is - // then chosen from this range using the 'quality:<0, 1>' parameter. - // The minimum can be lowered if the wall thickness is great enough and - // the maximum is lowered if the model volume very big. - double mesh_vol = its_volume(mesh.its); - double sc_divider = std::max(1.0, (mesh_vol / UNIT_VOLUME)); - double min_oversampl = std::max(MIN_SAMPLES_IN_WALL / hc.min_thickness, 1.); - double max_oversampl_scaled = std::max(min_oversampl, MAX_OVERSAMPL / sc_divider); - auto voxel_scale = min_oversampl + (max_oversampl_scaled - min_oversampl) * hc.quality; - - BOOST_LOG_TRIVIAL(debug) << "Hollowing: max oversampl will be: " << max_oversampl_scaled; - BOOST_LOG_TRIVIAL(debug) << "Hollowing: voxel scale will be: " << voxel_scale; - BOOST_LOG_TRIVIAL(debug) << "Hollowing: mesh volume is: " << mesh_vol; - - InteriorPtr interior = generate_interior_verbose(mesh, ctl, - hc.min_thickness, - voxel_scale, - hc.closing_distance); - - if (interior && !interior->mesh.empty()) { - - // flip normals back... - swap_normals(interior->mesh); - - // simplify mesh lossless - float loss_less_max_error = 2*std::numeric_limits::epsilon(); - its_quadric_edge_collapse(interior->mesh, 0U, &loss_less_max_error); - - its_compactify_vertices(interior->mesh); - its_merge_vertices(interior->mesh); - - // flip normals back... - swap_normals(interior->mesh); - } + interior->full_narrowb = (out_range + in_range) / 2.; return interior; } @@ -208,7 +147,6 @@ bool DrainHole::is_inside(const Vec3f& pt) const return false; } - // Given a line s+dir*t, find parameter t of intersections with the hole // and the normal (points inside the hole). Outputs through out reference, // returns true if two intersections were found. @@ -331,7 +269,7 @@ void cut_drainholes(std::vector & obj_slices, void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags) { - InteriorPtr interior = generate_interior(mesh, cfg, JobController{}); + InteriorPtr interior = generate_interior(mesh.its, cfg, JobController{}); if (!interior) return; hollow_mesh(mesh, *interior, flags); @@ -354,13 +292,7 @@ static double get_distance_raw(const Vec3f &p, const Interior &interior) { assert(interior.gridptr); - if (!interior.accessor) interior.reset_accessor(); - - auto v = (p * interior.voxel_scale).cast(); - auto grididx = interior.gridptr->transform().worldToIndexCellCentered( - {v.x(), v.y(), v.z()}); - - return interior.accessor->getValue(grididx) ; + return Slic3r::get_distance_raw(p, *interior.gridptr); } struct TriangleBubble { Vec3f center; double R; }; @@ -369,7 +301,7 @@ struct TriangleBubble { Vec3f center; double R; }; // triangle is too big to be measured. static double get_distance(const TriangleBubble &b, const Interior &interior) { - double R = b.R * interior.voxel_scale; + double R = b.R; double D = 2. * R; double Dst = get_distance_raw(b.center, interior); @@ -379,10 +311,16 @@ static double get_distance(const TriangleBubble &b, const Interior &interior) Dst - interior.iso_surface; } -double get_distance(const Vec3f &p, const Interior &interior) +inline double get_distance(const Vec3f &p, const Interior &interior) { double d = get_distance_raw(p, interior) - interior.iso_surface; - return d / interior.voxel_scale; + return d; +} + +template +FloatingOnly get_distance(const Vec<3, T> &p, const Interior &interior) +{ + return get_distance(Vec3f(p.template cast()), interior); } // A face that can be divided. Stores the indices into the original mesh if its @@ -500,7 +438,7 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, TriangleBubble bubble{facebb.center().cast(), facebb.radius()}; double D = get_distance(bubble, interior); - double R = bubble.R * interior.voxel_scale; + double R = bubble.R; if (std::isnan(D)) // The distance cannot be measured, triangle too big return true; @@ -585,4 +523,223 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, //FIXME do we want to repair the mesh? Are there duplicate vertices or flipped triangles? } +struct FaceHash { + + // A 64 bit number's max hex digits + static constexpr size_t MAX_NUM_CHARS = 16; + + // A hash is created for each triangle to be identifiable. The hash uses + // only the triangle's geometric traits, not the index in a particular mesh. + std::unordered_set facehash; + + // Returns the string in reverse, but that is ok for hashing + static std::array to_chars(int64_t val) + { + std::array ret; + + static const constexpr char * Conv = "0123456789abcdef"; + + auto ptr = ret.begin(); + auto uval = static_cast(std::abs(val)); + while (uval) { + *ptr = Conv[uval & 0xf]; + ++ptr; + uval = uval >> 4; + } + if (val < 0) { *ptr = '-'; ++ptr; } + *ptr = '\0'; // C style string ending + + return ret; + } + + static std::string hash(const Vec<3, int64_t> &v) + { + std::string ret; + ret.reserve(3 * MAX_NUM_CHARS); + + for (auto val : v) + ret += to_chars(val).data(); + + return ret; + } + + static std::string facekey(const Vec3i &face, const std::vector &vertices) + { + // Scale to integer to avoid floating points + std::array, 3> pts = { + scaled(vertices[face(0)]), + scaled(vertices[face(1)]), + scaled(vertices[face(2)]) + }; + + // Get the first two sides of the triangle, do a cross product and move + // that vector to the center of the triangle. This encodes all + // information to identify an identical triangle at the same position. + Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2]; + Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; + + // Return a concatenated string representation of the coordinates + return hash(c); + } + + FaceHash (const indexed_triangle_set &its): facehash(its.indices.size()) + { + for (const Vec3i &face : its.indices) + facehash.insert(facekey(face, its.vertices)); + } + + bool find(const std::string &key) + { + auto it = facehash.find(key); + return it != facehash.end(); + } +}; + + +static void exclude_neighbors(const Vec3i &face, + std::vector &mask, + const indexed_triangle_set &its, + const VertexFaceIndex &index, + size_t recursions) +{ + for (int i = 0; i < 3; ++i) { + const auto &neighbors_range = index[face(i)]; + for (size_t fi_n : neighbors_range) { + mask[fi_n] = true; + if (recursions > 0) + exclude_neighbors(its.indices[fi_n], mask, its, index, recursions - 1); + } + } +} + +std::vector create_exclude_mask(const indexed_triangle_set &its, + const Interior &interior, + const std::vector &holes) +{ + FaceHash interior_hash{sla::get_mesh(interior)}; + + std::vector exclude_mask(its.indices.size(), false); + + VertexFaceIndex neighbor_index{its}; + + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + + if (interior_hash.find(FaceHash::facekey(face, its.vertices))) { + exclude_mask[fi] = true; + continue; + } + + if (exclude_mask[fi]) { + exclude_neighbors(face, exclude_mask, its, neighbor_index, 1); + continue; + } + + // Lets deal with the holes. All the triangles of a hole and all the + // neighbors of these triangles need to be kept. The neigbors were + // created by CGAL mesh boolean operation that modified the original + // interior inside the input mesh to contain the holes. + Vec3d tr_center = ( + its.vertices[face(0)] + + its.vertices[face(1)] + + its.vertices[face(2)] + ).cast() / 3.; + + // If the center is more than half a mm inside the interior, + // it cannot possibly be part of a hole wall. + if (sla::get_distance(tr_center, interior) < -0.5) + continue; + + Vec3f U = its.vertices[face(1)] - its.vertices[face(0)]; + Vec3f V = its.vertices[face(2)] - its.vertices[face(0)]; + Vec3f C = U.cross(V); + Vec3f face_normal = C.normalized(); + + for (const sla::DrainHole &dh : holes) { + if (dh.failed) continue; + + Vec3d dhpos = dh.pos.cast(); + Vec3d dhend = dhpos + dh.normal.cast() * dh.height; + + Linef3 holeaxis{dhpos, dhend}; + + double D_hole_center = line_alg::distance_to(holeaxis, tr_center); + double D_hole = std::abs(D_hole_center - dh.radius); + float dot = dh.normal.dot(face_normal); + + // Empiric tolerances for center distance and normals angle. + // For triangles that are part of a hole wall the angle of + // triangle normal and the hole axis is around 90 degrees, + // so the dot product is around zero. + double D_tol = dh.radius / sla::DrainHole::steps; + float normal_angle_tol = 1.f / sla::DrainHole::steps; + + if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) { + exclude_mask[fi] = true; + exclude_neighbors(face, exclude_mask, its, neighbor_index, 1); + } + } + } + + return exclude_mask; +} + +DrainHoles transformed_drainhole_points(const ModelObject &mo, + const Transform3d &trafo) +{ + auto pts = mo.sla_drain_holes; + const Transform3d& vol_trafo = mo.volumes.front()->get_transformation().get_matrix(); + const Geometry::Transformation trans(trafo * vol_trafo); + const Transform3f& tr = trans.get_matrix().cast(); + const Vec3f sc = trans.get_scaling_factor().cast(); + for (sla::DrainHole &hl : pts) { + hl.pos = tr * hl.pos; + hl.normal = tr * hl.normal - tr.translation(); + + // The normal scales as a covector (and we must also + // undo the damage already done). + hl.normal = Vec3f(hl.normal(0)/(sc(0)*sc(0)), + hl.normal(1)/(sc(1)*sc(1)), + hl.normal(2)/(sc(2)*sc(2))); + + // Now shift the hole a bit above the object and make it deeper to + // compensate for it. This is to avoid problems when the hole is placed + // on (nearly) flat surface. + hl.pos -= hl.normal.normalized() * sla::HoleStickOutLength; + hl.height += sla::HoleStickOutLength; + } + + return pts; +} + +double get_voxel_scale(double mesh_volume, const HollowingConfig &hc) +{ + static constexpr double MIN_SAMPLES_IN_WALL = 3.5; + static constexpr double MAX_OVERSAMPL = 8.; + static constexpr double UNIT_VOLUME = 500000; // empiric + + // I can't figure out how to increase the grid resolution through openvdb + // API so the model will be scaled up before conversion and the result + // scaled down. Voxels have a unit size. If I set voxelSize smaller, it + // scales the whole geometry down, and doesn't increase the number of + // voxels. + // + // First an allowed range for voxel scale is determined from an initial + // range of . The final voxel scale is + // then chosen from this range using the 'quality:<0, 1>' parameter. + // The minimum can be lowered if the wall thickness is great enough and + // the maximum is lowered if the model volume very big. + + double sc_divider = std::max(1.0, (mesh_volume / UNIT_VOLUME)); + double min_oversampl = std::max(MIN_SAMPLES_IN_WALL / hc.min_thickness, 1.); + double max_oversampl_scaled = std::max(min_oversampl, MAX_OVERSAMPL / sc_divider); + auto voxel_scale = min_oversampl + (max_oversampl_scaled - min_oversampl) * hc.quality; + + BOOST_LOG_TRIVIAL(debug) << "Hollowing: max oversampl will be: " << max_oversampl_scaled; + BOOST_LOG_TRIVIAL(debug) << "Hollowing: voxel scale will be: " << voxel_scale; + BOOST_LOG_TRIVIAL(debug) << "Hollowing: mesh volume is: " << mesh_volume; + + return voxel_scale; +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index b57513fe7..6ff4660ba 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -3,10 +3,14 @@ #include #include +#include #include +#include namespace Slic3r { +class ModelObject; + namespace sla { struct HollowingConfig @@ -28,6 +32,9 @@ using InteriorPtr = std::unique_ptr; indexed_triangle_set & get_mesh(Interior &interior); const indexed_triangle_set &get_mesh(const Interior &interior); +const VoxelGrid & get_grid(const Interior &interior); +VoxelGrid &get_grid(Interior &interior); + struct DrainHole { Vec3f pos; @@ -46,18 +53,18 @@ struct DrainHole DrainHole(const DrainHole& rhs) : DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {} - + bool operator==(const DrainHole &sp) const; - + bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); } bool is_inside(const Vec3f& pt) const; bool get_intersections(const Vec3f& s, const Vec3f& dir, std::array, 2>& out) const; - + indexed_triangle_set to_mesh() const; - + template inline void serialize(Archive &ar) { ar(pos, normal, radius, height, failed); @@ -70,10 +77,51 @@ using DrainHoles = std::vector; constexpr float HoleStickOutLength = 1.f; -InteriorPtr generate_interior(const TriangleMesh &mesh, +double get_voxel_scale(double mesh_volume, const HollowingConfig &hc); + +InteriorPtr generate_interior(const VoxelGrid &mesh, const HollowingConfig & = {}, const JobController &ctl = {}); +inline InteriorPtr generate_interior(const indexed_triangle_set &mesh, + const HollowingConfig &hc = {}, + const JobController &ctl = {}) +{ + auto voxel_scale = get_voxel_scale(its_volume(mesh), hc); + auto grid = mesh_to_grid(mesh, Transform3f::Identity(), voxel_scale, 1.f, 1.f); + + if (its_is_splittable(mesh)) + grid = redistance_grid(*grid, 0.0f, 6.f / voxel_scale, 6.f / voxel_scale); + + return generate_interior(*grid, hc, ctl); +} + +template +InteriorPtr generate_interior(const Range &csgparts, + const HollowingConfig &hc = {}, + const JobController &ctl = {}) +{ + double mesh_vol = 0; + for (auto &part : csgparts) + mesh_vol = std::max(mesh_vol, + double(its_volume(*(csg::get_mesh(part))))); + + auto params = csg::VoxelizeParams{} + .voxel_scale(get_voxel_scale(mesh_vol, hc)) + .exterior_bandwidth(1.f) + .interior_bandwidth(1.f); + + auto ptr = csg::voxelize_csgmesh(csgparts, params); + + if (csgparts.size() > 1 || its_is_splittable(*csg::get_mesh(*csgparts.begin()))) + ptr = redistance_grid(*ptr, + 0.0f, + 6.f / params.voxel_scale(), + 6.f / params.voxel_scale()); + + return generate_interior(*ptr, hc, ctl); +} + // Will do the hollowing void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); @@ -83,13 +131,8 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, const std::vector &exclude_mask = {}); -double get_distance(const Vec3f &p, const Interior &interior); - -template -FloatingOnly get_distance(const Vec<3, T> &p, const Interior &interior) -{ - return get_distance(Vec3f(p.template cast()), interior); -} +sla::DrainHoles transformed_drainhole_points(const ModelObject &mo, + const Transform3d &trafo); void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, @@ -103,6 +146,16 @@ inline void swap_normals(indexed_triangle_set &its) std::swap(face(0), face(2)); } +// Create exclude mask for triangle removal inside hollowed interiors. +// This is necessary when the interior is already part of the mesh which was +// drilled using CGAL mesh boolean operation. Excluded will be the triangles +// originally part of the interior mesh and triangles that make up the drilled +// hole walls. +std::vector create_exclude_mask( + const indexed_triangle_set &its, + const sla::Interior &interior, + const std::vector &holes); + } // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 0ac29ff7a..ea785e640 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -108,6 +108,7 @@ struct SupportableMesh SupportPoints pts; SupportTreeConfig cfg; PadConfig pad_cfg; + double zoffset = 0.; explicit SupportableMesh(const indexed_triangle_set &trmsh, const SupportPoints &sp, @@ -124,7 +125,7 @@ struct SupportableMesh inline double ground_level(const SupportableMesh &sm) { - double lvl = sm.emesh.ground_level() - + double lvl = sm.zoffset - !bool(sm.pad_cfg.embed_object) * sm.cfg.enabled * sm.cfg.object_elevation_mm + bool(sm.pad_cfg.embed_object) * sm.pad_cfg.wall_thickness_mm; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d9b8e33df..3a93571c1 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1081,30 +1081,7 @@ sla::SupportPoints SLAPrintObject::transformed_support_points() const sla::DrainHoles SLAPrintObject::transformed_drainhole_points() const { - assert(m_model_object != nullptr); - auto pts = m_model_object->sla_drain_holes; - const Transform3d& vol_trafo = m_model_object->volumes.front()->get_transformation().get_matrix(); - const Geometry::Transformation trans(trafo() * vol_trafo); - const Transform3f& tr = trans.get_matrix().cast(); - const Vec3f sc = trans.get_scaling_factor().cast(); - for (sla::DrainHole &hl : pts) { - hl.pos = tr * hl.pos; - hl.normal = tr * hl.normal - tr.translation(); - - // The normal scales as a covector (and we must also - // undo the damage already done). - hl.normal = Vec3f(hl.normal(0)/(sc(0)*sc(0)), - hl.normal(1)/(sc(1)*sc(1)), - hl.normal(2)/(sc(2)*sc(2))); - - // Now shift the hole a bit above the object and make it deeper to - // compensate for it. This is to avoid problems when the hole is placed - // on (nearly) flat surface. - hl.pos -= hl.normal.normalized() * sla::HoleStickOutLength; - hl.height += sla::HoleStickOutLength; - } - - return pts; + return sla::transformed_drainhole_points(*this->model_object(), trafo()); } DynamicConfig SLAPrintStatistics::config() const diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 528d3c28b..c18f877b5 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -318,6 +318,10 @@ private: inline SupportData(const TriangleMesh &t) : input{t.its, {}, {}} {} + + inline SupportData(const indexed_triangle_set &t) + : input{t, {}, {}} + {} void create_support_tree(const sla::JobController &ctl) { diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 4498cb6b0..3ce185bd0 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -16,6 +16,7 @@ #include #include +#include #include @@ -134,209 +135,41 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) double quality = po.m_config.hollowing_quality.getFloat(); double closing_d = po.m_config.hollowing_closing_distance.getFloat(); sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; + sla::JobController ctl; + ctl.stopcondition = [this]() { return canceled(); }; + ctl.cancelfn = [this]() { throw_if_canceled(); }; - sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg); + sla::InteriorPtr interior = generate_interior(po.transformed_mesh().its, hlwcfg, ctl); if (!interior || sla::get_mesh(*interior).empty()) BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; else { po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); po.m_hollowing_data->interior = std::move(interior); - } -} -struct FaceHash { + indexed_triangle_set &m = sla::get_mesh(*po.m_hollowing_data->interior); - // A 64 bit number's max hex digits - static constexpr size_t MAX_NUM_CHARS = 16; + if (!m.empty()) { + // simplify mesh lossless + float loss_less_max_error = 2*std::numeric_limits::epsilon(); + its_quadric_edge_collapse(m, 0U, &loss_less_max_error); - // A hash is created for each triangle to be identifiable. The hash uses - // only the triangle's geometric traits, not the index in a particular mesh. - std::unordered_set facehash; + its_compactify_vertices(m); + its_merge_vertices(m); - // Returns the string in reverse, but that is ok for hashing - static std::array to_chars(int64_t val) - { - std::array ret; - - static const constexpr char * Conv = "0123456789abcdef"; - - auto ptr = ret.begin(); - auto uval = static_cast(std::abs(val)); - while (uval) { - *ptr = Conv[uval & 0xf]; - ++ptr; - uval = uval >> 4; - } - if (val < 0) { *ptr = '-'; ++ptr; } - *ptr = '\0'; // C style string ending - - return ret; - } - - static std::string hash(const Vec<3, int64_t> &v) - { - std::string ret; - ret.reserve(3 * MAX_NUM_CHARS); - - for (auto val : v) - ret += to_chars(val).data(); - - return ret; - } - - static std::string facekey(const Vec3i &face, const std::vector &vertices) - { - // Scale to integer to avoid floating points - std::array, 3> pts = { - scaled(vertices[face(0)]), - scaled(vertices[face(1)]), - scaled(vertices[face(2)]) - }; - - // Get the first two sides of the triangle, do a cross product and move - // that vector to the center of the triangle. This encodes all - // information to identify an identical triangle at the same position. - Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2]; - Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; - - // Return a concatenated string representation of the coordinates - return hash(c); - } - - FaceHash (const indexed_triangle_set &its): facehash(its.indices.size()) - { - for (const Vec3i &face : its.indices) - facehash.insert(facekey(face, its.vertices)); - } - - bool find(const std::string &key) - { - auto it = facehash.find(key); - return it != facehash.end(); - } -}; - -static void exclude_neighbors(const Vec3i &face, - std::vector &mask, - const indexed_triangle_set &its, - const VertexFaceIndex &index, - size_t recursions) -{ - for (int i = 0; i < 3; ++i) { - const auto &neighbors_range = index[face(i)]; - for (size_t fi_n : neighbors_range) { - mask[fi_n] = true; - if (recursions > 0) - exclude_neighbors(its.indices[fi_n], mask, its, index, recursions - 1); + // flip normals back... + sla::swap_normals(m); } } } -// Create exclude mask for triangle removal inside hollowed interiors. -// This is necessary when the interior is already part of the mesh which was -// drilled using CGAL mesh boolean operation. Excluded will be the triangles -// originally part of the interior mesh and triangles that make up the drilled -// hole walls. -static std::vector create_exclude_mask( - const indexed_triangle_set &its, - const sla::Interior &interior, - const std::vector &holes) -{ - FaceHash interior_hash{sla::get_mesh(interior)}; - - std::vector exclude_mask(its.indices.size(), false); - - VertexFaceIndex neighbor_index{its}; - - for (size_t fi = 0; fi < its.indices.size(); ++fi) { - auto &face = its.indices[fi]; - - if (interior_hash.find(FaceHash::facekey(face, its.vertices))) { - exclude_mask[fi] = true; - continue; - } - - if (exclude_mask[fi]) { - exclude_neighbors(face, exclude_mask, its, neighbor_index, 1); - continue; - } - - // Lets deal with the holes. All the triangles of a hole and all the - // neighbors of these triangles need to be kept. The neigbors were - // created by CGAL mesh boolean operation that modified the original - // interior inside the input mesh to contain the holes. - Vec3d tr_center = ( - its.vertices[face(0)] + - its.vertices[face(1)] + - its.vertices[face(2)] - ).cast() / 3.; - - // If the center is more than half a mm inside the interior, - // it cannot possibly be part of a hole wall. - if (sla::get_distance(tr_center, interior) < -0.5) - continue; - - Vec3f U = its.vertices[face(1)] - its.vertices[face(0)]; - Vec3f V = its.vertices[face(2)] - its.vertices[face(0)]; - Vec3f C = U.cross(V); - Vec3f face_normal = C.normalized(); - - for (const sla::DrainHole &dh : holes) { - if (dh.failed) continue; - - Vec3d dhpos = dh.pos.cast(); - Vec3d dhend = dhpos + dh.normal.cast() * dh.height; - - Linef3 holeaxis{dhpos, dhend}; - - double D_hole_center = line_alg::distance_to(holeaxis, tr_center); - double D_hole = std::abs(D_hole_center - dh.radius); - float dot = dh.normal.dot(face_normal); - - // Empiric tolerances for center distance and normals angle. - // For triangles that are part of a hole wall the angle of - // triangle normal and the hole axis is around 90 degrees, - // so the dot product is around zero. - double D_tol = dh.radius / sla::DrainHole::steps; - float normal_angle_tol = 1.f / sla::DrainHole::steps; - - if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) { - exclude_mask[fi] = true; - exclude_neighbors(face, exclude_mask, its, neighbor_index, 1); - } - } - } - - return exclude_mask; -} - static indexed_triangle_set remove_unconnected_vertices(const indexed_triangle_set &its) { if (its.indices.empty()) {}; indexed_triangle_set M; - - std::vector vtransl(its.vertices.size(), -1); - int vcnt = 0; - for (auto &f : its.indices) { - - for (int i = 0; i < 3; ++i) - if (vtransl[size_t(f(i))] < 0) { - - M.vertices.emplace_back(its.vertices[size_t(f(i))]); - vtransl[size_t(f(i))] = vcnt++; - } - - std::array new_f = { - vtransl[size_t(f(0))], - vtransl[size_t(f(1))], - vtransl[size_t(f(2))] - }; - - M.indices.emplace_back(new_f[0], new_f[1], new_f[2]); - } + its_compactify_vertices(M); return M; } @@ -583,6 +416,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) if (!po.m_supportdata) po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print())); + po.m_supportdata->input.zoffset = bounding_box(po.get_mesh_to_print()) + .min.z(); + const ModelObject& mo = *po.m_model_object; BOOST_LOG_TRIVIAL(debug) << "Support point count " @@ -661,7 +497,7 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) if (is_zero_elevation(po.config())) { remove_bottom_points(po.m_supportdata->input.pts, float( - po.m_supportdata->input.emesh.ground_level() + + po.m_supportdata->input.zoffset + EPSILON)); } diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp index 969fa8dac..3ea64e878 100644 --- a/src/libslic3r/SlicesToTriangleMesh.cpp +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -52,7 +52,8 @@ indexed_triangle_set slices_to_mesh( Layers layers(slices.size()); size_t len = slices.size() - 1; - tbb::parallel_for(size_t(0), len, [&slices, &layers, &grid](size_t i) { + auto threads_cnt = execution::max_concurrency(ex_tbb); + execution::for_each(ex_tbb, size_t(0), len, [&slices, &layers, &grid](size_t i) { const ExPolygons &upper = slices[i + 1]; const ExPolygons &lower = slices[i]; @@ -64,14 +65,15 @@ indexed_triangle_set slices_to_mesh( its_merge(layers[i], triangulate_expolygons_3d(free_top, grid[i], NORMALS_UP)); its_merge(layers[i], triangulate_expolygons_3d(overhang, grid[i], NORMALS_DOWN)); its_merge(layers[i], straight_walls(upper, grid[i], grid[i + 1])); - }); + }, threads_cnt); auto merge_fn = []( const indexed_triangle_set &a, const indexed_triangle_set &b ) { indexed_triangle_set res{a}; its_merge(res, b); return res; }; auto ret = execution::reduce(ex_tbb, layers.begin(), layers.end(), - indexed_triangle_set{}, merge_fn); + indexed_triangle_set{}, merge_fn, + threads_cnt); its_merge(ret, triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN)); its_merge(ret, straight_walls(slices.front(), zmin, grid.front())); @@ -80,9 +82,14 @@ indexed_triangle_set slices_to_mesh( // FIXME: these repairs do not fix the mesh entirely. There will be cracks // in the output. It is very hard to do the meshing in a way that does not // leave errors. - its_merge_vertices(ret); - its_remove_degenerate_faces(ret); - its_compactify_vertices(ret); + int num_mergedv = its_merge_vertices(ret); + BOOST_LOG_TRIVIAL(debug) << "Merged vertices count: " << num_mergedv; + + int remcnt = its_remove_degenerate_faces(ret); + BOOST_LOG_TRIVIAL(debug) << "Removed degenerate faces count: " << remcnt; + + int num_erasedv = its_compactify_vertices(ret); + BOOST_LOG_TRIVIAL(debug) << "Erased vertices count: " << num_erasedv; return ret; } diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1f0aa00b2..46a43b5a8 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -20,7 +20,7 @@ #include "MutablePolygon.hpp" #include "SupportMaterial.hpp" #include "TriangleMeshSlicer.hpp" -#include "OpenVDBUtils.hpp" +#include "OpenVDBUtilsLegacy.hpp" #include #include @@ -3441,7 +3441,7 @@ static void draw_branches( TriangleMesh mesh = print_object.model_object()->raw_mesh(); mesh.transform(print_object.trafo_centered()); double scale = 10.; - openvdb::FloatGrid::Ptr grid = mesh_to_grid(mesh.its, {}, scale, 0., 0.); + openvdb::FloatGrid::Ptr grid = mesh_to_grid(mesh.its, openvdb::math::Transform{}, scale, 0., 0.); closest_surface_point = openvdb::tools::ClosestSurfacePoint::create(*grid); std::vector pts, prev, projections; std::vector distances; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index c288b6d3a..2496b93d0 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -267,12 +267,18 @@ inline int its_triangle_edge_index(const stl_triangle_vertex_indices &triangle_i using its_triangle = std::array; +inline its_triangle its_triangle_vertices(const indexed_triangle_set &its, + const Vec3i &face) +{ + return {its.vertices[face(0)], + its.vertices[face(1)], + its.vertices[face(2)]}; +} + inline its_triangle its_triangle_vertices(const indexed_triangle_set &its, size_t face_id) { - return {its.vertices[its.indices[face_id](0)], - its.vertices[its.indices[face_id](1)], - its.vertices[its.indices[face_id](2)]}; + return its_triangle_vertices(its, its.indices[face_id]); } inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its, @@ -344,6 +350,22 @@ inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its) return {bmin.cast(), bmax.cast()}; } +inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its, const Transform3f &tr) +{ + if (its.vertices.empty()) + return {}; + + Vec3f bmin = tr * its.vertices.front(), bmax = tr * its.vertices.front(); + + for (const Vec3f &p : its.vertices) { + Vec3f pp = tr * p; + bmin = pp.cwiseMin(bmin); + bmax = pp.cwiseMax(bmax); + } + + return {bmin.cast(), bmax.cast()}; +} + } // Serialization through the Cereal library diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 1afd368aa..9b8a82348 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -352,10 +352,15 @@ public: Range(It b, It e) : from(std::move(b)), to(std::move(e)) {} // Some useful container-like methods... - inline size_t size() const { return end() - begin(); } - inline bool empty() const { return size() == 0; } + inline size_t size() const { return std::distance(from, to); } + inline bool empty() const { return from == to; } }; +template auto range(Cont &&cont) +{ + return Range{std::begin(cont), std::end(cont)}; +} + template> constexpr T NaN = std::numeric_limits::quiet_NaN(); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 9db2ed1b1..1815df9ca 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -157,13 +157,17 @@ class MeshRaycaster { public: #if ENABLE_RAYCAST_PICKING explicit MeshRaycaster(std::shared_ptr mesh) - : m_mesh(mesh) - , m_emesh(*mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length - , m_normals(its_face_normals(mesh->its)) + : m_mesh(std::move(mesh)) + , m_emesh(*m_mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length + , m_normals(its_face_normals(m_mesh->its)) { - assert(m_mesh != nullptr); + assert(m_mesh); } + explicit MeshRaycaster(const TriangleMesh &mesh) + : MeshRaycaster(std::make_unique(mesh)) + {} + static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction); #else diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 737659eed..00825997c 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -101,7 +101,7 @@ void test_supports(const std::string &obj_filename, REQUIRE_FALSE(mesh.empty()); if (hollowingcfg.enabled) { - sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg); + sla::InteriorPtr interior = sla::generate_interior(mesh.its, hollowingcfg); REQUIRE(interior); mesh.merge(TriangleMesh{sla::get_mesh(*interior)}); } From c448b312043aa7ec54107bf4e862a252c84d0ef4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Oct 2022 17:23:34 +0200 Subject: [PATCH 002/206] wip --- src/libslic3r/SLAPrint.cpp | 57 ++--- src/libslic3r/SLAPrint.hpp | 78 +++++-- src/libslic3r/SLAPrintSteps.cpp | 398 ++++++++++++++++---------------- src/libslic3r/SLAPrintSteps.hpp | 1 + 4 files changed, 295 insertions(+), 239 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 3a93571c1..9982a1476 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,13 +1,7 @@ #include "SLAPrint.hpp" #include "SLAPrintSteps.hpp" -#include "Format/SL1.hpp" -#include "Format/SL1_SVG.hpp" -#include "Format/pwmx.hpp" - -#include "ClipperUtils.hpp" #include "Geometry.hpp" -#include "MTUtils.hpp" #include "Thread.hpp" #include @@ -388,7 +382,12 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) it_print_object_status = print_object_status.end(); // Check whether a model part volume was added or removed, their transformations or order changed. - bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); + bool model_parts_differ = + model_volume_list_changed(model_object, model_object_new, + {ModelVolumeType::MODEL_PART, + ModelVolumeType::NEGATIVE_VOLUME, + ModelVolumeType::SUPPORT_ENFORCER, + ModelVolumeType::SUPPORT_BLOCKER}); bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || (! model_object.instances.empty() && @@ -597,7 +596,7 @@ void SLAPrint::process() // We want to first process all objects... std::vector level1_obj_steps = { - slaposHollowing, slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad + slaposAssembly, slaposHollowing, slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad }; // and then slice all supports to allow preview to be displayed ASAP @@ -793,12 +792,13 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) : Inherited(print, model_object) - , m_transformed_rmesh([this](TriangleMesh &obj) { - obj = m_model_object->raw_mesh(); - if (!obj.empty()) { - obj.transform(m_trafo); - } - }) +// , m_transformed_rmesh([this](TriangleMesh &obj) { +//// obj = m_model_object->raw_mesh(); +//// if (!obj.empty()) { +//// obj.transform(m_trafo); +//// } +// obj = transformed_mesh_csg(*this); +// }) {} SLAPrintObject::~SLAPrintObject() {} @@ -882,8 +882,10 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) { bool invalidated = Inherited::invalidate_step(step); // propagate to dependent steps - if (step == slaposHollowing) { + if (step == slaposAssembly) { invalidated |= this->invalidate_all_steps(); + } else if (step == slaposHollowing) { + invalidated |= invalidated |= this->invalidate_steps({ slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); } else if (step == slaposDrillHoles) { invalidated |= this->invalidate_steps({ slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); @@ -907,7 +909,7 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) bool SLAPrintObject::invalidate_all_steps() { - return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + return Inherited::invalidate_all_steps() || m_print->invalidate_all_steps(); } double SLAPrintObject::get_elevation() const { @@ -1001,8 +1003,12 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const { switch (step) { + case slaposAssembly: + return true; + case slaposObjectSlice: + return ! this->m_mesh_from_slices.empty(); case slaposDrillHoles: - return m_hollowing_data && !m_hollowing_data->hollow_mesh_with_holes.empty(); + return ! this->m_mesh_from_slices.empty(); case slaposSupportTree: return ! this->support_mesh().empty(); case slaposPad: @@ -1015,12 +1021,16 @@ bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const { switch (step) { + case slaposAssembly: + return m_transformed_rmesh; + case slaposObjectSlice: + return this->m_mesh_from_slices; case slaposSupportTree: return this->support_mesh(); case slaposPad: return this->pad_mesh(); case slaposDrillHoles: - if (m_hollowing_data) +// if (m_hollowing_data) return get_mesh_to_print(); [[fallthrough]]; default: @@ -1054,16 +1064,7 @@ const indexed_triangle_set &SLAPrintObject::hollowed_interior_mesh() const } const TriangleMesh &SLAPrintObject::transformed_mesh() const { - // we need to transform the raw mesh... - // currently all the instances share the same x and y rotation and scaling - // so we have to extract those from e.g. the first instance and apply to the - // raw mesh. This is also true for the support points. - // BUT: when the support structure is spawned for each instance than it has - // to omit the X, Y rotation and scaling as those have been already applied - // or apply an inverse transformation on the support structure after it - // has been created. - - return m_transformed_rmesh.get(); + return m_transformed_rmesh; } sla::SupportPoints SLAPrintObject::transformed_support_points() const diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index c18f877b5..f6d23cf89 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,16 +3,15 @@ #include #include +#include + #include "PrintBase.hpp" -#include "SLA/RasterBase.hpp" #include "SLA/SupportTree.hpp" #include "Point.hpp" -#include "MTUtils.hpp" -#include "Zipper.hpp" #include "Format/SLAArchiveWriter.hpp" #include "GCode/ThumbnailData.hpp" - -#include "libslic3r/Execution/ExecutionTBB.hpp" +#include "libslic3r/CSGMesh/CSGMesh.hpp" +#include "libslic3r/OpenVDBUtils.hpp" namespace Slic3r { @@ -23,6 +22,7 @@ enum SLAPrintStep : unsigned int { }; enum SLAPrintObjectStep : unsigned int { + slaposAssembly, slaposHollowing, slaposDrillHoles, slaposObjectSlice, @@ -45,6 +45,53 @@ using _SLAPrintObjectBase = enum SliceOrigin { soSupport, soModel }; +// Each sla object step can hold a collection of csg operations on the +// sla model to be sliced. Currently, Assembly step adds negative and positive +// volumes, hollowing adds the negative interior, drilling adds the hole cylinders. +// They need to be processed in this specific order. If CSGPartForStep instances +// are put into a multiset container the key being the sla step, +// iterating over the container will maintain the correct order of csg operations. +struct CSGPartForStep : public csg::CSGPart +{ + SLAPrintObjectStep key; + mutable struct { VoxelGridPtr gridptr; csg::VoxelizeParams params; } cache; + + CSGPartForStep(SLAPrintObjectStep k, CSGPart &&p = {}) + : key{k}, CSGPart{std::move(p)} + {} + + CSGPartForStep &operator=(CSGPart &&part) + { + this->its_ptr = std::move(part.its_ptr); + this->operation = part.operation; + return *this; + } + + bool operator<(const CSGPartForStep &other) const { return key < other.key; } +}; + +namespace csg { + +//inline VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, +// const VoxelizeParams &p) +//{ +// if (!part.cache.gridptr || get_voxel_scale(*part.cache.gridptr) < p.voxel_scale()) { +// part.cache.gridptr = mesh_to_grid(*csg::get_mesh(part), p.voxel_scale(), +// p.exterior_bandwidth(), +// p.interior_bandwidth()); + +// } /*else { +// float vscale = p.voxel_scale(); +// float oscale = get_voxel_scale(*part.cache.gridptr); + +// rescale_grid(*part.cache.gridptr, oscale/vscale); +// }*/ + +// return clone(*part.cache.gridptr); +//} + +} // namespace csg + class SLAPrintObject : public _SLAPrintObjectBase { private: // Prevents erroneous use by other classes. @@ -88,11 +135,7 @@ public: // Get the mesh that is going to be printed with all the modifications // like hollowing and drilled holes. const TriangleMesh & get_mesh_to_print() const { - return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh(); - } - - const TriangleMesh & get_mesh_to_slice() const { - return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh(); + return !m_mesh_from_slices.empty() ? m_mesh_from_slices : transformed_mesh(); } // This will return the transformed mesh which is cached @@ -275,7 +318,8 @@ protected: { m_config.apply_only(other, keys, ignore_nonexistent); } void set_trafo(const Transform3d& trafo, bool left_handed) { - m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); + m_trafo = trafo; + m_left_handed = left_handed; } template inline void set_instances(InstVec&& instances) { m_instances = std::forward(instances); } @@ -307,7 +351,7 @@ private: std::vector m_model_height_levels; // Caching the transformed (m_trafo) raw mesh of the object - mutable CachedObject m_transformed_rmesh; + TriangleMesh m_transformed_rmesh; struct SupportData { @@ -333,16 +377,16 @@ private: pad_mesh = TriangleMesh{sla::create_pad(input, tree_mesh.its, ctl)}; } }; - - std::unique_ptr m_supportdata; - + + std::unique_ptr m_supportdata; + TriangleMesh m_mesh_from_slices; + std::multiset m_mesh_to_slice; + class HollowingData { public: sla::InteriorPtr interior; - mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh - mutable TriangleMesh hollow_mesh_with_holes_trimmed; }; std::unique_ptr m_hollowing_data; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 3ce185bd0..74041c635 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -14,6 +14,12 @@ #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -33,7 +39,8 @@ namespace Slic3r { namespace { const std::array OBJ_STEP_LEVELS = { - 10, // slaposHollowing, + 5, // slaposAssembly + 5, // slaposHollowing, 10, // slaposDrillHoles 10, // slaposObjectSlice, 20, // slaposSupportPoints, @@ -45,6 +52,7 @@ const std::array OBJ_STEP_LEVELS = { std::string OBJ_STEP_LABELS(size_t idx) { switch (idx) { + case slaposAssembly: return L("Assembling model from parts"); case slaposHollowing: return L("Hollowing model"); case slaposDrillHoles: return L("Drilling holes into model."); case slaposObjectSlice: return L("Slicing model"); @@ -120,9 +128,38 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin } } +void clear_csg(std::multiset &s, SLAPrintObjectStep step) +{ + auto r = s.equal_range(step); + s.erase(r.first, r.second); +} + +struct csg_inserter { + std::multiset &m; + SLAPrintObjectStep key; + + csg_inserter &operator*() { return *this; } + void operator=(csg::CSGPart &&part) { m.emplace(key, std::move(part)); } + csg_inserter& operator++() { return *this; } +}; + +void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po) +{ + po.m_mesh_to_slice.clear(); + + po.m_transformed_rmesh = po.m_model_object->raw_mesh(); + po.m_transformed_rmesh.transform(po.trafo()); + + csg::model_to_csgmesh(*po.model_object(), po.trafo(), + csg_inserter{po.m_mesh_to_slice, slaposAssembly}, + csg::mpartsPositive | csg::mpartsNegative); +} + void SLAPrint::Steps::hollow_model(SLAPrintObject &po) { po.m_hollowing_data.reset(); + clear_csg(po.m_mesh_to_slice, slaposDrillHoles); + clear_csg(po.m_mesh_to_slice, slaposHollowing); if (! po.m_config.hollowing_enable.getBool()) { BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; @@ -139,7 +176,12 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - sla::InteriorPtr interior = generate_interior(po.transformed_mesh().its, hlwcfg, ctl); + sla::JobController ctl; + ctl.stopcondition = [this]() { return canceled(); }; + ctl.cancelfn = [this]() { throw_if_canceled(); }; + + sla::InteriorPtr interior = + generate_interior(range(po.m_mesh_to_slice), hlwcfg, ctl); if (!interior || sla::get_mesh(*interior).empty()) BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; @@ -147,166 +189,74 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); po.m_hollowing_data->interior = std::move(interior); - indexed_triangle_set &m = sla::get_mesh(*po.m_hollowing_data->interior); - - if (!m.empty()) { - // simplify mesh lossless - float loss_less_max_error = 2*std::numeric_limits::epsilon(); - its_quadric_edge_collapse(m, 0U, &loss_less_max_error); - - its_compactify_vertices(m); - its_merge_vertices(m); - - // flip normals back... - sla::swap_normals(m); - } + // Put the interior into the target mesh as a negative + po.m_mesh_to_slice + .emplace(slaposHollowing, + csg::CSGPart{&sla::get_mesh(*po.m_hollowing_data->interior), + csg::CSGType::Difference}); } } -static indexed_triangle_set -remove_unconnected_vertices(const indexed_triangle_set &its) -{ - if (its.indices.empty()) {}; - - indexed_triangle_set M; - its_compactify_vertices(M); - - return M; -} - // Drill holes into the hollowed/original mesh. void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { - bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty(); - bool is_hollowed = - (po.m_hollowing_data && po.m_hollowing_data->interior && - !sla::get_mesh(*po.m_hollowing_data->interior).empty()); + clear_csg(po.m_mesh_to_slice, slaposDrillHoles); - if (! is_hollowed && ! needs_drilling) { - // In this case we can dump any data that might have been - // generated on previous runs. - po.m_hollowing_data.reset(); - return; - } + csg::model_to_csgmesh(*po.model_object(), po.trafo(), + csg_inserter{po.m_mesh_to_slice, slaposDrillHoles}, + csg::mpartsDrillHoles); - if (! po.m_hollowing_data) - po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); + // update preview mesh + double vscale = 1. / 0.05; + auto voxparams = csg::VoxelizeParams{} + .voxel_scale(vscale) + .exterior_bandwidth(1.f) + .interior_bandwidth(1.f); + auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); + indexed_triangle_set mesh_from_slices = grid_to_mesh(*grid); + po.m_mesh_from_slices = TriangleMesh{mesh_from_slices}; +} - // Hollowing and/or drilling is active, m_hollowing_data is valid. - - // Regenerate hollowed mesh, even if it was there already. It may contain - // holes that are no longer on the frontend. - TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; - hollowed_mesh = po.transformed_mesh(); - if (is_hollowed) - sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); - - TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; - - if (! needs_drilling) { - mesh_view = po.transformed_mesh(); - - if (is_hollowed) - sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, - sla::hfRemoveInsideTriangles); - - BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; - return; - } - - BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; - sla::DrainHoles drainholes = po.transformed_drainhole_points(); - - auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - hollowed_mesh.its.vertices, - hollowed_mesh.its.indices - ); - - std::uniform_real_distribution dist(0., float(EPSILON)); - auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}, {}); - indexed_triangle_set part_to_drill = hollowed_mesh.its; - - bool hole_fail = false; - for (size_t i = 0; i < drainholes.size(); ++i) { - sla::DrainHole holept = drainholes[i]; - - holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; - holept.normal.normalize(); - holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; - indexed_triangle_set m = holept.to_mesh(); - - part_to_drill.indices.clear(); - auto bb = bounding_box(m); - Eigen::AlignedBox ebb{bb.min.cast(), - bb.max.cast()}; - - AABBTreeIndirect::traverse( - tree, - AABBTreeIndirect::intersecting(ebb), - [&part_to_drill, &hollowed_mesh](const auto& node) - { - part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[node.idx]); - }); - - auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal( - remove_unconnected_vertices(part_to_drill)); - - if (MeshBoolean::cgal::does_self_intersect(*cgal_meshpart)) { - BOOST_LOG_TRIVIAL(error) << "Failed to drill hole"; - - hole_fail = drainholes[i].failed = - po.model_object()->sla_drain_holes[i].failed = true; - - continue; +template +static std::vector slice_volumes( + const ModelVolumePtrs &volumes, + const std::vector &slice_grid, + const Transform3d &trafo, + const MeshSlicingParamsEx &slice_params, + Pred &&predicate) +{ + indexed_triangle_set mesh; + for (const ModelVolume *vol : volumes) { + if (predicate(vol)) { + indexed_triangle_set vol_mesh = vol->mesh().its; + its_transform(vol_mesh, trafo * vol->get_matrix()); + its_merge(mesh, vol_mesh); } - - auto cgal_hole = MeshBoolean::cgal::triangle_mesh_to_cgal(m); - MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_hole); } - if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) - throw Slic3r::SlicingError(L("Too many overlapping holes.")); + std::vector out; - auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); - - if (!MeshBoolean::cgal::does_bound_a_volume(*hollowed_mesh_cgal)) { - po.active_step_add_warning( - PrintStateBase::WarningLevel::NON_CRITICAL, - L("Mesh to be hollowed is not suitable for hollowing (does not " - "bound a volume).")); + if (!mesh.empty()) { + out = slice_mesh_ex(mesh, slice_grid, slice_params); } - if (!MeshBoolean::cgal::empty(*holes_mesh_cgal) - && !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) { - po.active_step_add_warning( - PrintStateBase::WarningLevel::NON_CRITICAL, - L("Unable to drill the current configuration of holes into the " - "model.")); + return out; +} + +template BoundingBoxf3 csgmesh_positive_bb(const Cont &csg) +{ + // Calculate the biggest possible bounding box of the mesh to be sliced + // from all the positive parts that it contains. + auto bb3d = csg.empty() ? BoundingBoxf3{} : + bounding_box(*csg::get_mesh(*csg.begin()), + csg::get_transform(*csg.begin())); + + for (const auto &m : csg) { + if (m.operation == csg::CSGType::Union) + bb3d.merge(bounding_box(*csg::get_mesh(m), csg::get_transform(m))); } - try { - if (!MeshBoolean::cgal::empty(*holes_mesh_cgal)) - MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); - - hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); - mesh_view = hollowed_mesh; - - if (is_hollowed) { - auto &interior = *po.m_hollowing_data->interior; - std::vector exclude_mask = - create_exclude_mask(mesh_view.its, interior, drainholes); - - sla::remove_inside_triangles(mesh_view, interior, exclude_mask); - } - } catch (const Slic3r::RuntimeError &) { - throw Slic3r::SlicingError(L( - "Drilling holes into the mesh failed. " - "This is usually caused by broken model. Try to fix it first.")); - } - - if (hole_fail) - po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, - L("Failed to drill some holes into the model")); + return bb3d; } // The slicing will be performed on an imaginary 1D grid which starts from @@ -319,14 +269,17 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // same imaginary grid (the height vector argument to TriangleMeshSlicer). void SLAPrint::Steps::slice_model(SLAPrintObject &po) { - const TriangleMesh &mesh = po.get_mesh_to_slice(); + // The first mesh in the csg sequence is assumed to be a positive part + assert(po.m_mesh_to_slice.empty() || + csg::get_operation(*po.m_mesh_to_slice.begin()) == csg::CSGType::Union); + + auto bb3d = csgmesh_positive_bb(po.m_mesh_to_slice); // We need to prepare the slice index... double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat(); float lh = float(lhd); coord_t lhs = scaled(lhd); - auto && bb3d = mesh.bounding_box(); double minZ = bb3d.min(Z) - po.get_elevation(); double maxZ = bb3d.max(Z); auto minZf = float(minZ); @@ -368,27 +321,9 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) } auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; - po.m_model_slices = slice_mesh_ex(mesh.its, slice_grid, params, thr); - sla::Interior *interior = po.m_hollowing_data ? - po.m_hollowing_data->interior.get() : - nullptr; - - if (interior && ! sla::get_mesh(*interior).empty()) { - indexed_triangle_set interiormesh = sla::get_mesh(*interior); - sla::swap_normals(interiormesh); - params.mode = MeshSlicingParams::SlicingMode::Regular; - - std::vector interior_slices = slice_mesh_ex(interiormesh, slice_grid, params, thr); - - execution::for_each( - ex_tbb, size_t(0), interior_slices.size(), - [&po, &interior_slices](size_t i) { - const ExPolygons &slice = interior_slices[i]; - po.m_model_slices[i] = diff_ex(po.m_model_slices[i], slice); - }, - execution::max_concurrency(ex_tbb)); - } + Range csgrange = {po.m_mesh_to_slice.begin(), po.m_mesh_to_slice.end()}; + po.m_model_slices = slice_csgmesh_ex(csgrange, slice_grid, params, thr); auto mit = slindex_it; for (size_t id = 0; @@ -400,10 +335,86 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) // We apply the printer correction offset here. apply_printer_corrections(po, soModel); - if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool()) - { - po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print())); +// auto simpl_slices = reserve_vector(po.m_model_slices.size()); +// for (const ExPolygons &slice : po.m_model_slices) { +// simpl_slices.emplace_back(expolygons_simplify(slice, scaled(1e-2))); +// } + +// po.m_mesh_from_slices = TriangleMesh{ +// slices_to_mesh(simpl_slices, slice_grid.front(), lhd, ilhd)}; + + double vscale = 1. / lhd; + auto voxparams = csg::VoxelizeParams{} + .voxel_scale(vscale) + .exterior_bandwidth(1.f) + .interior_bandwidth(1.f); + auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); + + assert(grid); + +// size_t max_face_cnt = 0; +// for (const CSGMesh &part : po.m_mesh_to_slice) +// max_face_cnt += part.its_ptr->indices.size(); + + indexed_triangle_set mesh_from_slices = grid_to_mesh(*grid); + +// its_quadric_edge_collapse(mesh_from_slices, vscale * max_face_cnt); + +// its_compactify_vertices(mesh_from_slices); +// its_merge_vertices(mesh_from_slices); + + po.m_mesh_from_slices = TriangleMesh{mesh_from_slices}; + +// po.m_mesh_from_slices = TriangleMesh{sla::get_mesh(*po.m_hollowing_data->interior)}; + +} + +static void filter_support_points_by_modifiers( + sla::SupportPoints &pts, + const std::vector &blockers, + const std::vector &enforcers, + const std::vector &slice_grid) +{ + assert((blockers.empty() || blockers.size() == slice_grid.size()) && + (enforcers.empty() || enforcers.size() == slice_grid.size())); + + auto new_pts = reserve_vector(pts.size()); + + for (size_t i = 0; i < pts.size(); ++i) { + const sla::SupportPoint &sp = pts[i]; + Point sp2d = scaled(to_2d(sp.pos)); + + auto it = std::lower_bound(slice_grid.begin(), slice_grid.end(), sp.pos.z()); + if (it != slice_grid.end()) { + size_t idx = std::distance(slice_grid.begin(), it); + bool is_enforced = false; + if (idx < enforcers.size()) { + for (size_t enf_idx = 0; + !is_enforced && enf_idx < enforcers[idx].size(); + ++enf_idx) + { + if (enforcers[idx][enf_idx].contains_b(sp2d)) + is_enforced = true; + } + } + + bool is_blocked = false; + if (!is_enforced && idx < blockers.size()) { + for (size_t blk_idx = 0; + !is_blocked && blk_idx < blockers[idx].size(); + ++blk_idx) + { + if (blockers[idx][blk_idx].contains_b(sp2d)) + is_blocked = true; + } + } + + if (!is_blocked) + new_pts.emplace_back(sp); + } } + + pts.swap(new_pts); } // In this step we check the slices, identify island and cover them with @@ -414,9 +425,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) if(!po.m_config.supports_enable.getBool()) return; if (!po.m_supportdata) - po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print())); + po.m_supportdata.reset(new SLAPrintObject::SupportData(po.m_mesh_from_slices)); - po.m_supportdata->input.zoffset = bounding_box(po.get_mesh_to_print()) + po.m_supportdata->input.zoffset = csgmesh_positive_bb(po.m_mesh_to_slice) .min.z(); const ModelObject& mo = *po.m_model_object; @@ -432,11 +443,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // calculate heights of slices (slices are calculated already) const std::vector& heights = po.m_model_height_levels; - // Tell the mesh where drain holes are. Although the points are - // calculated on slices, the algorithm then raycasts the points - // so they actually lie on the mesh. -// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); - throw_if_canceled(); sla::SupportPointGenerator::Config config; const SLAPrintObjectConfig& cfg = po.config(); @@ -464,8 +470,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) heights, config, [this]() { throw_if_canceled(); }, statuscb); // Now let's extract the result. - const std::vector& points = auto_supports.output(); + std::vector& points = auto_supports.output(); throw_if_canceled(); + + MeshSlicingParamsEx params; + params.closing_radius = float(po.config().slice_closing_radius.value); + std::vector blockers = + slice_volumes(po.model_object()->volumes, + po.m_model_height_levels, po.trafo(), params, + [](const ModelVolume *vol) { + return vol->is_support_blocker(); + }); + + std::vector enforcers = + slice_volumes(po.model_object()->volumes, + po.m_model_height_levels, po.trafo(), params, + [](const ModelVolume *vol) { + return vol->is_support_enforcer(); + }); + + filter_support_points_by_modifiers(points, blockers, enforcers, po.m_model_height_levels); + po.m_supportdata->input.pts = points; BOOST_LOG_TRIVIAL(debug) @@ -487,11 +512,6 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) { if(!po.m_supportdata) return; -// sla::PadConfig pcfg = make_pad_cfg(po.m_config); - -// if (pcfg.embed_object) -// po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); - // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (is_zero_elevation(po.config())) { @@ -503,7 +523,6 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) po.m_supportdata->input.cfg = make_support_cfg(po.m_config); po.m_supportdata->input.pad_cfg = make_pad_cfg(po.m_config); -// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); // scaling for the sub operations double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; @@ -554,16 +573,6 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { sla::PadConfig pcfg = make_pad_cfg(po.m_config); po.m_supportdata->input.pad_cfg = pcfg; -// if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { -// // No support (thus no elevation) or zero elevation mode -// // we sometimes call it "builtin pad" is enabled so we will -// // get a sample from the bottom of the mesh and use it for pad -// // creation. -// sla::pad_blueprint(trmesh.its, bp, float(pad_h), -// float(po.m_config.layer_height.getFloat()), -// [this](){ throw_if_canceled(); }); -// } - sla::JobController ctl; ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; @@ -996,6 +1005,7 @@ double SLAPrint::Steps::progressrange(SLAPrintStep step) const void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj) { switch(step) { + case slaposAssembly: mesh_assembly(obj); break; case slaposHollowing: hollow_model(obj); break; case slaposDrillHoles: drill_holes(obj); break; case slaposObjectSlice: slice_model(obj); break; diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index 19b64d4a9..1a1900153 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -48,6 +48,7 @@ private: public: explicit Steps(SLAPrint *print); + void mesh_assembly(SLAPrintObject &po); void hollow_model(SLAPrintObject &po); void drill_holes (SLAPrintObject &po); void slice_model(SLAPrintObject& po); From 99581a5e53428e7bca4b00cfdd67da9e887c6929 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Oct 2022 17:50:24 +0200 Subject: [PATCH 003/206] Fix unguarded header --- src/libslic3r/QuadricEdgeCollapse.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libslic3r/QuadricEdgeCollapse.hpp b/src/libslic3r/QuadricEdgeCollapse.hpp index 956ad3511..0043fc24a 100644 --- a/src/libslic3r/QuadricEdgeCollapse.hpp +++ b/src/libslic3r/QuadricEdgeCollapse.hpp @@ -4,6 +4,8 @@ // paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf // sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/ // inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification +#ifndef PRUSASLICER_QUADRIC_EDGE_COLLAPSE_HPP +#define PRUSASLICER_QUADRIC_EDGE_COLLAPSE_HPP #include #include @@ -30,3 +32,5 @@ void its_quadric_edge_collapse( } // namespace Slic3r #endif // slic3r_quadric_edge_collapse_hpp_ + +#endif // PRUSASLICER_QUADRIC_EDGE_COLLAPSE_HPP From ab9f231c0c303511cfb565738fa7e05a7dcf37d2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Oct 2022 17:52:00 +0200 Subject: [PATCH 004/206] Fix broken compilation --- src/libslic3r/SLAPrintSteps.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 74041c635..a024a799c 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -176,10 +176,6 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - sla::JobController ctl; - ctl.stopcondition = [this]() { return canceled(); }; - ctl.cancelfn = [this]() { throw_if_canceled(); }; - sla::InteriorPtr interior = generate_interior(range(po.m_mesh_to_slice), hlwcfg, ctl); From 39a1ed0e1ae16d8b12648d344f67b68ac14ff810 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Oct 2022 18:29:00 +0200 Subject: [PATCH 005/206] WIP: prepare frontend to use new sla backend interface --- src/libslic3r/SLAPrint.cpp | 100 +++++++++++++++++----------------- src/libslic3r/SLAPrint.hpp | 14 ++--- src/slic3r/GUI/GLCanvas3D.cpp | 78 +++++++++++++------------- 3 files changed, 97 insertions(+), 95 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 9982a1476..716029257 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1000,72 +1000,74 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const return idx >= v.size() ? EMPTY_SLICE : v[idx]; } -bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const -{ - switch (step) { - case slaposAssembly: - return true; - case slaposObjectSlice: - return ! this->m_mesh_from_slices.empty(); - case slaposDrillHoles: - return ! this->m_mesh_from_slices.empty(); - case slaposSupportTree: - return ! this->support_mesh().empty(); - case slaposPad: - return ! this->pad_mesh().empty(); - default: - return false; - } -} +//bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const +//{ +// switch (step) { +// case slaposAssembly: +// return true; +// case slaposObjectSlice: +// return ! this->m_mesh_from_slices.empty(); +// case slaposDrillHoles: +// return ! this->m_mesh_from_slices.empty(); +// case slaposSupportTree: +// return ! this->support_mesh().empty(); +// case slaposPad: +// return ! this->pad_mesh().empty(); +// default: +// return false; +// } +//} -TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const -{ - switch (step) { - case slaposAssembly: - return m_transformed_rmesh; - case slaposObjectSlice: - return this->m_mesh_from_slices; - case slaposSupportTree: - return this->support_mesh(); - case slaposPad: - return this->pad_mesh(); - case slaposDrillHoles: -// if (m_hollowing_data) - return get_mesh_to_print(); - [[fallthrough]]; - default: - return TriangleMesh(); - } -} +//TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const +//{ +// switch (step) { +// case slaposAssembly: +// return m_transformed_rmesh; +// case slaposObjectSlice: +// return this->m_mesh_from_slices; +// case slaposSupportTree: +// return this->support_mesh(); +// case slaposPad: +// return this->pad_mesh(); +// case slaposDrillHoles: +//// if (m_hollowing_data) +// return get_mesh_to_print(); +// [[fallthrough]]; +// default: +// return TriangleMesh(); +// } +//} const TriangleMesh& SLAPrintObject::support_mesh() const { - if(m_config.supports_enable.getBool() && m_supportdata) + if (m_config.supports_enable.getBool() && + is_step_done(slaposSupportTree) && + m_supportdata) return m_supportdata->tree_mesh; - + return EMPTY_MESH; } const TriangleMesh& SLAPrintObject::pad_mesh() const { - if(m_config.pad_enable.getBool() && m_supportdata) + if(m_config.pad_enable.getBool() && is_step_done(slaposPad) && m_supportdata) return m_supportdata->pad_mesh; return EMPTY_MESH; } -const indexed_triangle_set &SLAPrintObject::hollowed_interior_mesh() const -{ - if (m_hollowing_data && m_hollowing_data->interior && - m_config.hollowing_enable.getBool()) - return sla::get_mesh(*m_hollowing_data->interior); +//const indexed_triangle_set &SLAPrintObject::hollowed_interior_mesh() const +//{ +// if (m_hollowing_data && m_hollowing_data->interior && +// m_config.hollowing_enable.getBool()) +// return sla::get_mesh(*m_hollowing_data->interior); - return EMPTY_TRIANGLE_SET; -} +// return EMPTY_TRIANGLE_SET; +//} -const TriangleMesh &SLAPrintObject::transformed_mesh() const { - return m_transformed_rmesh; -} +//const TriangleMesh &SLAPrintObject::transformed_mesh() const { +// return m_transformed_rmesh; +//} sla::SupportPoints SLAPrintObject::transformed_support_points() const { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index f6d23cf89..a02164508 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -119,8 +119,8 @@ public: }; const std::vector& instances() const { return m_instances; } - bool has_mesh(SLAPrintObjectStep step) const; - TriangleMesh get_mesh(SLAPrintObjectStep step) const; +// bool has_mesh(SLAPrintObjectStep step) const; +// TriangleMesh get_mesh(SLAPrintObjectStep step) const; // Get a support mesh centered around origin in XY, and with zero rotation around Z applied. // Support mesh is only valid if this->is_step_done(slaposSupportTree) is true. @@ -129,17 +129,17 @@ public: // Support mesh is only valid if this->is_step_done(slaposPad) is true. const TriangleMesh& pad_mesh() const; - // Ready after this->is_step_done(slaposDrillHoles) is true - const indexed_triangle_set &hollowed_interior_mesh() const; +// // Ready after this->is_step_done(slaposDrillHoles) is true +// const indexed_triangle_set &hollowed_interior_mesh() const; // Get the mesh that is going to be printed with all the modifications // like hollowing and drilled holes. const TriangleMesh & get_mesh_to_print() const { - return !m_mesh_from_slices.empty() ? m_mesh_from_slices : transformed_mesh(); + return !m_mesh_from_slices.empty() ? m_mesh_from_slices : m_transformed_rmesh; } - // This will return the transformed mesh which is cached - const TriangleMesh& transformed_mesh() const; +// // This will return the transformed mesh which is cached +// const TriangleMesh& transformed_mesh() const; sla::SupportPoints transformed_support_points() const; sla::DrainHoles transformed_drainhole_points() const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 11d1bb80c..8c98c603d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2011,14 +2011,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re size_t volume_idx; }; - // SLA steps to pull the preview meshes for. - typedef std::array SLASteps; - SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; - struct SLASupportState { - std::array::value> step; - }; - // State of the sla_steps for all SLAPrintObjects. - std::vector sla_support_state; +// // SLA steps to pull the preview meshes for. +// typedef std::array SLASteps; +// SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; +// struct SLASupportState { +// std::array::value> step; +// }; +// // State of the sla_steps for all SLAPrintObjects. +// std::vector sla_support_state; std::vector instance_ids_selected; std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); @@ -2044,33 +2044,33 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } } } - if (printer_technology == ptSLA) { - const SLAPrint* sla_print = this->sla_print(); -#ifndef NDEBUG - // Verify that the SLAPrint object is synchronized with m_model. - check_model_ids_equal(*m_model, sla_print->model()); -#endif /* NDEBUG */ - sla_support_state.reserve(sla_print->objects().size()); - for (const SLAPrintObject* print_object : sla_print->objects()) { - SLASupportState state; - for (size_t istep = 0; istep < sla_steps.size(); ++istep) { - state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); - if (state.step[istep].state == PrintStateBase::DONE) { - if (!print_object->has_mesh(sla_steps[istep])) - // Consider the DONE step without a valid mesh as invalid for the purpose - // of mesh visualization. - state.step[istep].state = PrintStateBase::INVALID; - else if (sla_steps[istep] != slaposDrillHoles) - for (const ModelInstance* model_instance : print_object->model_object()->instances) - // Only the instances, which are currently printable, will have the SLA support structures kept. - // The instances outside the print bed will have the GLVolumes of their support structures released. - if (model_instance->is_printable()) - aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); - } - } - sla_support_state.emplace_back(state); - } - } +// if (printer_technology == ptSLA) { +// const SLAPrint* sla_print = this->sla_print(); +//#ifndef NDEBUG +// // Verify that the SLAPrint object is synchronized with m_model. +// check_model_ids_equal(*m_model, sla_print->model()); +//#endif /* NDEBUG */ +// sla_support_state.reserve(sla_print->objects().size()); +// for (const SLAPrintObject* print_object : sla_print->objects()) { +// SLASupportState state; +// for (size_t istep = 0; istep < sla_steps.size(); ++istep) { +// state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); +// if (state.step[istep].state == PrintStateBase::DONE) { +// if (!print_object->has_mesh(sla_steps[istep])) +// // Consider the DONE step without a valid mesh as invalid for the purpose +// // of mesh visualization. +// state.step[istep].state = PrintStateBase::INVALID; +// else if (sla_steps[istep] != slaposDrillHoles) +// for (const ModelInstance* model_instance : print_object->model_object()->instances) +// // Only the instances, which are currently printable, will have the SLA support structures kept. +// // The instances outside the print bed will have the GLVolumes of their support structures released. +// if (model_instance->is_printable()) +// aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); +// } +// } +// sla_support_state.emplace_back(state); +// } +// } std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); // Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh. @@ -7642,10 +7642,10 @@ void GLCanvas3D::_load_sla_shells() // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when // through the update_volumes_colors_by_extruder() call. m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); - if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) - add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); - if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) - add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); + if (auto &tree_mesh = obj->support_mesh(); !tree_mesh.empty()) + add_volume(*obj, -int(slaposSupportTree), instance, tree_mesh, GLVolume::SLA_SUPPORT_COLOR, true); + if (auto &pad_mesh = obj->pad_mesh(); !pad_mesh.empty()) + add_volume(*obj, -int(slaposPad), instance, pad_mesh, GLVolume::SLA_PAD_COLOR, false); } double shift_z = obj->get_current_elevation(); for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { From 15fa4c42d6d686cc6fdf720c14cdc1f7d7c00cde Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Oct 2022 16:30:58 +0200 Subject: [PATCH 006/206] Finalize new sla backend interface no has_mesh or get_mesh based on states, but specific methods to get the mesh type needed (support, pad, object) Commented out everything that does not conform in frontend --- src/libslic3r/SLAPrint.cpp | 17 ++ src/libslic3r/SLAPrint.hpp | 66 ++++-- src/libslic3r/SLAPrintSteps.cpp | 112 +++++---- src/libslic3r/SLAPrintSteps.hpp | 2 + src/slic3r/GUI/3DScene.cpp | 134 +++++------ src/slic3r/GUI/3DScene.hpp | 16 +- src/slic3r/GUI/GLCanvas3D.cpp | 275 ++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 12 +- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 24 +- 10 files changed, 366 insertions(+), 294 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 716029257..c3b90b192 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1056,6 +1056,23 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const return EMPTY_MESH; } +const TriangleMesh &SLAPrintObject::get_mesh_to_print() const { + const TriangleMesh *ret = nullptr; + + int s = SLAPrintObjectStep::slaposCount; + + while (s > 0 && !ret) { + --s; + if (is_step_done(SLAPrintObjectStep(s)) && !m_preview_meshes[s].empty()) + ret = &m_preview_meshes[s]; + } + + if (!ret) + ret = &m_transformed_rmesh; + + return *ret; +} + //const indexed_triangle_set &SLAPrintObject::hollowed_interior_mesh() const //{ // if (m_hollowing_data && m_hollowing_data->interior && diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a02164508..084c78024 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -45,6 +45,24 @@ using _SLAPrintObjectBase = enum SliceOrigin { soSupport, soModel }; +} // namespace Slic3r + +namespace std { + +template<> struct hash { + size_t operator() (const Slic3r::csg::VoxelizeParams &p) const { + std::string str = Slic3r::float_to_string_decimal_point(p.voxel_scale()); + str += Slic3r::float_to_string_decimal_point(p.exterior_bandwidth()); + str += Slic3r::float_to_string_decimal_point(p.interior_bandwidth()); + + return std::hash{}(str); + } +}; + +} // namespace std + +namespace Slic3r { + // Each sla object step can hold a collection of csg operations on the // sla model to be sliced. Currently, Assembly step adds negative and positive // volumes, hollowing adds the negative interior, drilling adds the hole cylinders. @@ -54,7 +72,7 @@ enum SliceOrigin { soSupport, soModel }; struct CSGPartForStep : public csg::CSGPart { SLAPrintObjectStep key; - mutable struct { VoxelGridPtr gridptr; csg::VoxelizeParams params; } cache; + mutable std::unordered_map gridcache; CSGPartForStep(SLAPrintObjectStep k, CSGPart &&p = {}) : key{k}, CSGPart{std::move(p)} @@ -64,6 +82,7 @@ struct CSGPartForStep : public csg::CSGPart { this->its_ptr = std::move(part.its_ptr); this->operation = part.operation; + return *this; } @@ -72,23 +91,27 @@ struct CSGPartForStep : public csg::CSGPart namespace csg { -//inline VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, -// const VoxelizeParams &p) -//{ -// if (!part.cache.gridptr || get_voxel_scale(*part.cache.gridptr) < p.voxel_scale()) { -// part.cache.gridptr = mesh_to_grid(*csg::get_mesh(part), p.voxel_scale(), -// p.exterior_bandwidth(), -// p.interior_bandwidth()); +inline bool operator==(const VoxelizeParams &a, const VoxelizeParams &b) +{ + std::hash h; + return h(a) == h(b); +} -// } /*else { -// float vscale = p.voxel_scale(); -// float oscale = get_voxel_scale(*part.cache.gridptr); +inline VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, + const VoxelizeParams &p) +{ + VoxelGridPtr &ret = part.gridcache[p]; -// rescale_grid(*part.cache.gridptr, oscale/vscale); -// }*/ + if (!ret) { + ret = mesh_to_grid(*csg::get_mesh(part), + csg::get_transform(part), + p.voxel_scale(), + p.exterior_bandwidth(), + p.interior_bandwidth()); + } -// return clone(*part.cache.gridptr); -//} + return clone(*ret); +} } // namespace csg @@ -134,9 +157,7 @@ public: // Get the mesh that is going to be printed with all the modifications // like hollowing and drilled holes. - const TriangleMesh & get_mesh_to_print() const { - return !m_mesh_from_slices.empty() ? m_mesh_from_slices : m_transformed_rmesh; - } + const TriangleMesh & get_mesh_to_print() const; // // This will return the transformed mesh which is cached // const TriangleMesh& transformed_mesh() const; @@ -379,9 +400,16 @@ private: }; std::unique_ptr m_supportdata; - TriangleMesh m_mesh_from_slices; + + // Holds CSG operations for the printed object, prioritized by print steps. std::multiset m_mesh_to_slice; + // Holds the preview of the object to be printed (as it will look like with + // all its holes and cavities, negatives and positive volumes unified. + // Essentially this should be a m_mesh_to_slice after the CSG operations + // or an approximation of that. + std::array m_preview_meshes; + class HollowingData { public: diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index a024a799c..b0efa31fa 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include @@ -128,6 +128,54 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin } } + +void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) +{ + Benchmark bench; + + bench.start(); + // update preview mesh + double vscale = 1. / (2. * po.m_config.layer_height.getFloat()); + auto voxparams = csg::VoxelizeParams{} + .voxel_scale(vscale) + .exterior_bandwidth(1.f) + .interior_bandwidth(1.f); + auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); + + indexed_triangle_set m = grid_to_mesh(*grid); + +// if (!m.empty()) { +// // simplify mesh lossless + +// std::cout << "simplify started" << std::endl; +// int expected_cnt = m.indices.size() * 0.8; //std::pow(po.m_transformed_rmesh.volume() / std::pow(1./vscale, 3), 2./3.); +// std::cout << "expected triangles " << expected_cnt << std::endl; +// float err = std::pow(vscale, 3); +// its_quadric_edge_collapse(m, 0U, &err); +// std::cout << "simplify ended " << m.indices.size() << " triangles" << std::endl; + +// std::cout << "cleanup started" << std::endl; +// its_compactify_vertices(m); +// its_merge_vertices(m); +// std::cout << "cleanup ended" << std::endl; +// } + + po.m_preview_meshes[step] = TriangleMesh{std::move(m)}; + + for (size_t i = size_t(step) + 1; i < slaposCount; ++i) + { + po.m_preview_meshes[i] = {}; + } + + bench.stop(); + + std::cout << "Preview gen took: " << bench.getElapsedSec() << std::endl; + using namespace std::string_literals; + + report_status(-2, "Reload preview from step "s + std::to_string(int(step)), SlicingStatus::RELOAD_SLA_PREVIEW); +} + +static inline void clear_csg(std::multiset &s, SLAPrintObjectStep step) { auto r = s.equal_range(step); @@ -153,6 +201,8 @@ void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po) csg::model_to_csgmesh(*po.model_object(), po.trafo(), csg_inserter{po.m_mesh_to_slice, slaposAssembly}, csg::mpartsPositive | csg::mpartsNegative); + + generate_preview(po, slaposAssembly); } void SLAPrint::Steps::hollow_model(SLAPrintObject &po) @@ -185,11 +235,24 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); po.m_hollowing_data->interior = std::move(interior); + indexed_triangle_set &m = sla::get_mesh(*po.m_hollowing_data->interior); + + if (!m.empty()) { + // simplify mesh lossless + float loss_less_max_error = 2*std::numeric_limits::epsilon(); + its_quadric_edge_collapse(m, 0U, &loss_less_max_error); + + its_compactify_vertices(m); + its_merge_vertices(m); + } + // Put the interior into the target mesh as a negative po.m_mesh_to_slice .emplace(slaposHollowing, csg::CSGPart{&sla::get_mesh(*po.m_hollowing_data->interior), csg::CSGType::Difference}); + + generate_preview(po, slaposHollowing); } } @@ -203,14 +266,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) csg::mpartsDrillHoles); // update preview mesh - double vscale = 1. / 0.05; - auto voxparams = csg::VoxelizeParams{} - .voxel_scale(vscale) - .exterior_bandwidth(1.f) - .interior_bandwidth(1.f); - auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); - indexed_triangle_set mesh_from_slices = grid_to_mesh(*grid); - po.m_mesh_from_slices = TriangleMesh{mesh_from_slices}; + generate_preview(po, slaposDrillHoles); } template @@ -331,38 +387,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) // We apply the printer correction offset here. apply_printer_corrections(po, soModel); -// auto simpl_slices = reserve_vector(po.m_model_slices.size()); -// for (const ExPolygons &slice : po.m_model_slices) { -// simpl_slices.emplace_back(expolygons_simplify(slice, scaled(1e-2))); -// } - -// po.m_mesh_from_slices = TriangleMesh{ -// slices_to_mesh(simpl_slices, slice_grid.front(), lhd, ilhd)}; - - double vscale = 1. / lhd; - auto voxparams = csg::VoxelizeParams{} - .voxel_scale(vscale) - .exterior_bandwidth(1.f) - .interior_bandwidth(1.f); - auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); - - assert(grid); - -// size_t max_face_cnt = 0; -// for (const CSGMesh &part : po.m_mesh_to_slice) -// max_face_cnt += part.its_ptr->indices.size(); - - indexed_triangle_set mesh_from_slices = grid_to_mesh(*grid); - -// its_quadric_edge_collapse(mesh_from_slices, vscale * max_face_cnt); - -// its_compactify_vertices(mesh_from_slices); -// its_merge_vertices(mesh_from_slices); - - po.m_mesh_from_slices = TriangleMesh{mesh_from_slices}; - -// po.m_mesh_from_slices = TriangleMesh{sla::get_mesh(*po.m_hollowing_data->interior)}; - + generate_preview(po, slaposObjectSlice); } static void filter_support_points_by_modifiers( @@ -421,7 +446,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) if(!po.m_config.supports_enable.getBool()) return; if (!po.m_supportdata) - po.m_supportdata.reset(new SLAPrintObject::SupportData(po.m_mesh_from_slices)); + po.m_supportdata = + std::make_unique( + po.m_preview_meshes[slaposObjectSlice] + ); po.m_supportdata->input.zoffset = csgmesh_positive_bb(po.m_mesh_to_slice) .min.z(); diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index 1a1900153..30dff8628 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -44,6 +44,8 @@ private: void initialize_printer_input(); void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o); + + void generate_preview(SLAPrintObject &po, SLAPrintObjectStep step); public: explicit Steps(SLAPrint *print); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index e65a321f1..103ab339c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -852,73 +852,73 @@ int GLVolumeCollection::load_object_volume( return int(this->volumes.size() - 1); } -// Load SLA auxiliary GLVolumes (for support trees or pad). -// This function produces volumes for multiple instances in a single shot, -// as some object specific mesh conversions may be expensive. -#if ENABLE_LEGACY_OPENGL_REMOVAL -void GLVolumeCollection::load_object_auxiliary( - const SLAPrintObject* print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp) -#else -void GLVolumeCollection::load_object_auxiliary( - const SLAPrintObject *print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp, - bool opengl_initialized) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - assert(print_object->is_step_done(milestone)); - Transform3d mesh_trafo_inv = print_object->trafo().inverse(); - // Get the support mesh. - TriangleMesh mesh = print_object->get_mesh(milestone); - mesh.transform(mesh_trafo_inv); - // Convex hull is required for out of print bed detection. - TriangleMesh convex_hull = mesh.convex_hull_3d(); - for (const std::pair& instance_idx : instances) { - const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; - this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); - GLVolume& v = *this->volumes.back(); -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_SMOOTH_NORMALS - v.model.init_from(mesh, true); -#else - v.model.init_from(mesh); - v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR); -#if ENABLE_RAYCAST_PICKING - v.mesh_raycaster = std::make_unique(std::make_shared(mesh)); -#endif // ENABLE_RAYCAST_PICKING -#endif // ENABLE_SMOOTH_NORMALS -#else -#if ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.load_mesh(mesh, true); -#else - v.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.finalize_geometry(opengl_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); - v.geometry_id = std::pair(timestamp, model_instance.id().id); - // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. - if (&instance_idx == &instances.back()) - v.set_convex_hull(std::move(convex_hull)); - else - v.set_convex_hull(convex_hull); - v.is_modifier = false; - v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); - v.set_instance_transformation(model_instance.get_transformation()); - // Leave the volume transformation at identity. - // v.set_volume_transformation(model_volume->get_transformation()); - } -} +//// Load SLA auxiliary GLVolumes (for support trees or pad). +//// This function produces volumes for multiple instances in a single shot, +//// as some object specific mesh conversions may be expensive. +//#if ENABLE_LEGACY_OPENGL_REMOVAL +//void GLVolumeCollection::load_object_auxiliary( +// const SLAPrintObject* print_object, +// int obj_idx, +// // pairs of +// const std::vector>& instances, +// SLAPrintObjectStep milestone, +// // Timestamp of the last change of the milestone +// size_t timestamp) +//#else +//void GLVolumeCollection::load_object_auxiliary( +// const SLAPrintObject *print_object, +// int obj_idx, +// // pairs of +// const std::vector>& instances, +// SLAPrintObjectStep milestone, +// // Timestamp of the last change of the milestone +// size_t timestamp, +// bool opengl_initialized) +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +//{ +// assert(print_object->is_step_done(milestone)); +// Transform3d mesh_trafo_inv = print_object->trafo().inverse(); +// // Get the support mesh. +// TriangleMesh mesh = print_object->get_mesh(milestone); +// mesh.transform(mesh_trafo_inv); +// // Convex hull is required for out of print bed detection. +// TriangleMesh convex_hull = mesh.convex_hull_3d(); +// for (const std::pair& instance_idx : instances) { +// const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; +// this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); +// GLVolume& v = *this->volumes.back(); +//#if ENABLE_LEGACY_OPENGL_REMOVAL +//#if ENABLE_SMOOTH_NORMALS +// v.model.init_from(mesh, true); +//#else +// v.model.init_from(mesh); +// v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR); +//#if ENABLE_RAYCAST_PICKING +// v.mesh_raycaster = std::make_unique(std::make_shared(mesh)); +//#endif // ENABLE_RAYCAST_PICKING +//#endif // ENABLE_SMOOTH_NORMALS +//#else +//#if ENABLE_SMOOTH_NORMALS +// v.indexed_vertex_array.load_mesh(mesh, true); +//#else +// v.indexed_vertex_array.load_mesh(mesh); +//#endif // ENABLE_SMOOTH_NORMALS +// v.indexed_vertex_array.finalize_geometry(opengl_initialized); +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +// v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); +// v.geometry_id = std::pair(timestamp, model_instance.id().id); +// // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. +// if (&instance_idx == &instances.back()) +// v.set_convex_hull(std::move(convex_hull)); +// else +// v.set_convex_hull(convex_hull); +// v.is_modifier = false; +// v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); +// v.set_instance_transformation(model_instance.get_transformation()); +// // Leave the volume transformation at identity. +// // v.set_volume_transformation(model_volume->get_transformation()); +// } +//} #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_OPENGL_ES diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 1e8897c4e..e63f095e4 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -659,14 +659,14 @@ public: int instance_idx); // Load SLA auxiliary GLVolumes (for support trees or pad). - void load_object_auxiliary( - const SLAPrintObject* print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp); +// void load_object_auxiliary( +// const SLAPrintObject* print_object, +// int obj_idx, +// // pairs of +// const std::vector>& instances, +// SLAPrintObjectStep milestone, +// // Timestamp of the last change of the milestone +// size_t timestamp); #if ENABLE_OPENGL_ES int load_wipe_tower_preview( diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8c98c603d..d20e53643 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2189,143 +2189,146 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } } if (printer_technology == ptSLA) { - size_t idx = 0; - const SLAPrint *sla_print = this->sla_print(); - std::vector shift_zs(m_model->objects.size(), 0); - double relative_correction_z = sla_print->relative_correction().z(); - if (relative_correction_z <= EPSILON) - relative_correction_z = 1.; - for (const SLAPrintObject *print_object : sla_print->objects()) { - SLASupportState &state = sla_support_state[idx ++]; - const ModelObject *model_object = print_object->model_object(); - // Find an index of the ModelObject - int object_idx; - // There may be new SLA volumes added to the scene for this print_object. - // Find the object index of this print_object in the Model::objects list. - auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); - assert(it != sla_print->model().objects.end()); - object_idx = it - sla_print->model().objects.begin(); - // Cache the Z offset to be applied to all volumes with this object_idx. - shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; - // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. - // pairs of - std::vector> instances[std::tuple_size::value]; - for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { - const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; - // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. - auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), - [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); - assert(it != model_object->instances.end()); - int instance_idx = it - model_object->instances.begin(); - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) - if (sla_steps[istep] == slaposDrillHoles) { - // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance, - // not into its own GLVolume. - // There shall always be such a GLVolume allocated. - ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); - assert(!it->new_geometry()); - GLVolume &volume = *m_volumes.volumes[it->volume_idx]; - if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { - // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.reset(); -#else - volume.indexed_vertex_array.release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (state.step[istep].state == PrintStateBase::DONE) { - TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); - assert(! mesh.empty()); +// size_t idx = 0; +// const SLAPrint *sla_print = this->sla_print(); +// std::vector shift_zs(m_model->objects.size(), 0); +// double relative_correction_z = sla_print->relative_correction().z(); +// if (relative_correction_z <= EPSILON) +// relative_correction_z = 1.; +// for (const SLAPrintObject *print_object : sla_print->objects()) { +// SLASupportState &state = sla_support_state[idx ++]; +// const ModelObject *model_object = print_object->model_object(); +// // Find an index of the ModelObject +// int object_idx; +// // There may be new SLA volumes added to the scene for this print_object. +// // Find the object index of this print_object in the Model::objects list. +// auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); +// assert(it != sla_print->model().objects.end()); +// object_idx = it - sla_print->model().objects.begin(); +// // Cache the Z offset to be applied to all volumes with this object_idx. +// shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; +// // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. +// // pairs of +// std::vector> instances[std::tuple_size::value]; +// for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { +// const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; +// // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. +// auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), +// [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); +// assert(it != model_object->instances.end()); +// int instance_idx = it - model_object->instances.begin(); +// for (size_t istep = 0; istep < sla_steps.size(); ++ istep) +// if (sla_steps[istep] == slaposDrillHoles) { +// // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance, +// // not into its own GLVolume. +// // There shall always be such a GLVolume allocated. +// ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id); +// auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); +// assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); +// assert(!it->new_geometry()); +// GLVolume &volume = *m_volumes.volumes[it->volume_idx]; +// if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { +// // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. +//#if ENABLE_LEGACY_OPENGL_REMOVAL +// volume.model.reset(); +//#else +// volume.indexed_vertex_array.release_geometry(); +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +// if (state.step[istep].state == PrintStateBase::DONE) { +// TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); +// assert(! mesh.empty()); - // sla_trafo does not contain volume trafo. To get a mesh to create - // a new volume from, we have to apply vol trafo inverse separately. - const ModelObject& mo = *m_model->objects[volume.object_idx()]; - Transform3d trafo = sla_print->sla_trafo(mo) - * mo.volumes.front()->get_transformation().get_matrix(); - mesh.transform(trafo.inverse()); -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(mesh, true); -#else - volume.indexed_vertex_array.load_mesh(mesh, true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(mesh); -#if ENABLE_RAYCAST_PICKING - volume.mesh_raycaster = std::make_unique(std::make_shared(mesh)); -#endif // ENABLE_RAYCAST_PICKING -#else - volume.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS - } - else { - // Reload the original volume. -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_RAYCAST_PICKING - const TriangleMesh& new_mesh = m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(); - volume.model.init_from(new_mesh); - volume.mesh_raycaster = std::make_unique(std::make_shared(new_mesh)); -#else - volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -#endif // ENABLE_RAYCAST_PICKING -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS - } -#if !ENABLE_LEGACY_OPENGL_REMOVAL - volume.finalize_geometry(true); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable - // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables - // of various concenrs (model vs. 3D print path). - volume.offsets = { state.step[istep].timestamp }; - } - else if (state.step[istep].state == PrintStateBase::DONE) { - // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. - ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) { - // This can be an SLA support structure that should not be rendered (in case someone used undo - // to revert to before it was generated). If that's the case, we should not generate anything. - if (model_object->sla_points_status != sla::PointsStatus::NoPoints) - instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); - else - shift_zs[object_idx] = 0.; - } - else { - // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); - m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); - } - } +// // sla_trafo does not contain volume trafo. To get a mesh to create +// // a new volume from, we have to apply vol trafo inverse separately. +// const ModelObject& mo = *m_model->objects[volume.object_idx()]; +// Transform3d trafo = sla_print->sla_trafo(mo) +// * mo.volumes.front()->get_transformation().get_matrix(); +// mesh.transform(trafo.inverse()); +//#if ENABLE_SMOOTH_NORMALS +//#if ENABLE_LEGACY_OPENGL_REMOVAL +// volume.model.init_from(mesh, true); +//#else +// volume.indexed_vertex_array.load_mesh(mesh, true); +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +//#else +//#if ENABLE_LEGACY_OPENGL_REMOVAL +// volume.model.init_from(mesh); +//#if ENABLE_RAYCAST_PICKING +// volume.mesh_raycaster = std::make_unique(std::make_shared(mesh)); +//#endif // ENABLE_RAYCAST_PICKING +//#else +// volume.indexed_vertex_array.load_mesh(mesh); +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +//#endif // ENABLE_SMOOTH_NORMALS +// } +// else { +// // Reload the original volume. +//#if ENABLE_SMOOTH_NORMALS +//#if ENABLE_LEGACY_OPENGL_REMOVAL +// volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +//#else +// volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +//#else +//#if ENABLE_LEGACY_OPENGL_REMOVAL +//#if ENABLE_RAYCAST_PICKING +// const TriangleMesh& new_mesh = m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(); +// volume.model.init_from(new_mesh); +// volume.mesh_raycaster = std::make_unique(std::make_shared(new_mesh)); +//#else +// volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +//#endif // ENABLE_RAYCAST_PICKING +//#else +// volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +//#endif // ENABLE_SMOOTH_NORMALS +// } +//#if !ENABLE_LEGACY_OPENGL_REMOVAL +// volume.finalize_geometry(true); +//#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +// } +// //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable +// // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables +// // of various concenrs (model vs. 3D print path). +// volume.offsets = { state.step[istep].timestamp }; +// } +// else if (state.step[istep].state == PrintStateBase::DONE) { +// // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. +// ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); +// auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); +// assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); +// if (it->new_geometry()) { +// // This can be an SLA support structure that should not be rendered (in case someone used undo +// // to revert to before it was generated). If that's the case, we should not generate anything. +// if (model_object->sla_points_status != sla::PointsStatus::NoPoints) +// instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); +// else +// shift_zs[object_idx] = 0.; +// } +// else { +// // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. +// m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); +// m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); +// } +// } +// } + +// for (size_t istep = 0; istep < sla_steps.size(); ++istep) +// if (!instances[istep].empty()) +//#if ENABLE_LEGACY_OPENGL_REMOVAL +// m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); +//#else +// m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); +//#endif // ENABLE_LEGACY_OPENGL_REMOVAL +// } + + // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed + for (GLVolume* volume : m_volumes.volumes) + if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) { + const SLAPrintObject *po = sla_print()->objects()[volume->object_idx()]; + float zoffs = po->get_current_elevation() / sla_print()->relative_correction().z(); + volume->set_sla_shift_z(zoffs); } - - for (size_t istep = 0; istep < sla_steps.size(); ++istep) - if (!instances[istep].empty()) -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); -#else - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed - for (GLVolume* volume : m_volumes.volumes) - if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) - volume->set_sla_shift_z(shift_zs[volume->object_idx()]); } if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) { @@ -7635,7 +7638,7 @@ void GLCanvas3D::_load_sla_shells() // adds objects' volumes for (const SLAPrintObject* obj : print->objects()) - if (obj->is_step_done(slaposSliceSupports)) { + /*if (obj->is_step_done(slaposSliceSupports))*/ { unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); for (const SLAPrintObject::Instance& instance : obj->instances()) { add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 394e879b7..52ae9b25b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -281,10 +281,10 @@ void HollowedMesh::on_update() // If there is a valid SLAPrintObject, check state of Hollowing step. if (print_object) { - if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { + if (print_object->is_step_done(slaposDrillHoles) && !print_object->get_mesh_to_print().empty()) { size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); + const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); if (! backend_mesh.empty()) { m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); @@ -292,10 +292,10 @@ void HollowedMesh::on_update() m_drainholes = print_object->model_object()->sla_drain_holes; m_old_hollowing_timestamp = timestamp; - indexed_triangle_set interior = print_object->hollowed_interior_mesh(); - its_flip_triangles(interior); - m_hollowed_interior_transformed = std::make_unique(std::move(interior)); - m_hollowed_interior_transformed->transform(trafo_inv); +// indexed_triangle_set interior = print_object->hollowed_interior_mesh(); +// its_flip_triangles(interior); +// m_hollowed_interior_transformed = std::make_unique(std::move(interior)); +// m_hollowed_interior_transformed->transform(trafo_inv); } else { m_hollowed_mesh_transformed.reset(nullptr); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 5764b283c..b0f06541c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -944,7 +944,7 @@ bool MainFrame::can_export_supports() const const PrintObjects& objects = m_plater->sla_print().objects(); for (const SLAPrintObject* object : objects) { - if (object->has_mesh(slaposPad) || object->has_mesh(slaposSupportTree)) + if (!object->support_mesh().empty()) { can_export = true; break; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e11e08d04..d4b279f60 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6155,19 +6155,13 @@ void Plater::export_stl_obj(bool extended, bool selection_only) const Transform3d mesh_trafo_inv = object->trafo().inverse(); const bool is_left_handed = object->is_left_handed(); - TriangleMesh pad_mesh; - const bool has_pad_mesh = extended && object->has_mesh(slaposPad); - if (has_pad_mesh) { - pad_mesh = object->get_mesh(slaposPad); - pad_mesh.transform(mesh_trafo_inv); - } + auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{}; + pad_mesh = object->pad_mesh(); + pad_mesh.transform(mesh_trafo_inv); + + auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{}; + supports_mesh.transform(mesh_trafo_inv); - TriangleMesh supports_mesh; - const bool has_supports_mesh = extended && object->has_mesh(slaposSupportTree); - if (has_supports_mesh) { - supports_mesh = object->get_mesh(slaposSupportTree); - supports_mesh.transform(mesh_trafo_inv); - } const std::vector& obj_instances = object->instances(); for (const SLAPrintObject::Instance& obj_instance : obj_instances) { auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), @@ -6184,19 +6178,19 @@ void Plater::export_stl_obj(bool extended, bool selection_only) TriangleMesh inst_mesh; - if (has_pad_mesh) { + if (!pad_mesh.empty()) { TriangleMesh inst_pad_mesh = pad_mesh; inst_pad_mesh.transform(inst_transform, is_left_handed); inst_mesh.merge(inst_pad_mesh); } - if (has_supports_mesh) { + if (!supports_mesh.empty()) { TriangleMesh inst_supports_mesh = supports_mesh; inst_supports_mesh.transform(inst_transform, is_left_handed); inst_mesh.merge(inst_supports_mesh); } - TriangleMesh inst_object_mesh = object->get_mesh_to_slice(); + TriangleMesh inst_object_mesh = object->get_mesh_to_print(); inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); From 9bc34104743f5b158a6c0f98a2413753836387dc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 20 Oct 2022 14:14:25 +0200 Subject: [PATCH 007/206] Re-enable volumes in SLA, use raycasters from canvas in supports gizmo Got rid of HollowedMesh and Raycaster usage from GizmosCommon pool to prevent crashes --- src/libslic3r/AABBMesh.hpp | 19 +- src/slic3r/GUI/3DScene.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 5 +- src/slic3r/GUI/GLCanvas3D.hpp | 2 + src/slic3r/GUI/GUI_Factories.cpp | 70 ++++++-- src/slic3r/GUI/GUI_Factories.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 38 ++-- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 42 +++-- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 177 ++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 50 +++--- src/slic3r/GUI/Plater.cpp | 11 -- src/slic3r/GUI/SceneRaycaster.cpp | 18 +- src/slic3r/GUI/SceneRaycaster.hpp | 3 +- src/slic3r/GUI/Selection.cpp | 32 ++-- src/slic3r/GUI/Tab.cpp | 4 +- 16 files changed, 269 insertions(+), 207 deletions(-) diff --git a/src/libslic3r/AABBMesh.hpp b/src/libslic3r/AABBMesh.hpp index 179bf8c4c..337935839 100644 --- a/src/libslic3r/AABBMesh.hpp +++ b/src/libslic3r/AABBMesh.hpp @@ -26,10 +26,9 @@ class TriangleMesh; // casting and other higher level operations. class AABBMesh { class AABBImpl; - + const indexed_triangle_set* m_tm; -// double m_ground_level = 0/*, m_gnd_offset = 0*/; - + std::unique_ptr m_aabb; VertexFaceIndex m_vfidx; // vertex-face index std::vector m_fnidx; // face-neighbor index @@ -43,7 +42,7 @@ class AABBMesh { template void init(const M &mesh, bool calculate_epsilon); public: - + // calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length. // If set to false, a default epsilon is used, which works for "reasonable" meshes. explicit AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false); @@ -51,21 +50,17 @@ public: AABBMesh(const AABBMesh& other); AABBMesh& operator=(const AABBMesh&); - + AABBMesh(AABBMesh &&other); AABBMesh& operator=(AABBMesh &&other); - + ~AABBMesh(); - -// inline double ground_level() const { return m_ground_level /*+ m_gnd_offset*/; } -// inline void ground_level_offset(double o) { m_gnd_offset = o; } -// inline double ground_level_offset() const { return m_gnd_offset; } - + const std::vector& vertices() const; const std::vector& indices() const; const Vec3f& vertices(size_t idx) const; const Vec3i& indices(size_t idx) const; - + // Result of a raycast class hit_result { // m_t holds a distance from m_source to the intersection. diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index e63f095e4..abf3e9264 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -386,7 +386,7 @@ public: bool force_neutral_color : 1; // Whether or not to force rendering of sinking contours bool force_sinking_contours : 1; - }; + }; // this gets instantiated automatically in the parent struct // Is mouse or rectangle selection over this object to select/deselect it ? EHoverState hover; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d20e53643..a698633a8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2188,6 +2188,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } } } + if (printer_technology == ptSLA) { // size_t idx = 0; // const SLAPrint *sla_print = this->sla_print(); @@ -3846,7 +3847,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) #else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); #endif // ENABLE_WORLD_COORDINATE - else if (selection_mode == Selection::Volume) + else if (volume_idx >= 0 && selection_mode == Selection::Volume) #if ENABLE_WORLD_COORDINATE model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else @@ -4020,7 +4021,7 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); #endif // ENABLE_WORLD_COORDINATE } - else if (selection_mode == Selection::Volume) { + else if (selection_mode == Selection::Volume && volume_idx >= 0) { #if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a519a26a1..140cbaf74 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -688,6 +688,8 @@ public: void set_raycaster_gizmos_on_top(bool value) { m_scene_raycaster.set_gizmos_on_top(value); } + + const SceneRaycaster & raycaster() const { return m_scene_raycaster; } #endif // ENABLE_RAYCAST_PICKING void set_as_dirty(); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 0ae1b8870..39cf44eb4 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -44,7 +44,6 @@ static bool is_improper_category(const std::string& category, const int extruder (!is_object_settings && category == "Support material"); } - //------------------------------------- // SettingsFactory //------------------------------------- @@ -155,14 +154,14 @@ wxBitmapBundle* SettingsFactory::get_category_bitmap(const std::string& category //------------------------------------- // Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important -const std::vector> MenuFactory::ADD_VOLUME_MENU_ITEMS { -// menu_item Name menu_item bitmap name - {L("Add part"), "add_part" }, // ~ModelVolumeType::MODEL_PART - {L("Add negative volume"), "add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME - {L("Add modifier"), "add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER - {L("Add support blocker"), "support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER - {L("Add support enforcer"), "support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER -}; +static const constexpr std::array, 5> ADD_VOLUME_MENU_ITEMS = {{ + // menu_item Name menu_item bitmap name + {L("Add part"), "add_part" }, // ~ModelVolumeType::MODEL_PART + {L("Add negative volume"), "add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME + {L("Add modifier"), "add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER + {L("Add support blocker"), "support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER + {L("Add support enforcer"), "support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER +}}; // Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important const std::vector> MenuFactory::TEXT_VOLUME_ICONS { @@ -583,6 +582,55 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu) append_menu_item_layers_editing(menu); } +void MenuFactory::append_menu_items_add_sla_volume(wxMenu *menu) +{ + // Update "add" items(delete old & create new) settings popupmenu + for (auto& item : ADD_VOLUME_MENU_ITEMS) { + const auto settings_id = menu->FindItem(_(item.first)); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } + + const ConfigOptionMode mode = wxGetApp().get_mode(); + + if (mode == comAdvanced) { + append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", + [](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); }, + ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } else { + auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)]; + + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::MODEL_PART); + append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } + + { + auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::NEGATIVE_VOLUME)]; + + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::NEGATIVE_VOLUME); + append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } + + { + auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)]; + + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::SUPPORT_ENFORCER); + append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } + + { + auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)]; + + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::SUPPORT_BLOCKER); + append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } +} + wxMenuItem* MenuFactory::append_menu_item_layers_editing(wxMenu* menu) { return append_menu_item(menu, wxID_ANY, _L("Height range Modifier"), "", @@ -631,7 +679,7 @@ wxMenuItem* MenuFactory::append_menu_item_settings(wxMenu* menu_) // If there are selected more then one instance but not all of them // don't add settings menu items const Selection& selection = get_selection(); - if ((selection.is_multiple_full_instance() && !selection.is_single_full_object()) || + if ((selection.is_multiple_full_instance() && !selection.is_single_full_object()) || (printer_technology() == ptSLA && selection.is_single_volume()) || selection.is_multiple_volume() || selection.is_mixed()) // more than one volume(part) is selected on the scene return nullptr; @@ -1066,6 +1114,8 @@ void MenuFactory::create_sla_object_menu() []() { return plater()->can_split(true); }, m_parent); m_sla_object_menu.AppendSeparator(); + append_menu_items_add_sla_volume(&m_sla_object_menu); + m_sla_object_menu.AppendSeparator(); } void MenuFactory::append_immutable_part_menu_items(wxMenu* menu) diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index ed27655be..e418cf675 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -92,6 +92,7 @@ private: wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); void append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item = true); void append_menu_items_add_volume(wxMenu* menu); + void append_menu_items_add_sla_volume(wxMenu* menu); wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 1ce118d71..52aed6bfc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -53,8 +53,8 @@ void GLGizmoHollow::data_changed() reload_cache(); m_old_mo_id = mo->id(); } - if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) - m_holes_in_drilled_mesh = mo->sla_drain_holes; +// if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) +// m_holes_in_drilled_mesh = mo->sla_drain_holes; #if ENABLE_RAYCAST_PICKING if (m_raycasters.empty()) on_register_raycasters_for_picking(); @@ -206,11 +206,11 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) #endif // !ENABLE_RAYCAST_PICKING if (size_t(m_hover_id) == i) render_color = ColorRGBA::CYAN(); - else if (m_c->hollowed_mesh() && - i < m_c->hollowed_mesh()->get_drainholes().size() && - m_c->hollowed_mesh()->get_drainholes()[i].failed) { - render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; - } +// else if (m_c->hollowed_mesh() && +// i < m_c->hollowed_mesh()->get_drainholes().size() && +// m_c->hollowed_mesh()->get_drainholes()[i].failed) { +// render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; +// } else render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); #if !ENABLE_RAYCAST_PICKING @@ -314,18 +314,18 @@ bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pairhollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { - // in this case the raycaster sees the hollowed and drilled mesh. - // if the point lies on the surface created by the hole, we want - // to ignore it. - for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { - sla::DrainHole outer(hole); - outer.radius *= 1.001f; - outer.height *= 1.001f; - if (outer.is_inside(hit)) - return false; - } - } +// if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { +// // in this case the raycaster sees the hollowed and drilled mesh. +// // if the point lies on the surface created by the hole, we want +// // to ignore it. +// for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { +// sla::DrainHole outer(hole); +// outer.radius *= 1.001f; +// outer.height *= 1.001f; +// if (outer.is_inside(hit)) +// return false; +// } +// } // Return both the point and the facet normal. pos_and_normal = std::make_pair(hit, normal); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 1646f31c7..d0dd3baa0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -86,6 +86,8 @@ void GLGizmoSlaSupports::data_changed() update_raycasters_for_picking_transform(); #endif // ENABLE_RAYCAST_PICKING } + +// m_parent.toggle_model_objects_visibility(false); } @@ -179,8 +181,8 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); const bool has_points = (cache_size != 0); - const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() - && ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); + const bool has_holes = (/*! m_c->hollowed_mesh()->get_hollowed_mesh() + &&*/ ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); if (! has_points && ! has_holes) return; @@ -304,7 +306,8 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) if (m_editing_mode) { // in case the normal is not yet cached, find and cache it if (m_editing_cache[i].normal == Vec3f::Zero()) - m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); + m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); + //m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); @@ -453,7 +456,7 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const // Return false if no intersection was found, true otherwise. bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) { - if (! m_c->raycaster()->raycaster()) + if (m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->empty()) return false; const Camera& camera = wxGetApp().plater()->get_camera(); @@ -468,7 +471,7 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pairraycaster()->raycaster()->unproject_on_mesh( + if (m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->unproject_on_mesh( mouse_pos, trafo.get_matrix(), camera, @@ -477,24 +480,24 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pairhollowed_mesh()->get_hollowed_mesh()) { - sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - for (const sla::DrainHole& hole : drain_holes) { - if (hole.is_inside(hit)) { - in_hole = true; - break; - } - } - } - if (! in_hole) { +// if (! m_c->hollowed_mesh()->get_hollowed_mesh()) { +// sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; +// for (const sla::DrainHole& hole : drain_holes) { +// if (hole.is_inside(hit)) { +// in_hole = true; +// break; +// } +// } +// } +// if (! in_hole) { // Return both the point and the facet normal. pos_and_normal = std::make_pair(hit, normal); return true; - } +// } } return false; @@ -589,7 +592,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous for (size_t idx : points_idxs) points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast()); - for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( + assert(!m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume).emtpy()); + for (size_t idx : m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->get_unobscured_idxs( trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->object_clipper()->get_clipping_plane())) { @@ -1457,7 +1461,7 @@ void GLGizmoSlaSupports::update_raycasters_for_picking_transform() const Transform3d support_matrix = Geometry::translation_transform(m_editing_cache[i].support_point.pos.cast()) * instance_scaling_matrix_inverse; if (m_editing_cache[i].normal == Vec3f::Zero()) - m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); + m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 136276803..c4232ed48 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -25,7 +25,7 @@ private: bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); - const float RenderPointScale = 1.f; + static constexpr float RenderPointScale = 1.f; class CacheEntry { public: diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 52ae9b25b..31fbb0f7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -23,7 +23,7 @@ CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas) using c = CommonGizmosDataID; m_data[c::SelectionInfo].reset( new SelectionInfo(this)); m_data[c::InstancesHider].reset( new InstancesHider(this)); - m_data[c::HollowedMesh].reset( new HollowedMesh(this)); +// m_data[c::HollowedMesh].reset( new HollowedMesh(this)); m_data[c::Raycaster].reset( new Raycaster(this)); m_data[c::ObjectClipper].reset( new ObjectClipper(this)); m_data[c::SupportsClipper].reset( new SupportsClipper(this)); @@ -59,12 +59,12 @@ InstancesHider* CommonGizmosDataPool::instances_hider() const return inst_hider->is_valid() ? inst_hider : nullptr; } -HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const -{ - HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); - assert(hol_mesh); - return hol_mesh->is_valid() ? hol_mesh : nullptr; -} +//HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const +//{ +// HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); +// assert(hol_mesh); +// return hol_mesh->is_valid() ? hol_mesh : nullptr; +//} Raycaster* CommonGizmosDataPool::raycaster() const { @@ -117,16 +117,18 @@ bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const void SelectionInfo::on_update() { const Selection& selection = get_pool()->get_canvas()->get_selection(); + + m_model_object = nullptr; + m_print_object = nullptr; + if (selection.is_single_full_instance()) { m_model_object = selection.get_model()->objects[selection.get_object_idx()]; - m_model_volume = nullptr; + + if (m_model_object) + m_print_object = get_pool()->get_canvas()->sla_print()->get_object(m_model_object->id()); + m_z_shift = selection.get_first_volume()->get_sla_shift_z(); } - else { - m_model_object = nullptr; - if (selection.is_single_volume()) - m_model_volume = selection.get_model()->objects[selection.get_object_idx()]->volumes[selection.get_first_volume()->volume_idx()]; - } } void SelectionInfo::on_release() @@ -253,78 +255,78 @@ void InstancesHider::render_cut() const -void HollowedMesh::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; - if (! mo || ! is_sla) - return; +//void HollowedMesh::on_update() +//{ +// const ModelObject* mo = get_pool()->selection_info()->model_object(); +// bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; +// if (! mo || ! is_sla) +// return; - const GLCanvas3D* canvas = get_pool()->get_canvas(); - const PrintObjects& print_objects = canvas->sla_print()->objects(); - const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) - ? print_objects[m_print_object_idx] - : nullptr; +// const GLCanvas3D* canvas = get_pool()->get_canvas(); +// const PrintObjects& print_objects = canvas->sla_print()->objects(); +// const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) +// ? print_objects[m_print_object_idx] +// : nullptr; - // Find the respective SLAPrintObject. - if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { - m_print_objects_count = print_objects.size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : print_objects) { - ++m_print_object_idx; - if (po->model_object()->id() == mo->id()) { - print_object = po; - break; - } - } - } +// // Find the respective SLAPrintObject. +// if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { +// m_print_objects_count = print_objects.size(); +// m_print_object_idx = -1; +// for (const SLAPrintObject* po : print_objects) { +// ++m_print_object_idx; +// if (po->model_object()->id() == mo->id()) { +// print_object = po; +// break; +// } +// } +// } - // If there is a valid SLAPrintObject, check state of Hollowing step. - if (print_object) { - if (print_object->is_step_done(slaposDrillHoles) && !print_object->get_mesh_to_print().empty()) { - size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; - if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); - if (! backend_mesh.empty()) { - m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); - Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); - m_hollowed_mesh_transformed->transform(trafo_inv); - m_drainholes = print_object->model_object()->sla_drain_holes; - m_old_hollowing_timestamp = timestamp; +// // If there is a valid SLAPrintObject, check state of Hollowing step. +// if (print_object) { +// if (print_object->is_step_done(slaposDrillHoles) && !print_object->get_mesh_to_print().empty()) { +// size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; +// if (timestamp > m_old_hollowing_timestamp) { +// const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); +// if (! backend_mesh.empty()) { +// m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); +// Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); +// m_hollowed_mesh_transformed->transform(trafo_inv); +// m_drainholes = print_object->model_object()->sla_drain_holes; +// m_old_hollowing_timestamp = timestamp; -// indexed_triangle_set interior = print_object->hollowed_interior_mesh(); -// its_flip_triangles(interior); -// m_hollowed_interior_transformed = std::make_unique(std::move(interior)); -// m_hollowed_interior_transformed->transform(trafo_inv); - } - else { - m_hollowed_mesh_transformed.reset(nullptr); - } - } - } - else - m_hollowed_mesh_transformed.reset(nullptr); - } -} +//// indexed_triangle_set interior = print_object->hollowed_interior_mesh(); +//// its_flip_triangles(interior); +//// m_hollowed_interior_transformed = std::make_unique(std::move(interior)); +//// m_hollowed_interior_transformed->transform(trafo_inv); +// } +// else { +// m_hollowed_mesh_transformed.reset(nullptr); +// } +// } +// } +// else +// m_hollowed_mesh_transformed.reset(nullptr); +// } +//} -void HollowedMesh::on_release() -{ - m_hollowed_mesh_transformed.reset(); - m_old_hollowing_timestamp = 0; - m_print_object_idx = -1; -} +//void HollowedMesh::on_release() +//{ +// m_hollowed_mesh_transformed.reset(); +// m_old_hollowing_timestamp = 0; +// m_print_object_idx = -1; +//} -const TriangleMesh* HollowedMesh::get_hollowed_mesh() const -{ - return m_hollowed_mesh_transformed.get(); -} +//const TriangleMesh* HollowedMesh::get_hollowed_mesh() const +//{ +// return m_hollowed_mesh_transformed.get(); +//} -const TriangleMesh* HollowedMesh::get_hollowed_interior() const -{ - return m_hollowed_interior_transformed.get(); -} +//const TriangleMesh* HollowedMesh::get_hollowed_interior() const +//{ +// return m_hollowed_interior_transformed.get(); +//} @@ -345,12 +347,13 @@ void Raycaster::on_update() mvs = mo->volumes; std::vector meshes; - if (mvs.size() == 1) { - assert(mvs.front()->is_model_part()); - const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); - if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) - meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); - } + const std::vector& mvs = mo->volumes; +// if (mvs.size() == 1) { +// assert(mvs.front()->is_model_part()); +// const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); +// if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) +// meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); +// } if (meshes.empty()) { for (const ModelVolume* v : mvs) { if (v->is_model_part()) @@ -396,9 +399,9 @@ void ObjectClipper::on_update() // which mesh should be cut? std::vector meshes; - bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); - if (has_hollowed) - meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); +// bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); +// if (has_hollowed) +// meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); if (meshes.empty()) for (const ModelVolume* mv : mo->volumes) @@ -412,8 +415,8 @@ void ObjectClipper::on_update() } m_old_meshes = meshes; - if (has_hollowed) - m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); +// if (has_hollowed) +// m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 42faa25f8..d6c580123 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -5,11 +5,11 @@ #include #include "slic3r/GUI/MeshUtils.hpp" -#include "libslic3r/SLA/Hollowing.hpp" namespace Slic3r { class ModelObject; +class SLAPrintObject; class ModelVolume; namespace GUI { @@ -85,7 +85,7 @@ public: // Getters for the data that need to be accessed from the gizmos directly. CommonGizmosDataObjects::SelectionInfo* selection_info() const; CommonGizmosDataObjects::InstancesHider* instances_hider() const; - CommonGizmosDataObjects::HollowedMesh* hollowed_mesh() const; +// CommonGizmosDataObjects::HollowedMesh* hollowed_mesh() const; CommonGizmosDataObjects::Raycaster* raycaster() const; CommonGizmosDataObjects::ObjectClipper* object_clipper() const; CommonGizmosDataObjects::SupportsClipper* supports_clipper() const; @@ -158,6 +158,7 @@ public: // Returns a non-null pointer if the selection is a single full instance ModelObject* model_object() const { return m_model_object; } + const SLAPrintObject *print_object() const { return m_print_object; } // Returns a non-null pointer if the selection is a single volume ModelVolume* model_volume() const { return m_model_volume; } int get_active_instance() const; @@ -169,6 +170,7 @@ protected: private: ModelObject* m_model_object = nullptr; + const SLAPrintObject *m_print_object = nullptr; ModelVolume* m_model_volume = nullptr; // int m_active_inst = -1; float m_z_shift = 0.f; @@ -201,32 +203,32 @@ private: -class HollowedMesh : public CommonGizmosDataBase -{ -public: - explicit HollowedMesh(CommonGizmosDataPool* cgdp) - : CommonGizmosDataBase(cgdp) {} -#ifndef NDEBUG - CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } -#endif // NDEBUG +//class HollowedMesh : public CommonGizmosDataBase +//{ +//public: +// explicit HollowedMesh(CommonGizmosDataPool* cgdp) +// : CommonGizmosDataBase(cgdp) {} +//#ifndef NDEBUG +// CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } +//#endif // NDEBUG - const sla::DrainHoles &get_drainholes() const { return m_drainholes; } +// const sla::DrainHoles &get_drainholes() const { return m_drainholes; } - const TriangleMesh* get_hollowed_mesh() const; - const TriangleMesh* get_hollowed_interior() const; +// const TriangleMesh* get_hollowed_mesh() const; +// const TriangleMesh* get_hollowed_interior() const; -protected: - void on_update() override; - void on_release() override; +//protected: +// void on_update() override; +// void on_release() override; -private: - std::unique_ptr m_hollowed_mesh_transformed; - std::unique_ptr m_hollowed_interior_transformed; - size_t m_old_hollowing_timestamp = 0; - int m_print_object_idx = -1; - int m_print_objects_count = 0; - sla::DrainHoles m_drainholes; -}; +//private: +// std::unique_ptr m_hollowed_mesh_transformed; +// std::unique_ptr m_hollowed_interior_transformed; +// size_t m_old_hollowing_timestamp = 0; +// int m_print_object_idx = -1; +// int m_print_objects_count = 0; +// sla::DrainHoles m_drainholes; +//}; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d4b279f60..d57963e25 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2649,17 +2649,6 @@ std::vector Plater::priv::load_files(const std::vector& input_ model_object->ensure_on_bed(is_project_file); } - // check multi-part object adding for the SLA-printing - if (printer_technology == ptSLA) { - for (auto obj : model.objects) - if ( obj->volumes.size()>1 ) { - Slic3r::GUI::show_error(nullptr, - format_wxstr(_L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part"), - from_path(filename))); - return obj_idxs; - } - } - if (one_by_one) { if ((type_3mf && !is_project_file) || (type_any_amf && !type_zip_amf)) model.center_instances_around_point(this->bed.build_volume().bed_center()); diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp index a92c622c1..f309d6eaa 100644 --- a/src/slic3r/GUI/SceneRaycaster.cpp +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -88,7 +88,7 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr item) } } -SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) +SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const { double closest_hit_squared_distance = std::numeric_limits::max(); auto is_closest = [&closest_hit_squared_distance](const Camera& camera, const Vec3f& hit) { @@ -107,7 +107,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came auto test_raycasters = [this, is_closest, clipping_plane](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) { const ClippingPlane* clip_plane = (clipping_plane != nullptr && type == EType::Volume) ? clipping_plane : nullptr; - std::vector>* raycasters = get_raycasters(type); + const std::vector>* raycasters = get_raycasters(type); const Vec3f camera_forward = camera.get_dir_forward().cast(); HitResult current_hit = { type }; for (std::shared_ptr item : *raycasters) { @@ -214,6 +214,20 @@ std::vector>* SceneRaycaster::get_raycasters return ret; } +const std::vector>* SceneRaycaster::get_raycasters(EType type) const +{ + const std::vector>* ret = nullptr; + switch (type) + { + case EType::Bed: { ret = &m_bed; break; } + case EType::Volume: { ret = &m_volumes; break; } + case EType::Gizmo: { ret = &m_gizmos; break; } + default: { break; } + } + assert(ret != nullptr); + return ret; +} + int SceneRaycaster::base_id(EType type) { switch (type) diff --git a/src/slic3r/GUI/SceneRaycaster.hpp b/src/slic3r/GUI/SceneRaycaster.hpp index 2254a2022..bf8ab2b1b 100644 --- a/src/slic3r/GUI/SceneRaycaster.hpp +++ b/src/slic3r/GUI/SceneRaycaster.hpp @@ -89,10 +89,11 @@ public: void remove_raycaster(std::shared_ptr item); std::vector>* get_raycasters(EType type); + const std::vector>* get_raycasters(EType type) const; void set_gizmos_on_top(bool value) { m_gizmos_on_top = value; } - HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr); + HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr) const; #if ENABLE_RAYCAST_PICKING_DEBUG void render_hit(const Camera& camera); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b27f7a738..7caf2d9ea 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -62,18 +62,18 @@ Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_trans bool Selection::Clipboard::is_sla_compliant() const { - if (m_mode == Selection::Volume) - return false; +// if (m_mode == Selection::Volume) +// return false; - for (const ModelObject* o : m_model->objects) { - if (o->is_multiparts()) - return false; +// for (const ModelObject* o : m_model->objects) { +// if (o->is_multiparts()) +// return false; - for (const ModelVolume* v : o->volumes) { - if (v->is_modifier()) - return false; - } - } +// for (const ModelVolume* v : o->volumes) { +// if (v->is_modifier()) +// return false; +// } +// } return true; } @@ -571,13 +571,13 @@ bool Selection::is_from_single_object() const bool Selection::is_sla_compliant() const { - if (m_mode == Volume) - return false; +// if (m_mode == Volume) +// return false; - for (unsigned int i : m_list) { - if ((*m_volumes)[i]->is_modifier) - return false; - } +// for (unsigned int i : m_list) { +// if ((*m_volumes)[i]->is_modifier) +// return false; +// } return true; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d5b909f37..9515fa2b6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3279,9 +3279,9 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset); PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); - if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset selected"))) + /*if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset selected"))) canceled = true; - else { + else */{ struct PresetUpdate { Preset::Type tab_type; PresetCollection *presets; From 2a8c9d74626bafb181b5ed023b119345669558b4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 21 Oct 2022 16:59:00 +0200 Subject: [PATCH 008/206] Refinements --- src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 8 ++++++ src/libslic3r/SLAPrint.cpp | 37 +++++++++++++++++++------- src/libslic3r/SLAPrint.hpp | 24 ++--------------- src/libslic3r/SLAPrintSteps.cpp | 17 ++++++------ 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index 95035b5ef..32f790546 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -46,6 +46,14 @@ std::vector slice_csgmesh_ex( break; } } + + for (ExPolygons &slice : ret) { + auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){ + return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON); + }); + + slice.erase(it, slice.end()); + } } return ret; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index c3b90b192..b8000f384 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -792,13 +792,6 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) : Inherited(print, model_object) -// , m_transformed_rmesh([this](TriangleMesh &obj) { -//// obj = m_model_object->raw_mesh(); -//// if (!obj.empty()) { -//// obj.transform(m_trafo); -//// } -// obj = transformed_mesh_csg(*this); -// }) {} SLAPrintObject::~SLAPrintObject() {} @@ -1056,7 +1049,8 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const return EMPTY_MESH; } -const TriangleMesh &SLAPrintObject::get_mesh_to_print() const { +const TriangleMesh &SLAPrintObject::get_mesh_to_print() const +{ const TriangleMesh *ret = nullptr; int s = SLAPrintObjectStep::slaposCount; @@ -1068,7 +1062,7 @@ const TriangleMesh &SLAPrintObject::get_mesh_to_print() const { } if (!ret) - ret = &m_transformed_rmesh; + ret = &EMPTY_MESH; return *ret; } @@ -1158,4 +1152,29 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p, p.set_status(int(std::round(st)), msg, flags); } +namespace csg { + +inline bool operator==(const VoxelizeParams &a, const VoxelizeParams &b) +{ + std::hash h; + return h(a) == h(b); +} + +VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, const VoxelizeParams &p) +{ + VoxelGridPtr &ret = part.gridcache[p]; + + if (!ret) { + ret = mesh_to_grid(*csg::get_mesh(part), + csg::get_transform(part), + p.voxel_scale(), + p.exterior_bandwidth(), + p.interior_bandwidth()); + } + + return clone(*ret); +} + +} // namespace csg + } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 084c78024..9ca610087 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -91,27 +91,7 @@ struct CSGPartForStep : public csg::CSGPart namespace csg { -inline bool operator==(const VoxelizeParams &a, const VoxelizeParams &b) -{ - std::hash h; - return h(a) == h(b); -} - -inline VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, - const VoxelizeParams &p) -{ - VoxelGridPtr &ret = part.gridcache[p]; - - if (!ret) { - ret = mesh_to_grid(*csg::get_mesh(part), - csg::get_transform(part), - p.voxel_scale(), - p.exterior_bandwidth(), - p.interior_bandwidth()); - } - - return clone(*ret); -} +VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, const VoxelizeParams &p); } // namespace csg @@ -372,7 +352,7 @@ private: std::vector m_model_height_levels; // Caching the transformed (m_trafo) raw mesh of the object - TriangleMesh m_transformed_rmesh; +// TriangleMesh m_transformed_rmesh; struct SupportData { diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index b0efa31fa..52915a028 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -194,10 +194,6 @@ struct csg_inserter { void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po) { po.m_mesh_to_slice.clear(); - - po.m_transformed_rmesh = po.m_model_object->raw_mesh(); - po.m_transformed_rmesh.transform(po.trafo()); - csg::model_to_csgmesh(*po.model_object(), po.trafo(), csg_inserter{po.m_mesh_to_slice, slaposAssembly}, csg::mpartsPositive | csg::mpartsNegative); @@ -265,8 +261,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) csg_inserter{po.m_mesh_to_slice, slaposDrillHoles}, csg::mpartsDrillHoles); + auto r = po.m_mesh_to_slice.equal_range(slaposDrillHoles); + // update preview mesh - generate_preview(po, slaposDrillHoles); + if (r.first != r.second) + generate_preview(po, slaposDrillHoles); } template @@ -374,8 +373,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; - Range csgrange = {po.m_mesh_to_slice.begin(), po.m_mesh_to_slice.end()}; - po.m_model_slices = slice_csgmesh_ex(csgrange, slice_grid, params, thr); + po.m_model_slices = slice_csgmesh_ex(range(po.m_mesh_to_slice), slice_grid, params, thr); auto mit = slindex_it; for (size_t id = 0; @@ -387,7 +385,8 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) // We apply the printer correction offset here. apply_printer_corrections(po, soModel); - generate_preview(po, slaposObjectSlice); +// po.m_preview_meshes[slaposObjectSlice] = po.get_mesh_to_print(); +// report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } static void filter_support_points_by_modifiers( @@ -448,7 +447,7 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) if (!po.m_supportdata) po.m_supportdata = std::make_unique( - po.m_preview_meshes[slaposObjectSlice] + po.get_mesh_to_print() ); po.m_supportdata->input.zoffset = csgmesh_positive_bb(po.m_mesh_to_slice) From 191f04568d6c86359950a24c6f40fbc111d5ecf0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 21 Oct 2022 17:55:30 +0200 Subject: [PATCH 009/206] Do mesh booleans with cgal if possible. --- .../CSGMesh/PerformCSGMeshBooleans.hpp | 49 +++++++++++++++++-- src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 2 + src/libslic3r/MeshBoolean.hpp | 11 +++-- src/libslic3r/SLAPrintSteps.cpp | 44 +++++++++-------- src/libslic3r/SLAPrintSteps.hpp | 3 +- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index 5c64c65e0..da1ddce3f 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -5,15 +5,58 @@ #include "libslic3r/MeshBoolean.hpp" -namespace Slic3r { +namespace Slic3r { namespace csg { + +// This method can be overriden when a specific CSGPart type supports caching +// of the voxel grid +template +MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart) +{ + const indexed_triangle_set *its = csg::get_mesh(csgpart); + MeshBoolean::cgal::CGALMeshPtr ret; + + if (its) { + indexed_triangle_set m = *its; + its_transform(m, get_transform(csgpart)); + ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m); + } + + return ret; +} template void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMesh &cgalm, - const Range &csg) + const Range &csgparts) { - + for (auto &csgpart : csgparts) { + auto m = get_cgalmesh(csgpart); + if (m) { + switch (get_operation(csgpart)) { + case CSGType::Union: + MeshBoolean::cgal::plus(cgalm, *m); + break; + case CSGType::Difference: + MeshBoolean::cgal::minus(cgalm, *m); + break; + case CSGType::Intersection: + MeshBoolean::cgal::intersect(cgalm, *m); + break; + } + } + } } +template +MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range &csgparts) +{ + auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}); + if (ret) + perform_csgmesh_booleans(*ret, csgparts); + + return ret; +} + +} // namespace csg } // namespace Slic3r #endif // PERFORMCSGMESHBOOLEANS_HPP diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index 32f790546..f8a735420 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -52,6 +52,8 @@ std::vector slice_csgmesh_ex( return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON); }); + // Hopefully, ExPolygons are moved, not copied to new positions + // and that is cheap for expolygons slice.erase(it, slice.end()); } } diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 140c96931..441fc78c1 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -26,16 +26,17 @@ namespace cgal { struct CGALMesh; struct CGALMeshDeleter { void operator()(CGALMesh *ptr); }; +using CGALMeshPtr = std::unique_ptr; -std::unique_ptr -triangle_mesh_to_cgal(const std::vector &V, - const std::vector &F); +CGALMeshPtr triangle_mesh_to_cgal( + const std::vector &V, + const std::vector &F); -inline std::unique_ptr triangle_mesh_to_cgal(const indexed_triangle_set &M) +inline CGALMeshPtr triangle_mesh_to_cgal(const indexed_triangle_set &M) { return triangle_mesh_to_cgal(M.vertices, M.indices); } -inline std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) +inline CGALMeshPtr triangle_mesh_to_cgal(const TriangleMesh &M) { return triangle_mesh_to_cgal(M.its); } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 52915a028..a5873155c 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -18,11 +18,13 @@ #include #include #include +#include > +#include #include #include #include -#include +//#include #include @@ -128,48 +130,50 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin } } - -void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) +indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( + SLAPrintObject &po, SLAPrintObjectStep step) { Benchmark bench; bench.start(); + // update preview mesh double vscale = 1. / (2. * po.m_config.layer_height.getFloat()); auto voxparams = csg::VoxelizeParams{} .voxel_scale(vscale) .exterior_bandwidth(1.f) .interior_bandwidth(1.f); + auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); indexed_triangle_set m = grid_to_mesh(*grid); -// if (!m.empty()) { -// // simplify mesh lossless + bench.stop(); -// std::cout << "simplify started" << std::endl; -// int expected_cnt = m.indices.size() * 0.8; //std::pow(po.m_transformed_rmesh.volume() / std::pow(1./vscale, 3), 2./3.); -// std::cout << "expected triangles " << expected_cnt << std::endl; -// float err = std::pow(vscale, 3); -// its_quadric_edge_collapse(m, 0U, &err); -// std::cout << "simplify ended " << m.indices.size() << " triangles" << std::endl; + std::cout << "Preview gen took: " << bench.getElapsedSec() << std::endl; -// std::cout << "cleanup started" << std::endl; -// its_compactify_vertices(m); -// its_merge_vertices(m); -// std::cout << "cleanup ended" << std::endl; -// } + return m; +} - po.m_preview_meshes[step] = TriangleMesh{std::move(m)}; + +void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) +{ + MeshBoolean::cgal::CGALMeshPtr cgalptr; + + try { + cgalptr = csg::perform_csgmesh_booleans(range(po.m_mesh_to_slice)); + } catch(...) {} + + if (cgalptr) { + po.m_preview_meshes[step] = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalptr); + } else + po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; for (size_t i = size_t(step) + 1; i < slaposCount; ++i) { po.m_preview_meshes[i] = {}; } - bench.stop(); - - std::cout << "Preview gen took: " << bench.getElapsedSec() << std::endl; using namespace std::string_literals; report_status(-2, "Reload preview from step "s + std::to_string(int(step)), SlicingStatus::RELOAD_SLA_PREVIEW); diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index 30dff8628..3d63cbbad 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -46,7 +46,8 @@ private: void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o); void generate_preview(SLAPrintObject &po, SLAPrintObjectStep step); - + indexed_triangle_set generate_preview_vdb(SLAPrintObject &po, SLAPrintObjectStep step); + public: explicit Steps(SLAPrint *print); From ce5d242f76be4a94b074ba1db2da9eb8bbd4441d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Nov 2022 09:54:55 +0100 Subject: [PATCH 010/206] Fix failing test for hollowing --- src/libslic3r/SLA/Hollowing.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 3cc8ea340..974e52563 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -282,7 +282,11 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) if (flags & hfRemoveInsideTriangles && interior.gridptr) remove_inside_triangles(mesh, interior); - mesh.merge(TriangleMesh{interior.mesh}); + indexed_triangle_set interi = interior.mesh; + sla::swap_normals(interi); + TriangleMesh inter{std::move(interi)}; + + mesh.merge(inter); } // Get the distance of p to the interior's zero iso_surface. Interior should From f100a596883c12c81c0ff9af3c333a79a4f9fd84 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Nov 2022 12:42:02 +0100 Subject: [PATCH 011/206] Cherry-picking 118f4859c472ccbc30b43101c6674dadc81d7b12 And resolve conflicts --- resources/shaders/110/gouraud_light_clip.fs | 17 + resources/shaders/110/gouraud_light_clip.vs | 54 +++ resources/shaders/140/gouraud_light_clip.fs | 19 + resources/shaders/140/gouraud_light_clip.vs | 54 +++ resources/shaders/ES/gouraud_light_clip.fs | 19 + resources/shaders/ES/gouraud_light_clip.vs | 54 +++ src/libslic3r/SLAPrint.hpp | 5 + src/slic3r/GUI/3DScene.cpp | 68 ---- src/slic3r/GUI/3DScene.hpp | 21 -- src/slic3r/GUI/GLCanvas3D.cpp | 205 +---------- src/slic3r/GUI/GLCanvas3D.hpp | 2 - src/slic3r/GUI/GLShadersManager.cpp | 2 + src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 249 ++++++++----- src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp | 18 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 349 ++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 18 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 210 ++++------- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 41 +-- src/slic3r/GUI/SceneRaycaster.cpp | 4 +- 19 files changed, 674 insertions(+), 735 deletions(-) create mode 100644 resources/shaders/110/gouraud_light_clip.fs create mode 100644 resources/shaders/110/gouraud_light_clip.vs create mode 100644 resources/shaders/140/gouraud_light_clip.fs create mode 100644 resources/shaders/140/gouraud_light_clip.vs create mode 100644 resources/shaders/ES/gouraud_light_clip.fs create mode 100644 resources/shaders/ES/gouraud_light_clip.vs diff --git a/resources/shaders/110/gouraud_light_clip.fs b/resources/shaders/110/gouraud_light_clip.fs new file mode 100644 index 000000000..5c7068709 --- /dev/null +++ b/resources/shaders/110/gouraud_light_clip.fs @@ -0,0 +1,17 @@ +#version 110 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +varying vec2 intensity; + +varying float clipping_planes_dot; + +void main() +{ + if (clipping_planes_dot < 0.0) + discard; + + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/110/gouraud_light_clip.vs b/resources/shaders/110/gouraud_light_clip.vs new file mode 100644 index 000000000..6d7c32e1b --- /dev/null +++ b/resources/shaders/110/gouraud_light_clip.vs @@ -0,0 +1,54 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; + +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +attribute vec3 v_position; +attribute vec3 v_normal; + +// x = tainted, y = specular; +varying vec2 intensity; + +varying float clipping_planes_dot; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 eye_position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * eye_position; + + // Fill in the scalar for fragment shader clipping. Fragments with this value lower than zero are discarded. + clipping_planes_dot = dot(volume_world_matrix * vec4(v_position, 1.0), clipping_plane); +} diff --git a/resources/shaders/140/gouraud_light_clip.fs b/resources/shaders/140/gouraud_light_clip.fs new file mode 100644 index 000000000..714e5bcaa --- /dev/null +++ b/resources/shaders/140/gouraud_light_clip.fs @@ -0,0 +1,19 @@ +#version 140 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +in vec2 intensity; + +in float clipping_planes_dot; + +out vec4 out_color; + +void main() +{ + if (clipping_planes_dot < 0.0) + discard; + + out_color = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/140/gouraud_light_clip.vs b/resources/shaders/140/gouraud_light_clip.vs new file mode 100644 index 000000000..8fca59380 --- /dev/null +++ b/resources/shaders/140/gouraud_light_clip.vs @@ -0,0 +1,54 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; + +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +in vec3 v_position; +in vec3 v_normal; + +// x = tainted, y = specular; +out vec2 intensity; + +out float clipping_planes_dot; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 eye_position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * eye_position; + + // Fill in the scalar for fragment shader clipping. Fragments with this value lower than zero are discarded. + clipping_planes_dot = dot(volume_world_matrix * vec4(v_position, 1.0), clipping_plane); +} diff --git a/resources/shaders/ES/gouraud_light_clip.fs b/resources/shaders/ES/gouraud_light_clip.fs new file mode 100644 index 000000000..45cae0ddb --- /dev/null +++ b/resources/shaders/ES/gouraud_light_clip.fs @@ -0,0 +1,19 @@ +#version 100 + +precision highp float; + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +varying vec2 intensity; + +varying float clipping_planes_dot; + +void main() +{ + if (clipping_planes_dot < 0.0) + discard; + + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/ES/gouraud_light_clip.vs b/resources/shaders/ES/gouraud_light_clip.vs new file mode 100644 index 000000000..f3ab8c3dc --- /dev/null +++ b/resources/shaders/ES/gouraud_light_clip.vs @@ -0,0 +1,54 @@ +#version 100 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; + +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +attribute vec3 v_position; +attribute vec3 v_normal; + +// x = tainted, y = specular; +varying vec2 intensity; + +varying float clipping_planes_dot; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 eye_position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * eye_position; + + // Fill in the scalar for fragment shader clipping. Fragments with this value lower than zero are discarded. + clipping_planes_dot = dot(volume_world_matrix * vec4(v_position, 1.0), clipping_plane); +} diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 9ca610087..ca74cb391 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -476,6 +476,11 @@ public: const PrintObjects& objects() const { return m_objects; } // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects // in the notification center. + const SLAPrintObject* get_print_object_by_model_object_id(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const SLAPrintObject* obj) { return obj->model_object()->id() == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const SLAPrintObject* get_object(ObjectID object_id) const { auto it = std::find_if(m_objects.begin(), m_objects.end(), [object_id](const SLAPrintObject *obj) { return obj->id() == object_id; }); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 103ab339c..c6ef2d13b 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -852,74 +852,6 @@ int GLVolumeCollection::load_object_volume( return int(this->volumes.size() - 1); } -//// Load SLA auxiliary GLVolumes (for support trees or pad). -//// This function produces volumes for multiple instances in a single shot, -//// as some object specific mesh conversions may be expensive. -//#if ENABLE_LEGACY_OPENGL_REMOVAL -//void GLVolumeCollection::load_object_auxiliary( -// const SLAPrintObject* print_object, -// int obj_idx, -// // pairs of -// const std::vector>& instances, -// SLAPrintObjectStep milestone, -// // Timestamp of the last change of the milestone -// size_t timestamp) -//#else -//void GLVolumeCollection::load_object_auxiliary( -// const SLAPrintObject *print_object, -// int obj_idx, -// // pairs of -// const std::vector>& instances, -// SLAPrintObjectStep milestone, -// // Timestamp of the last change of the milestone -// size_t timestamp, -// bool opengl_initialized) -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -//{ -// assert(print_object->is_step_done(milestone)); -// Transform3d mesh_trafo_inv = print_object->trafo().inverse(); -// // Get the support mesh. -// TriangleMesh mesh = print_object->get_mesh(milestone); -// mesh.transform(mesh_trafo_inv); -// // Convex hull is required for out of print bed detection. -// TriangleMesh convex_hull = mesh.convex_hull_3d(); -// for (const std::pair& instance_idx : instances) { -// const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; -// this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); -// GLVolume& v = *this->volumes.back(); -//#if ENABLE_LEGACY_OPENGL_REMOVAL -//#if ENABLE_SMOOTH_NORMALS -// v.model.init_from(mesh, true); -//#else -// v.model.init_from(mesh); -// v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR); -//#if ENABLE_RAYCAST_PICKING -// v.mesh_raycaster = std::make_unique(std::make_shared(mesh)); -//#endif // ENABLE_RAYCAST_PICKING -//#endif // ENABLE_SMOOTH_NORMALS -//#else -//#if ENABLE_SMOOTH_NORMALS -// v.indexed_vertex_array.load_mesh(mesh, true); -//#else -// v.indexed_vertex_array.load_mesh(mesh); -//#endif // ENABLE_SMOOTH_NORMALS -// v.indexed_vertex_array.finalize_geometry(opengl_initialized); -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -// v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); -// v.geometry_id = std::pair(timestamp, model_instance.id().id); -// // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. -// if (&instance_idx == &instances.back()) -// v.set_convex_hull(std::move(convex_hull)); -// else -// v.set_convex_hull(convex_hull); -// v.is_modifier = false; -// v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); -// v.set_instance_transformation(model_instance.get_transformation()); -// // Leave the volume transformation at identity. -// // v.set_volume_transformation(model_volume->get_transformation()); -// } -//} - #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_OPENGL_ES int GLVolumeCollection::load_wipe_tower_preview( diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index abf3e9264..d1c16290f 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -658,16 +658,6 @@ public: int volume_idx, int instance_idx); - // Load SLA auxiliary GLVolumes (for support trees or pad). -// void load_object_auxiliary( -// const SLAPrintObject* print_object, -// int obj_idx, -// // pairs of -// const std::vector>& instances, -// SLAPrintObjectStep milestone, -// // Timestamp of the last change of the milestone -// size_t timestamp); - #if ENABLE_OPENGL_ES int load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr); @@ -689,17 +679,6 @@ public: int instance_idx, bool opengl_initialized); - // Load SLA auxiliary GLVolumes (for support trees or pad). - void load_object_auxiliary( - const SLAPrintObject *print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp, - bool opengl_initialized); - int load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); #endif // ENABLE_LEGACY_OPENGL_REMOVAL diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a698633a8..636080db7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2011,15 +2011,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re size_t volume_idx; }; -// // SLA steps to pull the preview meshes for. -// typedef std::array SLASteps; -// SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; -// struct SLASupportState { -// std::array::value> step; -// }; -// // State of the sla_steps for all SLAPrintObjects. -// std::vector sla_support_state; - std::vector instance_ids_selected; std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); std::vector deleted_volumes; @@ -2044,33 +2035,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } } } -// if (printer_technology == ptSLA) { -// const SLAPrint* sla_print = this->sla_print(); -//#ifndef NDEBUG -// // Verify that the SLAPrint object is synchronized with m_model. -// check_model_ids_equal(*m_model, sla_print->model()); -//#endif /* NDEBUG */ -// sla_support_state.reserve(sla_print->objects().size()); -// for (const SLAPrintObject* print_object : sla_print->objects()) { -// SLASupportState state; -// for (size_t istep = 0; istep < sla_steps.size(); ++istep) { -// state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); -// if (state.step[istep].state == PrintStateBase::DONE) { -// if (!print_object->has_mesh(sla_steps[istep])) -// // Consider the DONE step without a valid mesh as invalid for the purpose -// // of mesh visualization. -// state.step[istep].state = PrintStateBase::INVALID; -// else if (sla_steps[istep] != slaposDrillHoles) -// for (const ModelInstance* model_instance : print_object->model_object()->instances) -// // Only the instances, which are currently printable, will have the SLA support structures kept. -// // The instances outside the print bed will have the GLVolumes of their support structures released. -// if (model_instance->is_printable()) -// aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); -// } -// } -// sla_support_state.emplace_back(state); -// } -// } + std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); // Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh. @@ -2190,139 +2155,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } if (printer_technology == ptSLA) { -// size_t idx = 0; -// const SLAPrint *sla_print = this->sla_print(); -// std::vector shift_zs(m_model->objects.size(), 0); -// double relative_correction_z = sla_print->relative_correction().z(); -// if (relative_correction_z <= EPSILON) -// relative_correction_z = 1.; -// for (const SLAPrintObject *print_object : sla_print->objects()) { -// SLASupportState &state = sla_support_state[idx ++]; -// const ModelObject *model_object = print_object->model_object(); -// // Find an index of the ModelObject -// int object_idx; -// // There may be new SLA volumes added to the scene for this print_object. -// // Find the object index of this print_object in the Model::objects list. -// auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); -// assert(it != sla_print->model().objects.end()); -// object_idx = it - sla_print->model().objects.begin(); -// // Cache the Z offset to be applied to all volumes with this object_idx. -// shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; -// // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. -// // pairs of -// std::vector> instances[std::tuple_size::value]; -// for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { -// const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; -// // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. -// auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), -// [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); -// assert(it != model_object->instances.end()); -// int instance_idx = it - model_object->instances.begin(); -// for (size_t istep = 0; istep < sla_steps.size(); ++ istep) -// if (sla_steps[istep] == slaposDrillHoles) { -// // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance, -// // not into its own GLVolume. -// // There shall always be such a GLVolume allocated. -// ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id); -// auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); -// assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); -// assert(!it->new_geometry()); -// GLVolume &volume = *m_volumes.volumes[it->volume_idx]; -// if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { -// // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. -//#if ENABLE_LEGACY_OPENGL_REMOVAL -// volume.model.reset(); -//#else -// volume.indexed_vertex_array.release_geometry(); -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -// if (state.step[istep].state == PrintStateBase::DONE) { -// TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); -// assert(! mesh.empty()); - -// // sla_trafo does not contain volume trafo. To get a mesh to create -// // a new volume from, we have to apply vol trafo inverse separately. -// const ModelObject& mo = *m_model->objects[volume.object_idx()]; -// Transform3d trafo = sla_print->sla_trafo(mo) -// * mo.volumes.front()->get_transformation().get_matrix(); -// mesh.transform(trafo.inverse()); -//#if ENABLE_SMOOTH_NORMALS -//#if ENABLE_LEGACY_OPENGL_REMOVAL -// volume.model.init_from(mesh, true); -//#else -// volume.indexed_vertex_array.load_mesh(mesh, true); -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -//#else -//#if ENABLE_LEGACY_OPENGL_REMOVAL -// volume.model.init_from(mesh); -//#if ENABLE_RAYCAST_PICKING -// volume.mesh_raycaster = std::make_unique(std::make_shared(mesh)); -//#endif // ENABLE_RAYCAST_PICKING -//#else -// volume.indexed_vertex_array.load_mesh(mesh); -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -//#endif // ENABLE_SMOOTH_NORMALS -// } -// else { -// // Reload the original volume. -//#if ENABLE_SMOOTH_NORMALS -//#if ENABLE_LEGACY_OPENGL_REMOVAL -// volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -//#else -// volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -//#else -//#if ENABLE_LEGACY_OPENGL_REMOVAL -//#if ENABLE_RAYCAST_PICKING -// const TriangleMesh& new_mesh = m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(); -// volume.model.init_from(new_mesh); -// volume.mesh_raycaster = std::make_unique(std::make_shared(new_mesh)); -//#else -// volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -//#endif // ENABLE_RAYCAST_PICKING -//#else -// volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -//#endif // ENABLE_SMOOTH_NORMALS -// } -//#if !ENABLE_LEGACY_OPENGL_REMOVAL -// volume.finalize_geometry(true); -//#endif // !ENABLE_LEGACY_OPENGL_REMOVAL -// } -// //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable -// // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables -// // of various concenrs (model vs. 3D print path). -// volume.offsets = { state.step[istep].timestamp }; -// } -// else if (state.step[istep].state == PrintStateBase::DONE) { -// // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. -// ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); -// auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); -// assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); -// if (it->new_geometry()) { -// // This can be an SLA support structure that should not be rendered (in case someone used undo -// // to revert to before it was generated). If that's the case, we should not generate anything. -// if (model_object->sla_points_status != sla::PointsStatus::NoPoints) -// instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); -// else -// shift_zs[object_idx] = 0.; -// } -// else { -// // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. -// m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); -// m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); -// } -// } -// } - -// for (size_t istep = 0; istep < sla_steps.size(); ++istep) -// if (!instances[istep].empty()) -//#if ENABLE_LEGACY_OPENGL_REMOVAL -// m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); -//#else -// m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); -//#endif // ENABLE_LEGACY_OPENGL_REMOVAL -// } - // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed for (GLVolume* volume : m_volumes.volumes) if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) { @@ -7638,25 +7470,24 @@ void GLCanvas3D::_load_sla_shells() }; // adds objects' volumes - for (const SLAPrintObject* obj : print->objects()) - /*if (obj->is_step_done(slaposSliceSupports))*/ { - unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); - for (const SLAPrintObject::Instance& instance : obj->instances()) { - add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); - // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when - // through the update_volumes_colors_by_extruder() call. - m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); - if (auto &tree_mesh = obj->support_mesh(); !tree_mesh.empty()) - add_volume(*obj, -int(slaposSupportTree), instance, tree_mesh, GLVolume::SLA_SUPPORT_COLOR, true); - if (auto &pad_mesh = obj->pad_mesh(); !pad_mesh.empty()) - add_volume(*obj, -int(slaposPad), instance, pad_mesh, GLVolume::SLA_PAD_COLOR, false); - } - double shift_z = obj->get_current_elevation(); - for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { - // apply shift z - m_volumes.volumes[i]->set_sla_shift_z(shift_z); - } + for (const SLAPrintObject* obj : print->objects()) { + unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); + for (const SLAPrintObject::Instance& instance : obj->instances()) { + add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); + // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when + // through the update_volumes_colors_by_extruder() call. + m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); + if (auto &tree_mesh = obj->support_mesh(); !tree_mesh.empty()) + add_volume(*obj, -int(slaposSupportTree), instance, tree_mesh, GLVolume::SLA_SUPPORT_COLOR, true); + if (auto &pad_mesh = obj->pad_mesh(); !pad_mesh.empty()) + add_volume(*obj, -int(slaposPad), instance, pad_mesh, GLVolume::SLA_PAD_COLOR, false); } + double shift_z = obj->get_current_elevation(); + for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { + // apply shift z + m_volumes.volumes[i]->set_sla_shift_z(shift_z); + } + } update_volumes_colors_by_extruder(); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 140cbaf74..a519a26a1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -688,8 +688,6 @@ public: void set_raycaster_gizmos_on_top(bool value) { m_scene_raycaster.set_gizmos_on_top(value); } - - const SceneRaycaster & raycaster() const { return m_scene_raycaster; } #endif // ENABLE_RAYCAST_PICKING void set_as_dirty(); diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index f0b91faf3..a60a5121b 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -72,6 +72,8 @@ std::pair GLShadersManager::init() #if ENABLE_LEGACY_OPENGL_REMOVAL // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview valid &= append_shader("gouraud_light", { prefix + "gouraud_light.vs", prefix + "gouraud_light.fs" }); + // extend "gouraud_light" by adding clipping, used in sla gizmos + valid &= append_shader("gouraud_light_clip", { prefix + "gouraud_light_clip.vs", prefix + "gouraud_light_clip.fs" }); // used to render printbed valid &= append_shader("printbed", { prefix + "printbed.vs", prefix + "printbed.fs" }); #else diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 52aed6bfc..d098d41f5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/SLAPrint.hpp" #include "libslic3r/Model.hpp" @@ -53,14 +54,21 @@ void GLGizmoHollow::data_changed() reload_cache(); m_old_mo_id = mo->id(); } -// if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) -// m_holes_in_drilled_mesh = mo->sla_drain_holes; + + const SLAPrintObject* po = m_c->selection_info()->print_object(); + if (po != nullptr && po->get_mesh_to_print().empty()) + process_mesh(slaposAssembly); + + update_volumes(); + #if ENABLE_RAYCAST_PICKING - if (m_raycasters.empty()) - on_register_raycasters_for_picking(); + if (m_hole_raycasters.empty()) + register_hole_raycasters_for_picking(); else - update_raycasters_for_picking_transform(); + update_hole_raycasters_for_picking_transform(); #endif // ENABLE_RAYCAST_PICKING + + m_c->instances_hider()->set_hide_full_scene(true); } } @@ -87,16 +95,15 @@ void GLGizmoHollow::on_render() glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - if (selection.is_from_single_instance()) #if ENABLE_RAYCAST_PICKING - render_points(selection); + render_volumes(); + render_points(selection); #else - render_points(selection, false); + render_points(selection, false); #endif // ENABLE_RAYCAST_PICKING m_selection_rectangle.render(m_parent); m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); glsafe(::glDisable(GL_BLEND)); } @@ -104,27 +111,14 @@ void GLGizmoHollow::on_render() #if ENABLE_RAYCAST_PICKING void GLGizmoHollow::on_register_raycasters_for_picking() { - assert(m_raycasters.empty()); - - init_cylinder_model(); - - set_sla_auxiliary_volumes_picking_state(false); - - const CommonGizmosDataObjects::SelectionInfo* info = m_c->selection_info(); - if (info != nullptr && !info->model_object()->sla_drain_holes.empty()) { - const sla::DrainHoles& drain_holes = info->model_object()->sla_drain_holes; - for (int i = 0; i < (int)drain_holes.size(); ++i) { - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cylinder.mesh_raycaster)); - } - update_raycasters_for_picking_transform(); - } + register_hole_raycasters_for_picking(); + register_volume_raycasters_for_picking(); } void GLGizmoHollow::on_unregister_raycasters_for_picking() { - m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); - m_raycasters.clear(); - set_sla_auxiliary_volumes_picking_state(true); + unregister_hole_raycasters_for_picking(); + unregister_volume_raycasters_for_picking(); } #else void GLGizmoHollow::on_render_for_picking() @@ -190,7 +184,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) #if ENABLE_RAYCAST_PICKING const bool clipped = is_mesh_point_clipped(drain_hole.pos.cast()); - m_raycasters[i]->set_active(!clipped); + m_hole_raycasters[i]->set_active(!clipped); if (clipped) continue; #else @@ -270,6 +264,25 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) #endif // !ENABLE_LEGACY_OPENGL_REMOVAL } +void GLGizmoHollow::render_volumes() +{ + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_clip"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("emission_factor", 0.0f); + const Camera& camera = wxGetApp().plater()->get_camera(); + + ClippingPlane clipping_plane = (m_c->object_clipper()->get_position() == 0.0) ? ClippingPlane::ClipsNothing() : *m_c->object_clipper()->get_clipping_plane(); + clipping_plane.set_normal(-clipping_plane.get_normal()); + m_volumes.set_clipping_plane(clipping_plane.get_data()); + + m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix()); + shader->stop_using(); + +} + bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const { if (m_c->object_clipper()->get_position() == 0.) @@ -291,48 +304,26 @@ bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const // Return false if no intersection was found, true otherwise. bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) { + if (m_c->raycaster()->raycasters().size() != 1) + return false; if (! m_c->raycaster()->raycaster()) return false; - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_first_volume(); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - // The raycaster query Vec3f hit; Vec3f normal; if (m_c->raycaster()->raycaster()->unproject_on_mesh( mouse_pos, - trafo.get_matrix(), - camera, + m_volumes.volumes.front()->world_matrix(), + wxGetApp().plater()->get_camera(), hit, normal, - clp_dist != 0. ? clp : nullptr)) - { -// if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { -// // in this case the raycaster sees the hollowed and drilled mesh. -// // if the point lies on the surface created by the hole, we want -// // to ignore it. -// for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { -// sla::DrainHole outer(hole); -// outer.radius *= 1.001f; -// outer.height *= 1.001f; -// if (outer.is_inside(hit)) -// return false; -// } -// } - + m_c->object_clipper()->get_position() != 0.0 ? m_c->object_clipper()->get_clipping_plane() : nullptr)) { // Return both the point and the facet normal. pos_and_normal = std::make_pair(hit, normal); return true; } - else - return false; + return false; } // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. @@ -348,9 +339,8 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos // left down with shift - show the selection rectangle: if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { if (m_hover_id == -1) { - if (shift_down || alt_down) { + if (shift_down || alt_down) m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); - } } else { if (m_selected[m_hover_id]) @@ -383,8 +373,8 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos m_parent.set_as_dirty(); m_wait_for_up_event = true; #if ENABLE_RAYCAST_PICKING - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + unregister_hole_raycasters_for_picking(); + register_hole_raycasters_for_picking(); #endif // ENABLE_RAYCAST_PICKING } else @@ -510,8 +500,8 @@ void GLGizmoHollow::delete_selected_points() } #if ENABLE_RAYCAST_PICKING - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + unregister_hole_raycasters_for_picking(); + register_hole_raycasters_for_picking(); #endif // ENABLE_RAYCAST_PICKING select_point(NoPoints); } @@ -580,39 +570,61 @@ bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) +void GLGizmoHollow::process_mesh(SLAPrintObjectStep step, bool postpone_error_messages) { - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_hollowing( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); + wxGetApp().CallAfter([this, step, postpone_error_messages]() { + wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages); + }); } #if ENABLE_RAYCAST_PICKING -void GLGizmoHollow::set_sla_auxiliary_volumes_picking_state(bool state) +void GLGizmoHollow::register_hole_raycasters_for_picking() { - std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); - if (raycasters != nullptr) { - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList ids = selection.get_volume_idxs(); - for (unsigned int id : ids) { - const GLVolume* v = selection.get_volume(id); - if (v->is_sla_pad() || v->is_sla_support()) { - auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr item) { return item->get_raycaster() == v->mesh_raycaster.get(); }); - if (it != raycasters->end()) - (*it)->set_active(state); - } + assert(m_hole_raycasters.empty()); + + init_cylinder_model(); + + const CommonGizmosDataObjects::SelectionInfo* info = m_c->selection_info(); + if (info != nullptr && !info->model_object()->sla_drain_holes.empty()) { + const sla::DrainHoles& drain_holes = info->model_object()->sla_drain_holes; + for (int i = 0; i < (int)drain_holes.size(); ++i) { + m_hole_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cylinder.mesh_raycaster, Transform3d::Identity())); } + update_hole_raycasters_for_picking_transform(); } } -void GLGizmoHollow::update_raycasters_for_picking_transform() +void GLGizmoHollow::unregister_hole_raycasters_for_picking() +{ + for (size_t i = 0; i < m_hole_raycasters.size(); ++i) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, i); + } + m_hole_raycasters.clear(); +} + +void GLGizmoHollow::register_volume_raycasters_for_picking() +{ + for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { + const GLVolume* v = m_volumes.volumes[i]; + m_volume_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i, *v->mesh_raycaster, v->world_matrix())); + } +} + +void GLGizmoHollow::unregister_volume_raycasters_for_picking() +{ + for (size_t i = 0; i < m_volume_raycasters.size(); ++i) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i); + } + m_volume_raycasters.clear(); +} + +void GLGizmoHollow::update_hole_raycasters_for_picking_transform() { const CommonGizmosDataObjects::SelectionInfo* info = m_c->selection_info(); if (info != nullptr) { const sla::DrainHoles& drain_holes = info->model_object()->sla_drain_holes; if (!drain_holes.empty()) { - assert(!m_raycasters.empty()); + assert(!m_hole_raycasters.empty()); const GLVolume* vol = m_parent.get_selection().get_first_volume(); const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse(); @@ -625,13 +637,62 @@ void GLGizmoHollow::update_raycasters_for_picking_transform() const Eigen::AngleAxisd aa(q); const Transform3d matrix = vol->world_matrix() * hole_matrix * Transform3d(aa.toRotationMatrix()) * Geometry::translation_transform(-drain_hole.height * Vec3d::UnitZ()) * Geometry::scale_transform(Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - m_raycasters[i]->set_transform(matrix); + m_hole_raycasters[i]->set_transform(matrix); } } } } #endif // ENABLE_RAYCAST_PICKING +void GLGizmoHollow::update_volumes() +{ + m_volumes.clear(); + unregister_volume_raycasters_for_picking(); + + const ModelObject* mo = m_c->selection_info()->model_object(); + if (mo == nullptr) + return; + + const SLAPrintObject* po = m_c->selection_info()->print_object(); + if (po == nullptr) + return; + + TriangleMesh backend_mesh = po->get_mesh_to_print(); + if (!backend_mesh.empty()) { + // The backend has generated a valid mesh. Use it + backend_mesh.transform(po->trafo().inverse()); + m_volumes.volumes.emplace_back(new GLVolume()); + GLVolume* new_volume = m_volumes.volumes.back(); + new_volume->model.init_from(backend_mesh); + new_volume->set_instance_transformation(po->model_object()->instances[m_parent.get_selection().get_instance_idx()]->get_transformation()); + new_volume->set_sla_shift_z(po->get_current_elevation()); + new_volume->selected = true; // to set the proper color + new_volume->mesh_raycaster = std::make_unique(backend_mesh); + } + + if (m_volumes.volumes.empty()) { + // No valid mesh found in the backend. Use the selection to duplicate the volumes + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int idx : idxs) { + const GLVolume* v = selection.get_volume(idx); + if (!v->is_modifier) { + m_volumes.volumes.emplace_back(new GLVolume()); + GLVolume* new_volume = m_volumes.volumes.back(); + const TriangleMesh& mesh = mo->volumes[v->volume_idx()]->mesh(); + new_volume->model.init_from(mesh); + new_volume->set_instance_transformation(v->get_instance_transformation()); + new_volume->set_volume_transformation(v->get_volume_transformation()); + new_volume->set_sla_shift_z(v->get_sla_shift_z()); + new_volume->selected = true; // to set the proper color + new_volume->mesh_raycaster = std::make_unique(mesh); + } + } + } + + register_volume_raycasters_for_picking(); +} + std::vector> GLGizmoHollow::get_config_options(const std::vector& keys) const { @@ -724,8 +785,8 @@ RENDER_AGAIN: window_width = std::max(window_width, button_preview_width); if (m_imgui->button(m_desc["preview"])) - hollow_mesh(); - + process_mesh(slaposDrillHoles); + bool config_changed = false; ImGui::Separator(); @@ -896,13 +957,6 @@ RENDER_AGAIN: if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); - // make sure supports are shown/hidden as appropriate - bool show_sups = m_c->instances_hider()->are_supports_shown(); - if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { - m_c->instances_hider()->show_supports(show_sups); - force_refresh = true; - } - m_imgui->end(); @@ -935,7 +989,7 @@ bool GLGizmoHollow::on_is_activable() const const Selection& selection = m_parent.get_selection(); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) + || !selection.is_single_full_instance()) return false; // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. @@ -964,9 +1018,7 @@ CommonGizmosDataID GLGizmoHollow::on_get_requirements() const int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); + | int(CommonGizmosDataID::ObjectClipper)); } @@ -975,8 +1027,12 @@ void GLGizmoHollow::on_set_state() if (m_state == m_old_state) return; - if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off + if (m_state == Off && m_old_state != Off) { + // the gizmo was just turned Off m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); + m_c->instances_hider()->set_hide_full_scene(false); + } + m_old_state = m_state; } @@ -1091,6 +1147,9 @@ void GLGizmoHollow::reload_cache() void GLGizmoHollow::on_set_hover_id() { + if (m_c->selection_info()->model_object() == nullptr) + return; + if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id) m_hover_id = -1; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 0fdc3b2db..e7a324a1e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -3,6 +3,7 @@ #include "GLGizmoBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" +#include "slic3r/GUI/3DScene.hpp" #include #include @@ -25,7 +26,6 @@ class GLGizmoHollow : public GLGizmoBase private: bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); - public: GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); void data_changed() override; @@ -59,21 +59,29 @@ private: #else void render_points(const Selection& selection, bool picking = false); #endif // ENABLE_RAYCAST_PICKING - void hollow_mesh(bool postpone_error_messages = false); + void render_volumes(); + void process_mesh(SLAPrintObjectStep step, bool postpone_error_messages = false); #if ENABLE_RAYCAST_PICKING - void set_sla_auxiliary_volumes_picking_state(bool state); - void update_raycasters_for_picking_transform(); + void register_hole_raycasters_for_picking(); + void unregister_hole_raycasters_for_picking(); + void register_volume_raycasters_for_picking(); + void unregister_volume_raycasters_for_picking(); + void update_hole_raycasters_for_picking_transform(); #endif // ENABLE_RAYCAST_PICKING + void update_volumes(); ObjectID m_old_mo_id = -1; #if ENABLE_RAYCAST_PICKING PickingModel m_cylinder; - std::vector> m_raycasters; + std::vector> m_hole_raycasters; + std::vector> m_volume_raycasters; #else GLModel m_cylinder; #endif // ENABLE_RAYCAST_PICKING + GLVolumeCollection m_volumes; + float m_new_hole_radius = 2.f; // Size of a new hole. float m_new_hole_height = 6.f; mutable std::vector m_selected; // which holes are currently selected diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index d0dd3baa0..cc45a8e73 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -60,6 +60,16 @@ bool GLGizmoSlaSupports::on_init() return true; } +static int last_completed_step(const SLAPrint& sla) +{ + int step = -1; + for (int i = 0; i < (int)SLAPrintObjectStep::slaposCount; ++i) { + if (sla.is_step_done((SLAPrintObjectStep)i)) + ++step; + } + return step; +} + void GLGizmoSlaSupports::data_changed() { if (! m_c->selection_info()) @@ -71,19 +81,25 @@ void GLGizmoSlaSupports::data_changed() disable_editing_mode(); reload_cache(); m_old_mo_id = mo->id(); - m_c->instances_hider()->show_supports(true); } // If we triggered autogeneration before, check backend and fetch results if they are there if (mo) { + m_c->instances_hider()->set_hide_full_scene(true); + const SLAPrintObject* po = m_c->selection_info()->print_object(); + if (po != nullptr && last_completed_step(*po->print()) < (int)slaposDrillHoles) + process_mesh(slaposDrillHoles, false); + + update_volumes(); + if (mo->sla_points_status == sla::PointsStatus::Generating) get_data_from_backend(); #if ENABLE_RAYCAST_PICKING - if (m_raycasters.empty()) - on_register_raycasters_for_picking(); + if (m_point_raycasters.empty()) + register_point_raycasters_for_picking(); else - update_raycasters_for_picking_transform(); + update_point_raycasters_for_picking_transform(); #endif // ENABLE_RAYCAST_PICKING } @@ -111,8 +127,6 @@ void GLGizmoSlaSupports::on_render() if (!m_sphere.is_initialized()) m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); #endif // ENABLE_RAYCAST_PICKING - if (!m_cylinder.is_initialized()) - m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); ModelObject* mo = m_c->selection_info()->model_object(); const Selection& selection = m_parent.get_selection(); @@ -128,16 +142,15 @@ void GLGizmoSlaSupports::on_render() glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - if (selection.is_from_single_instance()) #if ENABLE_RAYCAST_PICKING - render_points(selection); + render_volumes(); + render_points(selection); #else - render_points(selection, false); + render_points(selection, false); #endif // ENABLE_RAYCAST_PICKING m_selection_rectangle.render(m_parent); m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); glsafe(::glDisable(GL_BLEND)); } @@ -145,23 +158,14 @@ void GLGizmoSlaSupports::on_render() #if ENABLE_RAYCAST_PICKING void GLGizmoSlaSupports::on_register_raycasters_for_picking() { - assert(m_raycasters.empty()); - set_sla_auxiliary_volumes_picking_state(false); - - if (m_editing_mode && !m_editing_cache.empty()) { - for (size_t i = 0; i < m_editing_cache.size(); ++i) { - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_sphere.mesh_raycaster), - m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cone.mesh_raycaster)); - } - update_raycasters_for_picking_transform(); - } + register_point_raycasters_for_picking(); + register_volume_raycasters_for_picking(); } void GLGizmoSlaSupports::on_unregister_raycasters_for_picking() { - m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); - m_raycasters.clear(); - set_sla_auxiliary_volumes_picking_state(true); + unregister_point_raycasters_for_picking(); + unregister_volume_raycasters_for_picking(); } #else void GLGizmoSlaSupports::on_render_for_picking() @@ -181,10 +185,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); const bool has_points = (cache_size != 0); - const bool has_holes = (/*! m_c->hollowed_mesh()->get_hollowed_mesh() - &&*/ ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); - - if (! has_points && ! has_holes) + if (!has_points) return; #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -234,9 +235,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) #if ENABLE_RAYCAST_PICKING const bool clipped = is_mesh_point_clipped(support_point.pos.cast()); - if (!m_raycasters.empty()) { - m_raycasters[i].first->set_active(!clipped); - m_raycasters[i].second->set_active(!clipped); + if (i < m_point_raycasters.size()) { + m_point_raycasters[i].first->set_active(!clipped); + m_point_raycasters[i].second->set_active(!clipped); } if (clipped) continue; @@ -306,8 +307,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) if (m_editing_mode) { // in case the normal is not yet cached, find and cache it if (m_editing_cache[i].normal == Vec3f::Zero()) - m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); - //m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); + m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); @@ -373,67 +373,28 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) #endif // !ENABLE_LEGACY_OPENGL_REMOVAL } - // Now render the drain holes: -#if ENABLE_RAYCAST_PICKING - if (has_holes) { -#else - if (has_holes && ! picking) { -#endif // ENABLE_RAYCAST_PICKING - render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cylinder.set_color(render_color); -#else - m_cylinder.set_color(-1, render_color); - if (shader != nullptr) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - shader->set_uniform("emission_factor", 0.5f); - for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { - if (is_mesh_point_clipped(drain_hole.pos.cast())) - continue; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Transform3d hole_matrix = Geometry::translation_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; -#else - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - if (vol->is_left_handed()) - glsafe(::glFrontFace(GL_CW)); - - // Matrices set, we can render the point mark now. - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - const Eigen::AngleAxisd aa(q); -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Transform3d model_matrix = vol->world_matrix() * hole_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::translation_transform(-drain_hole.height * Vec3d::UnitZ()) * Geometry::scale_transform(Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - shader->set_uniform("view_model_matrix", view_matrix * model_matrix); - const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); - shader->set_uniform("view_normal_matrix", view_normal_matrix); -#else - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_cylinder.render(); - - if (vol->is_left_handed()) - glsafe(::glFrontFace(GL_CCW)); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPopMatrix()); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - } - #if !ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glPopMatrix()); #endif // !ENABLE_LEGACY_OPENGL_REMOVAL } +void GLGizmoSlaSupports::render_volumes() +{ + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_clip"); + if (shader == nullptr) + return; + shader->start_using(); + shader->set_uniform("emission_factor", 0.0f); + const Camera& camera = wxGetApp().plater()->get_camera(); + + ClippingPlane clipping_plane = (m_c->object_clipper()->get_position() == 0.0) ? ClippingPlane::ClipsNothing() : *m_c->object_clipper()->get_clipping_plane(); + clipping_plane.set_normal(-clipping_plane.get_normal()); + m_volumes.set_clipping_plane(clipping_plane.get_data()); + + m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix()); + shader->stop_using(); +} bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const { @@ -456,48 +417,22 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const // Return false if no intersection was found, true otherwise. bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) { - if (m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->empty()) + if (!m_c->raycaster()->raycaster()) return false; - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_first_volume(); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - // The raycaster query Vec3f hit; Vec3f normal; - if (m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->unproject_on_mesh( + if (m_c->raycaster()->raycaster()->unproject_on_mesh( mouse_pos, - trafo.get_matrix(), - camera, + m_volumes.volumes.front()->world_matrix(), + wxGetApp().plater()->get_camera(), hit, normal, - clp_dist != 0. ? clp : nullptr)) - { - // Check whether the hit is in a hole -// bool in_hole = false; - // In case the hollowed and drilled mesh is available, we can allow - // placing points in holes, because they should never end up - // on surface that's been drilled away. -// if (! m_c->hollowed_mesh()->get_hollowed_mesh()) { -// sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; -// for (const sla::DrainHole& hole : drain_holes) { -// if (hole.is_inside(hit)) { -// in_hole = true; -// break; -// } -// } -// } -// if (! in_hole) { - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; -// } + m_c->object_clipper()->get_position() != 0.0 ? m_c->object_clipper()->get_clipping_plane() : nullptr)) { + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); + return true; } return false; @@ -548,8 +483,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_parent.set_as_dirty(); m_wait_for_up_event = true; #if ENABLE_RAYCAST_PICKING - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + unregister_point_raycasters_for_picking(); + register_point_raycasters_for_picking(); #endif // ENABLE_RAYCAST_PICKING } else @@ -592,9 +527,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous for (size_t idx : points_idxs) points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast()); - assert(!m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume).emtpy()); - for (size_t idx : m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->get_unobscured_idxs( - trafo, wxGetApp().plater()->get_camera(), points_inside, + for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( + trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->object_clipper()->get_clipping_plane())) { if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to @@ -712,8 +646,8 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) } #if ENABLE_RAYCAST_PICKING - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + unregister_point_raycasters_for_picking(); + register_point_raycasters_for_picking(); #endif // ENABLE_RAYCAST_PICKING select_point(NoPoints); @@ -1027,7 +961,7 @@ bool GLGizmoSlaSupports::on_is_activable() const const Selection& selection = m_parent.get_selection(); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) + || !selection.is_single_full_instance()) return false; // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. @@ -1055,9 +989,7 @@ CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); + | int(CommonGizmosDataID::ObjectClipper)); } @@ -1101,6 +1033,9 @@ void GLGizmoSlaSupports::on_set_state() disable_editing_mode(); // so it is not active next time the gizmo opens m_old_mo_id = -1; } + + if (m_state == Off) + m_c->instances_hider()->set_hide_full_scene(false); } m_old_state = m_state; } @@ -1395,10 +1330,8 @@ void GLGizmoSlaSupports::switch_to_editing_mode() m_editing_cache.emplace_back(sp); select_point(NoPoints); #if ENABLE_RAYCAST_PICKING - on_register_raycasters_for_picking(); + register_point_raycasters_for_picking(); #endif // ENABLE_RAYCAST_PICKING - - m_c->instances_hider()->show_supports(false); m_parent.set_as_dirty(); } @@ -1408,10 +1341,9 @@ void GLGizmoSlaSupports::disable_editing_mode() if (m_editing_mode) { m_editing_mode = false; wxGetApp().plater()->leave_gizmos_stack(); - m_c->instances_hider()->show_supports(true); m_parent.set_as_dirty(); #if ENABLE_RAYCAST_PICKING - on_unregister_raycasters_for_picking(); + unregister_point_raycasters_for_picking(); #endif // ENABLE_RAYCAST_PICKING } wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); @@ -1432,53 +1364,130 @@ bool GLGizmoSlaSupports::unsaved_changes() const } #if ENABLE_RAYCAST_PICKING -void GLGizmoSlaSupports::set_sla_auxiliary_volumes_picking_state(bool state) +void GLGizmoSlaSupports::register_point_raycasters_for_picking() { - std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); - if (raycasters != nullptr) { - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList ids = selection.get_volume_idxs(); - for (unsigned int id : ids) { - const GLVolume* v = selection.get_volume(id); - if (v->is_sla_pad() || v->is_sla_support()) { - auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr item) { return item->get_raycaster() == v->mesh_raycaster.get(); }); - if (it != raycasters->end()) - (*it)->set_active(state); - } + assert(m_point_raycasters.empty()); + + if (m_editing_mode && !m_editing_cache.empty()) { + for (size_t i = 0; i < m_editing_cache.size(); ++i) { + m_point_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_sphere.mesh_raycaster, Transform3d::Identity()), + m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cone.mesh_raycaster, Transform3d::Identity())); } + update_point_raycasters_for_picking_transform(); } } -void GLGizmoSlaSupports::update_raycasters_for_picking_transform() +void GLGizmoSlaSupports::unregister_point_raycasters_for_picking() { - if (!m_editing_cache.empty()) { - assert(!m_raycasters.empty()); + for (size_t i = 0; i < m_point_raycasters.size(); ++i) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, i); + } + m_point_raycasters.clear(); +} - const GLVolume* vol = m_parent.get_selection().get_first_volume(); - const Geometry::Transformation transformation(vol->world_matrix()); - const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); - for (size_t i = 0; i < m_editing_cache.size(); ++i) { - const Transform3d support_matrix = Geometry::translation_transform(m_editing_cache[i].support_point.pos.cast()) * instance_scaling_matrix_inverse; +void GLGizmoSlaSupports::register_volume_raycasters_for_picking() +{ + for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { + const GLVolume* v = m_volumes.volumes[i]; + m_volume_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i, *v->mesh_raycaster, v->world_matrix())); + } +} - if (m_editing_cache[i].normal == Vec3f::Zero()) - m_parent.raycaster().get_raycasters(SceneRaycaster::EType::Volume)->front()->get_raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); +void GLGizmoSlaSupports::unregister_volume_raycasters_for_picking() +{ + for (size_t i = 0; i < m_volume_raycasters.size(); ++i) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i); + } + m_volume_raycasters.clear(); +} - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); - const Eigen::AngleAxisd aa(q); - const Transform3d cone_matrix = vol->world_matrix() * support_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::translation_transform((CONE_HEIGHT + m_editing_cache[i].support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ()) * - Geometry::rotation_transform({ double(PI), 0.0, 0.0 }) * Geometry::scale_transform({ CONE_RADIUS, CONE_RADIUS, CONE_HEIGHT }); - m_raycasters[i].second->set_transform(cone_matrix); +void GLGizmoSlaSupports::update_point_raycasters_for_picking_transform() +{ + if (m_editing_cache.empty()) + return; - const double radius = (double)m_editing_cache[i].support_point.head_front_radius * RenderPointScale; - const Transform3d sphere_matrix = vol->world_matrix() * support_matrix * Geometry::scale_transform(radius); - m_raycasters[i].first->set_transform(sphere_matrix); - } + assert(!m_point_raycasters.empty()); + + const GLVolume* vol = m_parent.get_selection().get_first_volume(); + const Geometry::Transformation transformation(vol->world_matrix()); + const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); + for (size_t i = 0; i < m_editing_cache.size(); ++i) { + const Transform3d support_matrix = Geometry::translation_transform(m_editing_cache[i].support_point.pos.cast()) * instance_scaling_matrix_inverse; + + if (m_editing_cache[i].normal == Vec3f::Zero()) + m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); + + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); + const Eigen::AngleAxisd aa(q); + const Transform3d cone_matrix = vol->world_matrix() * support_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform((CONE_HEIGHT + m_editing_cache[i].support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(), + Vec3d(PI, 0.0, 0.0), Vec3d(CONE_RADIUS, CONE_RADIUS, CONE_HEIGHT)); + m_point_raycasters[i].second->set_transform(cone_matrix); + + const double radius = (double)m_editing_cache[i].support_point.head_front_radius * RenderPointScale; + const Transform3d sphere_matrix = vol->world_matrix() * support_matrix * Geometry::scale_transform(radius); + m_point_raycasters[i].first->set_transform(sphere_matrix); } } #endif // ENABLE_RAYCAST_PICKING +void GLGizmoSlaSupports::update_volumes() +{ + m_volumes.clear(); + unregister_volume_raycasters_for_picking(); + + const ModelObject* mo = m_c->selection_info()->model_object(); + if (mo == nullptr) + return; + + const SLAPrintObject* po = m_c->selection_info()->print_object(); + if (po == nullptr) + return; + + TriangleMesh backend_mesh = po->get_mesh_to_print(); + if (!backend_mesh.empty()) { + // The backend has generated a valid mesh. Use it + backend_mesh.transform(po->trafo().inverse()); + m_volumes.volumes.emplace_back(new GLVolume()); + GLVolume* new_volume = m_volumes.volumes.back(); + new_volume->model.init_from(backend_mesh); + new_volume->set_instance_transformation(po->model_object()->instances[m_parent.get_selection().get_instance_idx()]->get_transformation()); + new_volume->set_sla_shift_z(po->get_current_elevation()); + new_volume->selected = true; // to set the proper color + new_volume->mesh_raycaster = std::make_unique(backend_mesh); + } + + if (m_volumes.volumes.empty()) { + // No valid mesh found in the backend. Use the selection to duplicate the volumes + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int idx : idxs) { + const GLVolume* v = selection.get_volume(idx); + if (!v->is_modifier) { + m_volumes.volumes.emplace_back(new GLVolume()); + GLVolume* new_volume = m_volumes.volumes.back(); + const TriangleMesh& mesh = mo->volumes[v->volume_idx()]->mesh(); + new_volume->model.init_from(mesh); + new_volume->set_instance_transformation(v->get_instance_transformation()); + new_volume->set_volume_transformation(v->get_volume_transformation()); + new_volume->set_sla_shift_z(v->get_sla_shift_z()); + new_volume->selected = true; // to set the proper color + new_volume->mesh_raycaster = std::make_unique(mesh); + } + } + } + + register_volume_raycasters_for_picking(); +} + +void GLGizmoSlaSupports::process_mesh(SLAPrintObjectStep step, bool postpone_error_messages) +{ + wxGetApp().CallAfter([this, step, postpone_error_messages]() { + wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages); + }); +} + SlaGizmoHelpDialog::SlaGizmoHelpDialog() : wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index c4232ed48..6729d6eda 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -3,6 +3,7 @@ #include "GLGizmoBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" +#include "slic3r/GUI/3DScene.hpp" #include "libslic3r/SLA/SupportPoint.hpp" #include "libslic3r/ObjectID.hpp" @@ -22,7 +23,6 @@ enum class SLAGizmoEventType : unsigned char; class GLGizmoSlaSupports : public GLGizmoBase { private: - bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); static constexpr float RenderPointScale = 1.f; @@ -93,11 +93,17 @@ private: #else void render_points(const Selection& selection, bool picking = false); #endif // ENABLE_RAYCAST_PICKING + void render_volumes(); bool unsaved_changes() const; #if ENABLE_RAYCAST_PICKING - void set_sla_auxiliary_volumes_picking_state(bool state); - void update_raycasters_for_picking_transform(); + void register_point_raycasters_for_picking(); + void unregister_point_raycasters_for_picking(); + void register_volume_raycasters_for_picking(); + void unregister_volume_raycasters_for_picking(); + void update_point_raycasters_for_picking_transform(); #endif // ENABLE_RAYCAST_PICKING + void update_volumes(); + void process_mesh(SLAPrintObjectStep step, bool postpone_error_messages = false); bool m_lock_unique_islands = false; bool m_editing_mode = false; // Is editing mode active? @@ -113,12 +119,14 @@ private: #if ENABLE_RAYCAST_PICKING PickingModel m_sphere; PickingModel m_cone; - std::vector, std::shared_ptr>> m_raycasters; + std::vector, std::shared_ptr>> m_point_raycasters; + std::vector> m_volume_raycasters; #else GLModel m_cone; GLModel m_sphere; #endif // ENABLE_RAYCAST_PICKING - GLModel m_cylinder; + + GLVolumeCollection m_volumes; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 31fbb0f7a..d8a858bac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -59,13 +59,6 @@ InstancesHider* CommonGizmosDataPool::instances_hider() const return inst_hider->is_valid() ? inst_hider : nullptr; } -//HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const -//{ -// HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); -// assert(hol_mesh); -// return hol_mesh->is_valid() ? hol_mesh : nullptr; -//} - Raycaster* CommonGizmosDataPool::raycaster() const { Raycaster* rc = dynamic_cast(m_data.at(CommonGizmosDataID::Raycaster).get()); @@ -123,9 +116,8 @@ void SelectionInfo::on_update() if (selection.is_single_full_instance()) { m_model_object = selection.get_model()->objects[selection.get_object_idx()]; - if (m_model_object) - m_print_object = get_pool()->get_canvas()->sla_print()->get_object(m_model_object->id()); + m_print_object = get_pool()->get_canvas()->sla_print()->get_print_object_by_model_object_id(m_model_object->id()); m_z_shift = selection.get_first_volume()->get_sla_shift_z(); } @@ -154,8 +146,10 @@ void InstancesHider::on_update() if (mo && active_inst != -1) { canvas->toggle_model_objects_visibility(false); - canvas->toggle_model_objects_visibility(true, mo, active_inst); - canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); + if (!m_hide_full_scene) { + canvas->toggle_model_objects_visibility(true, mo, active_inst); + canvas->toggle_sla_auxiliaries_visibility(false, mo, active_inst); + } canvas->set_use_clipping_planes(true); // Some objects may be sinking, do not show whatever is below the bed. canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); @@ -188,9 +182,10 @@ void InstancesHider::on_release() m_clippers.clear(); } -void InstancesHider::show_supports(bool show) { - if (m_show_supports != show) { - m_show_supports = show; +void InstancesHider::set_hide_full_scene(bool hide) +{ + if (m_hide_full_scene != hide) { + m_hide_full_scene = hide; on_update(); } } @@ -255,81 +250,6 @@ void InstancesHider::render_cut() const -//void HollowedMesh::on_update() -//{ -// const ModelObject* mo = get_pool()->selection_info()->model_object(); -// bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; -// if (! mo || ! is_sla) -// return; - -// const GLCanvas3D* canvas = get_pool()->get_canvas(); -// const PrintObjects& print_objects = canvas->sla_print()->objects(); -// const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) -// ? print_objects[m_print_object_idx] -// : nullptr; - -// // Find the respective SLAPrintObject. -// if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { -// m_print_objects_count = print_objects.size(); -// m_print_object_idx = -1; -// for (const SLAPrintObject* po : print_objects) { -// ++m_print_object_idx; -// if (po->model_object()->id() == mo->id()) { -// print_object = po; -// break; -// } -// } -// } - -// // If there is a valid SLAPrintObject, check state of Hollowing step. -// if (print_object) { -// if (print_object->is_step_done(slaposDrillHoles) && !print_object->get_mesh_to_print().empty()) { -// size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; -// if (timestamp > m_old_hollowing_timestamp) { -// const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); -// if (! backend_mesh.empty()) { -// m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); -// Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); -// m_hollowed_mesh_transformed->transform(trafo_inv); -// m_drainholes = print_object->model_object()->sla_drain_holes; -// m_old_hollowing_timestamp = timestamp; - -//// indexed_triangle_set interior = print_object->hollowed_interior_mesh(); -//// its_flip_triangles(interior); -//// m_hollowed_interior_transformed = std::make_unique(std::move(interior)); -//// m_hollowed_interior_transformed->transform(trafo_inv); -// } -// else { -// m_hollowed_mesh_transformed.reset(nullptr); -// } -// } -// } -// else -// m_hollowed_mesh_transformed.reset(nullptr); -// } -//} - - -//void HollowedMesh::on_release() -//{ -// m_hollowed_mesh_transformed.reset(); -// m_old_hollowing_timestamp = 0; -// m_print_object_idx = -1; -//} - - -//const TriangleMesh* HollowedMesh::get_hollowed_mesh() const -//{ -// return m_hollowed_mesh_transformed.get(); -//} - -//const TriangleMesh* HollowedMesh::get_hollowed_interior() const -//{ -// return m_hollowed_interior_transformed.get(); -//} - - - void Raycaster::on_update() { @@ -347,21 +267,28 @@ void Raycaster::on_update() mvs = mo->volumes; std::vector meshes; - const std::vector& mvs = mo->volumes; -// if (mvs.size() == 1) { -// assert(mvs.front()->is_model_part()); -// const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); -// if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) -// meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); -// } - if (meshes.empty()) { - for (const ModelVolume* v : mvs) { - if (v->is_model_part()) - meshes.push_back(&v->mesh()); + bool force_raycaster_regeneration = false; + if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) { + // For sla printers we use the mesh generated by the backend + const SLAPrintObject* po = get_pool()->selection_info()->print_object(); + assert(po != nullptr); + m_sla_mesh_cache = po->get_mesh_to_print(); + if (!m_sla_mesh_cache.empty()) { + m_sla_mesh_cache.transform(po->trafo().inverse()); + meshes.emplace_back(&m_sla_mesh_cache); + force_raycaster_regeneration = true; } } - if (meshes != m_old_meshes) { + if (meshes.empty()) { + const std::vector& mvs = mo->volumes; + for (const ModelVolume* mv : mvs) { + if (mv->is_model_part()) + meshes.push_back(&mv->mesh()); + } + } + + if (force_raycaster_regeneration || meshes != m_old_meshes) { m_raycasters.clear(); for (const TriangleMesh* mesh : meshes) #if ENABLE_RAYCAST_PICKING @@ -399,25 +326,36 @@ void ObjectClipper::on_update() // which mesh should be cut? std::vector meshes; -// bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); -// if (has_hollowed) -// meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); + std::vector trafos; + bool force_clipper_regeneration = false; + if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) { + // For sla printers we use the mesh generated by the backend + const SLAPrintObject* po = get_pool()->selection_info()->print_object(); + assert(po != nullptr); + m_sla_mesh_cache = po->get_mesh_to_print(); + if (!m_sla_mesh_cache.empty()) { + m_sla_mesh_cache.transform(po->trafo().inverse()); + meshes.emplace_back(&m_sla_mesh_cache); + trafos.emplace_back(Geometry::Transformation()); + force_clipper_regeneration = true; + } + } - if (meshes.empty()) - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); + if (meshes.empty()) { + for (const ModelVolume* mv : mo->volumes) { + meshes.emplace_back(&mv->mesh()); + trafos.emplace_back(mv->get_transformation()); + } + } - if (meshes != m_old_meshes) { + if (force_clipper_regeneration || meshes != m_old_meshes) { m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_mesh(*mesh); + for (size_t i = 0; i < meshes.size(); ++i) { + m_clippers.emplace_back(new MeshClipper, trafos[i]); + m_clippers.back().first->set_mesh(*meshes[i]); } m_old_meshes = meshes; -// if (has_hollowed) -// m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); - m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); } @@ -437,46 +375,27 @@ void ObjectClipper::render_cut() const { if (m_clp_ratio == 0.) return; + const SelectionInfo* sel_info = get_pool()->selection_info(); - int sel_instance_idx = sel_info->get_active_instance(); - if (sel_instance_idx < 0) - return; - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - const Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; + const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation(); + for (auto& clipper : m_clippers) { + Geometry::Transformation trafo = inst_trafo * clipper.second; trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); - clipper->render_contour({ 1.f, 1.f, 1.f, 1.f}); -#else - glsafe(::glPushMatrix()); - glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); - clipper->render_cut(); - glsafe(::glColor3f(1.f, 1.f, 1.f)); - clipper->render_contour(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - ++clipper_id; + clipper.first->set_plane(*m_clp); + clipper.first->set_transformation(trafo); + clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); } } bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const { - return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const std::unique_ptr& cl) { return cl->is_projection_inside_cut(point); }); + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const auto& cl) { return cl.first->is_projection_inside_cut(point); }); } bool ObjectClipper::has_valid_contour() const { - return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const std::unique_ptr& cl) { return cl->has_valid_contour(); }); + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); }); } void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) @@ -514,13 +433,13 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou { m_hide_clipped = hide_clipped; for (auto& clipper : m_clippers) - clipper->set_behaviour(fill_cut, contour_width); + clipper.first->set_behaviour(fill_cut, contour_width); } void ObjectClipper::pass_mouse_click(const Vec3d& pt) { for (auto& clipper : m_clippers) - clipper->pass_mouse_click(pt); + clipper.first->pass_mouse_click(pt); } std::vector ObjectClipper::get_disabled_contours() const @@ -586,7 +505,6 @@ void SupportsClipper::render_cut() const { const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper(); if (ocl->get_position() == 0. - || ! get_pool()->instances_hider()->are_supports_shown() || ! m_clipper) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index d6c580123..aa9493d9d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -63,7 +63,6 @@ enum class CommonGizmosDataID { None = 0, SelectionInfo = 1 << 0, InstancesHider = 1 << 1, - HollowedMesh = 1 << 2, Raycaster = 1 << 3, ObjectClipper = 1 << 4, SupportsClipper = 1 << 5, @@ -187,8 +186,7 @@ public: CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } #endif // NDEBUG - void show_supports(bool show); - bool are_supports_shown() const { return m_show_supports; } + void set_hide_full_scene(bool hide); void render_cut() const; protected: @@ -196,42 +194,13 @@ protected: void on_release() override; private: - bool m_show_supports = false; + bool m_hide_full_scene{ false }; std::vector m_old_meshes; std::vector> m_clippers; }; -//class HollowedMesh : public CommonGizmosDataBase -//{ -//public: -// explicit HollowedMesh(CommonGizmosDataPool* cgdp) -// : CommonGizmosDataBase(cgdp) {} -//#ifndef NDEBUG -// CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } -//#endif // NDEBUG - -// const sla::DrainHoles &get_drainholes() const { return m_drainholes; } - -// const TriangleMesh* get_hollowed_mesh() const; -// const TriangleMesh* get_hollowed_interior() const; - -//protected: -// void on_update() override; -// void on_release() override; - -//private: -// std::unique_ptr m_hollowed_mesh_transformed; -// std::unique_ptr m_hollowed_interior_transformed; -// size_t m_old_hollowing_timestamp = 0; -// int m_print_object_idx = -1; -// int m_print_objects_count = 0; -// sla::DrainHoles m_drainholes; -//}; - - - class Raycaster : public CommonGizmosDataBase { public: @@ -251,6 +220,8 @@ protected: private: std::vector> m_raycasters; std::vector m_old_meshes; + // Used to store the sla mesh coming from the backend + TriangleMesh m_sla_mesh_cache; }; @@ -285,7 +256,9 @@ protected: private: std::vector m_old_meshes; - std::vector> m_clippers; + // Used to store the sla mesh coming from the backend + TriangleMesh m_sla_mesh_cache; + std::vector, Geometry::Transformation>> m_clippers; std::unique_ptr m_clp; double m_clp_ratio = 0.; double m_active_inst_bb_radius = 0.; diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp index f309d6eaa..9e726b7f4 100644 --- a/src/slic3r/GUI/SceneRaycaster.cpp +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -100,7 +100,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came }; #if ENABLE_RAYCAST_PICKING_DEBUG - m_last_hit.reset(); + const_cast*>(&m_last_hit)->reset(); #endif // ENABLE_RAYCAST_PICKING_DEBUG HitResult ret; @@ -142,7 +142,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came ret.raycaster_id = decode_id(ret.type, ret.raycaster_id); #if ENABLE_RAYCAST_PICKING_DEBUG - m_last_hit = ret; + *const_cast*>(&m_last_hit) = ret; #endif // ENABLE_RAYCAST_PICKING_DEBUG return ret; } From c4db736f6fedb645c5f649e8ec7bc066a6c7a127 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 4 Nov 2022 09:22:47 +0100 Subject: [PATCH 012/206] Gizmo Hollow and SLA support - Disable imgui dialog and scene input until the proper geometry is not loaded --- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 52 ++++++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 30 ++++++++--- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 4 +- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index d098d41f5..e8889b39d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -14,10 +14,11 @@ #include "libslic3r/Model.hpp" - namespace Slic3r { namespace GUI { +static const ColorRGBA DISABLED_COLOR = ColorRGBA::DARK_GRAY(); + GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { @@ -508,6 +509,7 @@ void GLGizmoHollow::delete_selected_points() bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event) { + if (!m_input_enabled) return true; if (mouse_event.Moving()) return false; if (use_grabbers(mouse_event)) return true; @@ -644,6 +646,16 @@ void GLGizmoHollow::update_hole_raycasters_for_picking_transform() } #endif // ENABLE_RAYCAST_PICKING +static int last_completed_step(const SLAPrint& sla) +{ + int step = -1; + for (int i = 0; i < (int)SLAPrintObjectStep::slaposCount; ++i) { + if (sla.is_step_done((SLAPrintObjectStep)i)) + ++step; + } + return step; +} + void GLGizmoHollow::update_volumes() { m_volumes.clear(); @@ -657,6 +669,8 @@ void GLGizmoHollow::update_volumes() if (po == nullptr) return; + m_input_enabled = false; + TriangleMesh backend_mesh = po->get_mesh_to_print(); if (!backend_mesh.empty()) { // The backend has generated a valid mesh. Use it @@ -666,8 +680,12 @@ void GLGizmoHollow::update_volumes() new_volume->model.init_from(backend_mesh); new_volume->set_instance_transformation(po->model_object()->instances[m_parent.get_selection().get_instance_idx()]->get_transformation()); new_volume->set_sla_shift_z(po->get_current_elevation()); - new_volume->selected = true; // to set the proper color new_volume->mesh_raycaster = std::make_unique(backend_mesh); + m_input_enabled = last_completed_step(*m_c->selection_info()->print_object()->print()) >= slaposAssembly; + if (m_input_enabled) + new_volume->selected = true; // to set the proper color + else + new_volume->set_color(DISABLED_COLOR); } if (m_volumes.volumes.empty()) { @@ -684,7 +702,7 @@ void GLGizmoHollow::update_volumes() new_volume->set_instance_transformation(v->get_instance_transformation()); new_volume->set_volume_transformation(v->get_volume_transformation()); new_volume->set_sla_shift_z(v->get_sla_shift_z()); - new_volume->selected = true; // to set the proper color + new_volume->set_color(DISABLED_COLOR); new_volume->mesh_raycaster = std::make_unique(mesh); } } @@ -784,6 +802,8 @@ RENDER_AGAIN: float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); window_width = std::max(window_width, button_preview_width); + m_imgui->disabled_begin(!m_input_enabled); + if (m_imgui->button(m_desc["preview"])) process_mesh(slaposDrillHoles); @@ -801,7 +821,10 @@ RENDER_AGAIN: } } - m_imgui->disabled_begin(! m_enable_hollowing); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(!m_input_enabled || !m_enable_hollowing); + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("offset")); ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); @@ -844,7 +867,7 @@ RENDER_AGAIN: mo->config.set("hollowing_min_thickness", m_offset_stash); mo->config.set("hollowing_quality", m_quality_stash); mo->config.set("hollowing_closing_distance", m_closing_d_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Hollowing parameter change")); } mo->config.set("hollowing_min_thickness", offset); mo->config.set("hollowing_quality", quality); @@ -867,12 +890,15 @@ RENDER_AGAIN: if (m_new_hole_radius * 2.f > diameter_upper_cap) m_new_hole_radius = diameter_upper_cap / 2.f; ImGui::AlignTextToFramePadding(); + + m_imgui->disabled_begin(!m_input_enabled); + m_imgui->text(m_desc.at("hole_diameter")); ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); ImGui::PushItemWidth(window_width - diameter_slider_left); - float diam = 2.f * m_new_hole_radius; m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false); + // Let's clamp the value (which could have been entered by keyboard) to a larger range // than the slider. This allows entering off-scale values and still protects against //complete non-sense. @@ -883,9 +909,13 @@ RENDER_AGAIN: bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit; ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["hole_depth"]); ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); + + m_imgui->disabled_end(); + // Same as above: m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f); @@ -921,24 +951,24 @@ RENDER_AGAIN: break; } } - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change drainage hole diameter"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change drainage hole diameter")); m_new_hole_radius = backup_rad; m_new_hole_height = backup_hei; mo->sla_drain_holes = new_holes; } } - m_imgui->disabled_begin(m_selection_empty); + m_imgui->disabled_begin(!m_input_enabled || m_selection_empty); remove_selected = m_imgui->button(m_desc.at("remove_selected")); m_imgui->disabled_end(); - m_imgui->disabled_begin(mo->sla_drain_holes.empty()); + m_imgui->disabled_begin(!m_input_enabled || mo->sla_drain_holes.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); // Following is rendered in both editing and non-editing mode: - // m_imgui->text(""); ImGui::Separator(); + m_imgui->disabled_begin(!m_input_enabled); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("clipping_of_view")); @@ -957,6 +987,8 @@ RENDER_AGAIN: if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); + m_imgui->disabled_end(); + m_imgui->end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index e7a324a1e..96f8c6570 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -81,6 +81,7 @@ private: #endif // ENABLE_RAYCAST_PICKING GLVolumeCollection m_volumes; + bool m_input_enabled{ false }; float m_new_hole_radius = 2.f; // Size of a new hole. float m_new_hole_height = 6.f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index cc45a8e73..291a8323a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -30,6 +30,8 @@ static const double CONE_HEIGHT = 0.75; namespace Slic3r { namespace GUI { +static const ColorRGBA DISABLED_COLOR = ColorRGBA::DARK_GRAY(); + GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) {} @@ -748,8 +750,7 @@ RENDER_AGAIN: float win_h = ImGui::GetWindowHeight(); y = std::min(y, bottom_limit - win_h); ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if ((last_h != win_h) || (last_y != y)) - { + if (last_h != win_h || last_y != y) { // ask canvas for another frame to render the window in the correct position m_imgui->set_requires_extra_frame(); if (last_h != win_h) @@ -780,6 +781,7 @@ RENDER_AGAIN: if (m_new_point_head_diameter > diameter_upper_cap) m_new_point_head_diameter = diameter_upper_cap; ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("head_diameter")); ImGui::SameLine(diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left); @@ -840,6 +842,8 @@ RENDER_AGAIN: } } else { // not in editing mode: + m_imgui->disabled_begin(!m_input_enabled); + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("minimal_distance")); ImGui::SameLine(settings_sliders_left); @@ -889,7 +893,9 @@ RENDER_AGAIN: if (m_imgui->button(m_desc.at("manual_editing"))) switch_to_editing_mode(); - m_imgui->disabled_begin(m_normal_cache.empty()); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(!m_input_enabled || m_normal_cache.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); @@ -902,6 +908,7 @@ RENDER_AGAIN: // Following is rendered in both editing and non-editing mode: + m_imgui->disabled_begin(!m_input_enabled); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); @@ -921,7 +928,6 @@ RENDER_AGAIN: if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); - if (m_imgui->button("?")) { wxGetApp().CallAfter([]() { SlaGizmoHelpDialog help_dlg; @@ -929,6 +935,8 @@ RENDER_AGAIN: }); } + m_imgui->disabled_end(); + m_imgui->end(); if (remove_selected || remove_all) { @@ -1221,7 +1229,9 @@ void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) cons }); } -bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){ +bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event) +{ + if (!m_input_enabled) return true; if (mouse_event.Moving()) return false; if (!mouse_event.ShiftDown() && !mouse_event.AltDown() && use_grabbers(mouse_event)) return true; @@ -1445,6 +1455,8 @@ void GLGizmoSlaSupports::update_volumes() if (po == nullptr) return; + m_input_enabled = false; + TriangleMesh backend_mesh = po->get_mesh_to_print(); if (!backend_mesh.empty()) { // The backend has generated a valid mesh. Use it @@ -1454,8 +1466,12 @@ void GLGizmoSlaSupports::update_volumes() new_volume->model.init_from(backend_mesh); new_volume->set_instance_transformation(po->model_object()->instances[m_parent.get_selection().get_instance_idx()]->get_transformation()); new_volume->set_sla_shift_z(po->get_current_elevation()); - new_volume->selected = true; // to set the proper color new_volume->mesh_raycaster = std::make_unique(backend_mesh); + m_input_enabled = last_completed_step(*m_c->selection_info()->print_object()->print()) >= slaposDrillHoles; + if (m_input_enabled) + new_volume->selected = true; // to set the proper color + else + new_volume->set_color(DISABLED_COLOR); } if (m_volumes.volumes.empty()) { @@ -1472,7 +1488,7 @@ void GLGizmoSlaSupports::update_volumes() new_volume->set_instance_transformation(v->get_instance_transformation()); new_volume->set_volume_transformation(v->get_volume_transformation()); new_volume->set_sla_shift_z(v->get_sla_shift_z()); - new_volume->selected = true; // to set the proper color + new_volume->set_color(DISABLED_COLOR); new_volume->mesh_raycaster = std::make_unique(mesh); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 6729d6eda..bc5eede66 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -127,6 +127,7 @@ private: #endif // ENABLE_RAYCAST_PICKING GLVolumeCollection m_volumes; + bool m_input_enabled{ false }; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. @@ -161,8 +162,7 @@ private: protected: void on_set_state() override; - void on_set_hover_id() override - { + void on_set_hover_id() override { if (! m_editing_mode || (int)m_editing_cache.size() <= m_hover_id) m_hover_id = -1; } From 28ffdcc391ce1902eea0a553a7bfeb266ef8c410 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Nov 2022 12:46:46 +0100 Subject: [PATCH 013/206] Disable CGAL booleans for now in sla pipeline Does not work yet as expected. --- src/libslic3r/SLAPrintSteps.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index a5873155c..4cf420a29 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -158,16 +158,22 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) { - MeshBoolean::cgal::CGALMeshPtr cgalptr; + // TODO: enable when this works reliably. Currently, perform_csgmesh_booleans + // can generate incorrect result despite not throwing any exception. +// MeshBoolean::cgal::CGALMeshPtr cgalptr; - try { - cgalptr = csg::perform_csgmesh_booleans(range(po.m_mesh_to_slice)); - } catch(...) {} +// try { +// cgalptr = csg::perform_csgmesh_booleans(range(po.m_mesh_to_slice)); +// } catch(...) { +// cgalptr = nullptr; +// } - if (cgalptr) { - po.m_preview_meshes[step] = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalptr); - } else - po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; +// if (cgalptr) { +// po.m_preview_meshes[step] = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalptr); +// } else +// po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; + + po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; for (size_t i = size_t(step) + 1; i < slaposCount; ++i) { From c28a00ae0413afcf9b42833f1759d870b9462006 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Nov 2022 13:34:31 +0100 Subject: [PATCH 014/206] Holes are were not drilled in the right orientation. This change seemingly fixes the issue. --- src/libslic3r/SLA/Hollowing.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 974e52563..a35ba511b 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -118,9 +118,9 @@ indexed_triangle_set DrainHole::to_mesh() const { auto r = double(radius); auto h = double(height); - indexed_triangle_set hole = sla::cylinder(r, h, steps); + indexed_triangle_set hole = its_make_cylinder(r, h); //sla::cylinder(r, h, steps); Eigen::Quaternionf q; - q.setFromTwoVectors(Vec3f{0.f, 0.f, 1.f}, normal); + q.setFromTwoVectors(Vec3f::UnitZ(), normal); for(auto& p : hole.vertices) p = q * p + pos; return hole; @@ -692,24 +692,23 @@ DrainHoles transformed_drainhole_points(const ModelObject &mo, const Transform3d &trafo) { auto pts = mo.sla_drain_holes; - const Transform3d& vol_trafo = mo.volumes.front()->get_transformation().get_matrix(); - const Geometry::Transformation trans(trafo * vol_trafo); - const Transform3f& tr = trans.get_matrix().cast(); - const Vec3f sc = trans.get_scaling_factor().cast(); +// const Transform3d& vol_trafo = mo.volumes.front()->get_transformation().get_matrix(); + const Geometry::Transformation trans(trafo /** vol_trafo*/); + const Transform3d& tr = trans.get_matrix(); for (sla::DrainHole &hl : pts) { - hl.pos = tr * hl.pos; - hl.normal = tr * hl.normal - tr.translation(); + Vec3d pos = hl.pos.cast(); + Vec3d nrm = hl.normal.cast(); - // The normal scales as a covector (and we must also - // undo the damage already done). - hl.normal = Vec3f(hl.normal(0)/(sc(0)*sc(0)), - hl.normal(1)/(sc(1)*sc(1)), - hl.normal(2)/(sc(2)*sc(2))); + pos = tr * pos; + nrm = tr * nrm - tr.translation(); // Now shift the hole a bit above the object and make it deeper to // compensate for it. This is to avoid problems when the hole is placed // on (nearly) flat surface. - hl.pos -= hl.normal.normalized() * sla::HoleStickOutLength; + pos -= nrm.normalized() * sla::HoleStickOutLength; + + hl.pos = pos.cast(); + hl.normal = nrm.cast(); hl.height += sla::HoleStickOutLength; } From 602c48a116afd5901c089367c4c42d15af2faf8d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 4 Nov 2022 12:44:54 +0100 Subject: [PATCH 015/206] Added new base class for SLA gizmos to remove duplicated code --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 170 ++---------------- src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp | 18 +- src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp | 171 ++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp | 59 ++++++ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 179 ++----------------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 18 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 10 -- src/slic3r/GUI/Plater.hpp | 2 - 10 files changed, 259 insertions(+), 372 deletions(-) create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4fa51c486..ac103615a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -39,6 +39,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmosCommon.hpp GUI/Gizmos/GLGizmoBase.cpp GUI/Gizmos/GLGizmoBase.hpp + GUI/Gizmos/GLGizmoSlaBase.cpp + GUI/Gizmos/GLGizmoSlaBase.hpp GUI/Gizmos/GLGizmoEmboss.cpp GUI/Gizmos/GLGizmoEmboss.hpp GUI/Gizmos/GLGizmoMove.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index e8889b39d..633e61e56 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -1,6 +1,6 @@ +#include "libslic3r/libslic3r.h" #include "GLGizmoHollow.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include @@ -17,10 +17,8 @@ namespace Slic3r { namespace GUI { -static const ColorRGBA DISABLED_COLOR = ColorRGBA::DARK_GRAY(); - GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) + : GLGizmoSlaBase(parent, icon_filename, sprite_id, slaposAssembly) { } @@ -58,7 +56,7 @@ void GLGizmoHollow::data_changed() const SLAPrintObject* po = m_c->selection_info()->print_object(); if (po != nullptr && po->get_mesh_to_print().empty()) - process_mesh(slaposAssembly); + reslice_until_step(slaposAssembly); update_volumes(); @@ -265,25 +263,6 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) #endif // !ENABLE_LEGACY_OPENGL_REMOVAL } -void GLGizmoHollow::render_volumes() -{ - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_clip"); - if (shader == nullptr) - return; - - shader->start_using(); - shader->set_uniform("emission_factor", 0.0f); - const Camera& camera = wxGetApp().plater()->get_camera(); - - ClippingPlane clipping_plane = (m_c->object_clipper()->get_position() == 0.0) ? ClippingPlane::ClipsNothing() : *m_c->object_clipper()->get_clipping_plane(); - clipping_plane.set_normal(-clipping_plane.get_normal()); - m_volumes.set_clipping_plane(clipping_plane.get_data()); - - m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix()); - shader->stop_using(); - -} - bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const { if (m_c->object_clipper()->get_position() == 0.) @@ -299,34 +278,6 @@ bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); } - - -// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal -// Return false if no intersection was found, true otherwise. -bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (m_c->raycaster()->raycasters().size() != 1) - return false; - if (! m_c->raycaster()->raycaster()) - return false; - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - m_volumes.volumes.front()->world_matrix(), - wxGetApp().plater()->get_camera(), - hit, - normal, - m_c->object_clipper()->get_position() != 0.0 ? m_c->object_clipper()->get_clipping_plane() : nullptr)) { - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - return false; -} - // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. // The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is // aware that the event was reacted to and stops trying to make different sense of it. If the gizmo @@ -509,7 +460,7 @@ void GLGizmoHollow::delete_selected_points() bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event) { - if (!m_input_enabled) return true; + if (!is_input_enabled()) return true; if (mouse_event.Moving()) return false; if (use_grabbers(mouse_event)) return true; @@ -572,13 +523,6 @@ bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoHollow::process_mesh(SLAPrintObjectStep step, bool postpone_error_messages) -{ - wxGetApp().CallAfter([this, step, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - #if ENABLE_RAYCAST_PICKING void GLGizmoHollow::register_hole_raycasters_for_picking() { @@ -604,22 +548,6 @@ void GLGizmoHollow::unregister_hole_raycasters_for_picking() m_hole_raycasters.clear(); } -void GLGizmoHollow::register_volume_raycasters_for_picking() -{ - for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { - const GLVolume* v = m_volumes.volumes[i]; - m_volume_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i, *v->mesh_raycaster, v->world_matrix())); - } -} - -void GLGizmoHollow::unregister_volume_raycasters_for_picking() -{ - for (size_t i = 0; i < m_volume_raycasters.size(); ++i) { - m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i); - } - m_volume_raycasters.clear(); -} - void GLGizmoHollow::update_hole_raycasters_for_picking_transform() { const CommonGizmosDataObjects::SelectionInfo* info = m_c->selection_info(); @@ -646,71 +574,6 @@ void GLGizmoHollow::update_hole_raycasters_for_picking_transform() } #endif // ENABLE_RAYCAST_PICKING -static int last_completed_step(const SLAPrint& sla) -{ - int step = -1; - for (int i = 0; i < (int)SLAPrintObjectStep::slaposCount; ++i) { - if (sla.is_step_done((SLAPrintObjectStep)i)) - ++step; - } - return step; -} - -void GLGizmoHollow::update_volumes() -{ - m_volumes.clear(); - unregister_volume_raycasters_for_picking(); - - const ModelObject* mo = m_c->selection_info()->model_object(); - if (mo == nullptr) - return; - - const SLAPrintObject* po = m_c->selection_info()->print_object(); - if (po == nullptr) - return; - - m_input_enabled = false; - - TriangleMesh backend_mesh = po->get_mesh_to_print(); - if (!backend_mesh.empty()) { - // The backend has generated a valid mesh. Use it - backend_mesh.transform(po->trafo().inverse()); - m_volumes.volumes.emplace_back(new GLVolume()); - GLVolume* new_volume = m_volumes.volumes.back(); - new_volume->model.init_from(backend_mesh); - new_volume->set_instance_transformation(po->model_object()->instances[m_parent.get_selection().get_instance_idx()]->get_transformation()); - new_volume->set_sla_shift_z(po->get_current_elevation()); - new_volume->mesh_raycaster = std::make_unique(backend_mesh); - m_input_enabled = last_completed_step(*m_c->selection_info()->print_object()->print()) >= slaposAssembly; - if (m_input_enabled) - new_volume->selected = true; // to set the proper color - else - new_volume->set_color(DISABLED_COLOR); - } - - if (m_volumes.volumes.empty()) { - // No valid mesh found in the backend. Use the selection to duplicate the volumes - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int idx : idxs) { - const GLVolume* v = selection.get_volume(idx); - if (!v->is_modifier) { - m_volumes.volumes.emplace_back(new GLVolume()); - GLVolume* new_volume = m_volumes.volumes.back(); - const TriangleMesh& mesh = mo->volumes[v->volume_idx()]->mesh(); - new_volume->model.init_from(mesh); - new_volume->set_instance_transformation(v->get_instance_transformation()); - new_volume->set_volume_transformation(v->get_volume_transformation()); - new_volume->set_sla_shift_z(v->get_sla_shift_z()); - new_volume->set_color(DISABLED_COLOR); - new_volume->mesh_raycaster = std::make_unique(mesh); - } - } - } - - register_volume_raycasters_for_picking(); -} - std::vector> GLGizmoHollow::get_config_options(const std::vector& keys) const { @@ -802,10 +665,10 @@ RENDER_AGAIN: float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); window_width = std::max(window_width, button_preview_width); - m_imgui->disabled_begin(!m_input_enabled); + m_imgui->disabled_begin(!is_input_enabled()); if (m_imgui->button(m_desc["preview"])) - process_mesh(slaposDrillHoles); + reslice_until_step(slaposDrillHoles); bool config_changed = false; @@ -823,7 +686,7 @@ RENDER_AGAIN: m_imgui->disabled_end(); - m_imgui->disabled_begin(!m_input_enabled || !m_enable_hollowing); + m_imgui->disabled_begin(!is_input_enabled() || !m_enable_hollowing); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("offset")); @@ -891,7 +754,7 @@ RENDER_AGAIN: m_new_hole_radius = diameter_upper_cap / 2.f; ImGui::AlignTextToFramePadding(); - m_imgui->disabled_begin(!m_input_enabled); + m_imgui->disabled_begin(!is_input_enabled()); m_imgui->text(m_desc.at("hole_diameter")); ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); @@ -958,17 +821,17 @@ RENDER_AGAIN: } } - m_imgui->disabled_begin(!m_input_enabled || m_selection_empty); + m_imgui->disabled_begin(!is_input_enabled() || m_selection_empty); remove_selected = m_imgui->button(m_desc.at("remove_selected")); m_imgui->disabled_end(); - m_imgui->disabled_begin(!m_input_enabled || mo->sla_drain_holes.empty()); + m_imgui->disabled_begin(!is_input_enabled() || mo->sla_drain_holes.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); // Following is rendered in both editing and non-editing mode: ImGui::Separator(); - m_imgui->disabled_begin(!m_input_enabled); + m_imgui->disabled_begin(!is_input_enabled()); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("clipping_of_view")); @@ -1043,17 +906,6 @@ std::string GLGizmoHollow::on_get_name() const return _u8L("Hollow and drill"); } - -CommonGizmosDataID GLGizmoHollow::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::ObjectClipper)); -} - - void GLGizmoHollow::on_set_state() { if (m_state == m_old_state) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 96f8c6570..c1664b458 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -1,9 +1,8 @@ #ifndef slic3r_GLGizmoHollow_hpp_ #define slic3r_GLGizmoHollow_hpp_ -#include "GLGizmoBase.hpp" +#include "GLGizmoSlaBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" -#include "slic3r/GUI/3DScene.hpp" #include #include @@ -21,11 +20,8 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class Selection; -class GLGizmoHollow : public GLGizmoBase +class GLGizmoHollow : public GLGizmoSlaBase { -private: - bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); - public: GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); void data_changed() override; @@ -59,30 +55,21 @@ private: #else void render_points(const Selection& selection, bool picking = false); #endif // ENABLE_RAYCAST_PICKING - void render_volumes(); - void process_mesh(SLAPrintObjectStep step, bool postpone_error_messages = false); #if ENABLE_RAYCAST_PICKING void register_hole_raycasters_for_picking(); void unregister_hole_raycasters_for_picking(); - void register_volume_raycasters_for_picking(); - void unregister_volume_raycasters_for_picking(); void update_hole_raycasters_for_picking_transform(); #endif // ENABLE_RAYCAST_PICKING - void update_volumes(); ObjectID m_old_mo_id = -1; #if ENABLE_RAYCAST_PICKING PickingModel m_cylinder; std::vector> m_hole_raycasters; - std::vector> m_volume_raycasters; #else GLModel m_cylinder; #endif // ENABLE_RAYCAST_PICKING - GLVolumeCollection m_volumes; - bool m_input_enabled{ false }; - float m_new_hole_radius = 2.f; // Size of a new hole. float m_new_hole_height = 6.f; mutable std::vector m_selected; // which holes are currently selected @@ -129,7 +116,6 @@ protected: void on_stop_dragging() override; void on_dragging(const UpdateData &data) override; void on_render_input_window(float x, float y, float bottom_limit) override; - virtual CommonGizmosDataID on_get_requirements() const override; std::string on_get_name() const override; bool on_is_activable() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp new file mode 100644 index 000000000..7db24bb11 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp @@ -0,0 +1,171 @@ +#include "libslic3r/libslic3r.h" +#include "GLGizmoSlaBase.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +namespace Slic3r { +namespace GUI { + +static const ColorRGBA DISABLED_COLOR = ColorRGBA::DARK_GRAY(); +#if ENABLE_RAYCAST_PICKING +static const int VOLUME_RAYCASTERS_BASE_ID = (int)SceneRaycaster::EIdBase::Gizmo; +#endif // ENABLE_RAYCAST_PICKING + +GLGizmoSlaBase::GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, SLAPrintObjectStep min_step) +: GLGizmoBase(parent, icon_filename, sprite_id) +, m_min_sla_print_object_step((int)min_step) +{} + +void GLGizmoSlaBase::reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages) +{ + wxGetApp().CallAfter([this, step, postpone_error_messages]() { + wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages); + }); +} + +CommonGizmosDataID GLGizmoSlaBase::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::ObjectClipper)); +} + +void GLGizmoSlaBase::update_volumes() +{ + m_volumes.clear(); + unregister_volume_raycasters_for_picking(); + + const ModelObject* mo = m_c->selection_info()->model_object(); + if (mo == nullptr) + return; + + const SLAPrintObject* po = m_c->selection_info()->print_object(); + if (po == nullptr) + return; + + m_input_enabled = false; + + TriangleMesh backend_mesh = po->get_mesh_to_print(); + if (!backend_mesh.empty()) { + // The backend has generated a valid mesh. Use it + backend_mesh.transform(po->trafo().inverse()); + m_volumes.volumes.emplace_back(new GLVolume()); + GLVolume* new_volume = m_volumes.volumes.back(); + new_volume->model.init_from(backend_mesh); + new_volume->set_instance_transformation(po->model_object()->instances[m_parent.get_selection().get_instance_idx()]->get_transformation()); + new_volume->set_sla_shift_z(po->get_current_elevation()); + new_volume->mesh_raycaster = std::make_unique(backend_mesh); + m_input_enabled = last_completed_step(*m_c->selection_info()->print_object()->print()) >= m_min_sla_print_object_step; + if (m_input_enabled) + new_volume->selected = true; // to set the proper color + else + new_volume->set_color(DISABLED_COLOR); + } + + if (m_volumes.volumes.empty()) { + // No valid mesh found in the backend. Use the selection to duplicate the volumes + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int idx : idxs) { + const GLVolume* v = selection.get_volume(idx); + if (!v->is_modifier) { + m_volumes.volumes.emplace_back(new GLVolume()); + GLVolume* new_volume = m_volumes.volumes.back(); + const TriangleMesh& mesh = mo->volumes[v->volume_idx()]->mesh(); + new_volume->model.init_from(mesh); + new_volume->set_instance_transformation(v->get_instance_transformation()); + new_volume->set_volume_transformation(v->get_volume_transformation()); + new_volume->set_sla_shift_z(v->get_sla_shift_z()); + new_volume->set_color(DISABLED_COLOR); + new_volume->mesh_raycaster = std::make_unique(mesh); + } + } + } + +#if ENABLE_RAYCAST_PICKING + register_volume_raycasters_for_picking(); +#endif // ENABLE_RAYCAST_PICKING +} + +void GLGizmoSlaBase::render_volumes() +{ + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_clip"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("emission_factor", 0.0f); + const Camera& camera = wxGetApp().plater()->get_camera(); + + ClippingPlane clipping_plane = (m_c->object_clipper()->get_position() == 0.0) ? ClippingPlane::ClipsNothing() : *m_c->object_clipper()->get_clipping_plane(); + clipping_plane.set_normal(-clipping_plane.get_normal()); + m_volumes.set_clipping_plane(clipping_plane.get_data()); + + m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix()); + shader->stop_using(); + +} + +#if ENABLE_RAYCAST_PICKING +void GLGizmoSlaBase::register_volume_raycasters_for_picking() +{ + for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { + const GLVolume* v = m_volumes.volumes[i]; + m_volume_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, VOLUME_RAYCASTERS_BASE_ID + (int)i, *v->mesh_raycaster, v->world_matrix())); + } +} + +void GLGizmoSlaBase::unregister_volume_raycasters_for_picking() +{ + for (size_t i = 0; i < m_volume_raycasters.size(); ++i) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, VOLUME_RAYCASTERS_BASE_ID + (int)i); + } + m_volume_raycasters.clear(); +} +#endif // ENABLE_RAYCAST_PICKING + +int GLGizmoSlaBase::last_completed_step(const SLAPrint& sla) +{ + int step = -1; + for (int i = 0; i < (int)SLAPrintObjectStep::slaposCount; ++i) { + if (sla.is_step_done((SLAPrintObjectStep)i)) + ++step; + } + return step; +} + +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoSlaBase::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) +{ + if (m_c->raycaster()->raycasters().size() != 1) + return false; + if (!m_c->raycaster()->raycaster()) + return false; + if (m_volumes.volumes.empty()) + return false; + + // The raycaster query + Vec3f hit; + Vec3f normal; + if (m_c->raycaster()->raycaster()->unproject_on_mesh( + mouse_pos, + m_volumes.volumes.front()->world_matrix(), + wxGetApp().plater()->get_camera(), + hit, + normal, + m_c->object_clipper()->get_position() != 0.0 ? m_c->object_clipper()->get_clipping_plane() : nullptr)) { + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); + return true; + } + return false; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp new file mode 100644 index 000000000..ca87d75ac --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp @@ -0,0 +1,59 @@ +#ifndef slic3r_GLGizmoSlaBase_hpp_ +#define slic3r_GLGizmoSlaBase_hpp_ + +#include "GLGizmoBase.hpp" +#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/SceneRaycaster.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/Point.hpp" + +#include +#include +#include + +namespace Slic3r { + +class SLAPrint; + +namespace GUI { + +class GLCanvas3D; + +class GLGizmoSlaBase : public GLGizmoBase +{ +public: + GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, SLAPrintObjectStep min_step); + + void reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages = false); + +protected: + virtual CommonGizmosDataID on_get_requirements() const override; + + void update_volumes(); + void render_volumes(); + +#if ENABLE_RAYCAST_PICKING + void register_volume_raycasters_for_picking(); + void unregister_volume_raycasters_for_picking(); +#endif // ENABLE_RAYCAST_PICKING + + bool is_input_enabled() const { return m_input_enabled; } + int get_min_sla_print_object_step() const { return m_min_sla_print_object_step; } + + static int last_completed_step(const SLAPrint& sla); + + bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); + +private: + GLVolumeCollection m_volumes; + bool m_input_enabled{ false }; + int m_min_sla_print_object_step{ -1 }; +#if ENABLE_RAYCAST_PICKING + std::vector> m_volume_raycasters; +#endif // ENABLE_RAYCAST_PICKING +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoSlaBase_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 291a8323a..2884d4969 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1,8 +1,6 @@ +#include "libslic3r/libslic3r.h" // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoSlaSupports.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -12,11 +10,9 @@ #include #include -#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/MsgDialog.hpp" #include "libslic3r/PresetBundle.hpp" @@ -30,10 +26,8 @@ static const double CONE_HEIGHT = 0.75; namespace Slic3r { namespace GUI { -static const ColorRGBA DISABLED_COLOR = ColorRGBA::DARK_GRAY(); - GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) + : GLGizmoSlaBase(parent, icon_filename, sprite_id, slaposDrillHoles) {} bool GLGizmoSlaSupports::on_init() @@ -62,16 +56,6 @@ bool GLGizmoSlaSupports::on_init() return true; } -static int last_completed_step(const SLAPrint& sla) -{ - int step = -1; - for (int i = 0; i < (int)SLAPrintObjectStep::slaposCount; ++i) { - if (sla.is_step_done((SLAPrintObjectStep)i)) - ++step; - } - return step; -} - void GLGizmoSlaSupports::data_changed() { if (! m_c->selection_info()) @@ -89,8 +73,9 @@ void GLGizmoSlaSupports::data_changed() if (mo) { m_c->instances_hider()->set_hide_full_scene(true); const SLAPrintObject* po = m_c->selection_info()->print_object(); - if (po != nullptr && last_completed_step(*po->print()) < (int)slaposDrillHoles) - process_mesh(slaposDrillHoles, false); + const int required_step = get_min_sla_print_object_step(); + if (po != nullptr && last_completed_step(*po->print()) < required_step) + reslice_until_step((SLAPrintObjectStep)required_step, false); update_volumes(); @@ -380,24 +365,6 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) #endif // !ENABLE_LEGACY_OPENGL_REMOVAL } -void GLGizmoSlaSupports::render_volumes() -{ - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_clip"); - if (shader == nullptr) - return; - - shader->start_using(); - shader->set_uniform("emission_factor", 0.0f); - const Camera& camera = wxGetApp().plater()->get_camera(); - - ClippingPlane clipping_plane = (m_c->object_clipper()->get_position() == 0.0) ? ClippingPlane::ClipsNothing() : *m_c->object_clipper()->get_clipping_plane(); - clipping_plane.set_normal(-clipping_plane.get_normal()); - m_volumes.set_clipping_plane(clipping_plane.get_data()); - - m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix()); - shader->stop_using(); -} - bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const { if (m_c->object_clipper()->get_position() == 0.) @@ -413,33 +380,6 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); } - - -// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal -// Return false if no intersection was found, true otherwise. -bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (!m_c->raycaster()->raycaster()) - return false; - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - m_volumes.volumes.front()->world_matrix(), - wxGetApp().plater()->get_camera(), - hit, - normal, - m_c->object_clipper()->get_position() != 0.0 ? m_c->object_clipper()->get_clipping_plane() : nullptr)) { - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - - return false; -} - // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. // The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is // aware that the event was reacted to and stops trying to make different sense of it. If the gizmo @@ -842,7 +782,7 @@ RENDER_AGAIN: } } else { // not in editing mode: - m_imgui->disabled_begin(!m_input_enabled); + m_imgui->disabled_begin(!is_input_enabled()); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("minimal_distance")); @@ -895,7 +835,7 @@ RENDER_AGAIN: m_imgui->disabled_end(); - m_imgui->disabled_begin(!m_input_enabled || m_normal_cache.empty()); + m_imgui->disabled_begin(!is_input_enabled() || m_normal_cache.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); @@ -908,7 +848,7 @@ RENDER_AGAIN: // Following is rendered in both editing and non-editing mode: - m_imgui->disabled_begin(!m_input_enabled); + m_imgui->disabled_begin(!is_input_enabled()); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); @@ -991,17 +931,6 @@ std::string GLGizmoSlaSupports::on_get_name() const return _u8L("SLA Support Points"); } -CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::ObjectClipper)); -} - - - void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) { wxGetApp().CallAfter([on_yes, on_no]() { @@ -1189,7 +1118,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() mo->sla_support_points.clear(); mo->sla_support_points = m_normal_cache; - reslice_SLA_supports(); + reslice_until_step(slaposPad); } } @@ -1221,17 +1150,9 @@ bool GLGizmoSlaSupports::has_backend_supports() const return false; } -void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const -{ - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_supports( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event) { - if (!m_input_enabled) return true; + if (!is_input_enabled()) return true; if (mouse_event.Moving()) return false; if (!mouse_event.ShiftDown() && !mouse_event.AltDown() && use_grabbers(mouse_event)) return true; @@ -1324,7 +1245,7 @@ void GLGizmoSlaSupports::auto_generate() if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); - wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); + wxGetApp().CallAfter([this]() { reslice_until_step(slaposPad); }); mo->sla_points_status = sla::PointsStatus::Generating; } } @@ -1395,22 +1316,6 @@ void GLGizmoSlaSupports::unregister_point_raycasters_for_picking() m_point_raycasters.clear(); } -void GLGizmoSlaSupports::register_volume_raycasters_for_picking() -{ - for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { - const GLVolume* v = m_volumes.volumes[i]; - m_volume_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i, *v->mesh_raycaster, v->world_matrix())); - } -} - -void GLGizmoSlaSupports::unregister_volume_raycasters_for_picking() -{ - for (size_t i = 0; i < m_volume_raycasters.size(); ++i) { - m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, (int)SceneRaycaster::EIdBase::Gizmo + (int)i); - } - m_volume_raycasters.clear(); -} - void GLGizmoSlaSupports::update_point_raycasters_for_picking_transform() { if (m_editing_cache.empty()) @@ -1442,68 +1347,6 @@ void GLGizmoSlaSupports::update_point_raycasters_for_picking_transform() } #endif // ENABLE_RAYCAST_PICKING -void GLGizmoSlaSupports::update_volumes() -{ - m_volumes.clear(); - unregister_volume_raycasters_for_picking(); - - const ModelObject* mo = m_c->selection_info()->model_object(); - if (mo == nullptr) - return; - - const SLAPrintObject* po = m_c->selection_info()->print_object(); - if (po == nullptr) - return; - - m_input_enabled = false; - - TriangleMesh backend_mesh = po->get_mesh_to_print(); - if (!backend_mesh.empty()) { - // The backend has generated a valid mesh. Use it - backend_mesh.transform(po->trafo().inverse()); - m_volumes.volumes.emplace_back(new GLVolume()); - GLVolume* new_volume = m_volumes.volumes.back(); - new_volume->model.init_from(backend_mesh); - new_volume->set_instance_transformation(po->model_object()->instances[m_parent.get_selection().get_instance_idx()]->get_transformation()); - new_volume->set_sla_shift_z(po->get_current_elevation()); - new_volume->mesh_raycaster = std::make_unique(backend_mesh); - m_input_enabled = last_completed_step(*m_c->selection_info()->print_object()->print()) >= slaposDrillHoles; - if (m_input_enabled) - new_volume->selected = true; // to set the proper color - else - new_volume->set_color(DISABLED_COLOR); - } - - if (m_volumes.volumes.empty()) { - // No valid mesh found in the backend. Use the selection to duplicate the volumes - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int idx : idxs) { - const GLVolume* v = selection.get_volume(idx); - if (!v->is_modifier) { - m_volumes.volumes.emplace_back(new GLVolume()); - GLVolume* new_volume = m_volumes.volumes.back(); - const TriangleMesh& mesh = mo->volumes[v->volume_idx()]->mesh(); - new_volume->model.init_from(mesh); - new_volume->set_instance_transformation(v->get_instance_transformation()); - new_volume->set_volume_transformation(v->get_volume_transformation()); - new_volume->set_sla_shift_z(v->get_sla_shift_z()); - new_volume->set_color(DISABLED_COLOR); - new_volume->mesh_raycaster = std::make_unique(mesh); - } - } - } - - register_volume_raycasters_for_picking(); -} - -void GLGizmoSlaSupports::process_mesh(SLAPrintObjectStep step, bool postpone_error_messages) -{ - wxGetApp().CallAfter([this, step, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - SlaGizmoHelpDialog::SlaGizmoHelpDialog() : wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index bc5eede66..c10d225da 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -1,9 +1,8 @@ #ifndef slic3r_GLGizmoSlaSupports_hpp_ #define slic3r_GLGizmoSlaSupports_hpp_ -#include "GLGizmoBase.hpp" +#include "GLGizmoSlaBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" -#include "slic3r/GUI/3DScene.hpp" #include "libslic3r/SLA/SupportPoint.hpp" #include "libslic3r/ObjectID.hpp" @@ -20,11 +19,9 @@ namespace GUI { class Selection; enum class SLAGizmoEventType : unsigned char; -class GLGizmoSlaSupports : public GLGizmoBase +class GLGizmoSlaSupports : public GLGizmoSlaBase { private: - bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); - static constexpr float RenderPointScale = 1.f; class CacheEntry { @@ -65,7 +62,6 @@ public: bool is_in_editing_mode() const override { return m_editing_mode; } bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); } bool has_backend_supports() const; - void reslice_SLA_supports(bool postpone_error_messages = false) const; bool wants_enter_leave_snapshots() const override { return true; } std::string get_gizmo_entering_text() const override { return _u8L("Entering SLA support points"); } @@ -93,17 +89,12 @@ private: #else void render_points(const Selection& selection, bool picking = false); #endif // ENABLE_RAYCAST_PICKING - void render_volumes(); bool unsaved_changes() const; #if ENABLE_RAYCAST_PICKING void register_point_raycasters_for_picking(); void unregister_point_raycasters_for_picking(); - void register_volume_raycasters_for_picking(); - void unregister_volume_raycasters_for_picking(); void update_point_raycasters_for_picking_transform(); #endif // ENABLE_RAYCAST_PICKING - void update_volumes(); - void process_mesh(SLAPrintObjectStep step, bool postpone_error_messages = false); bool m_lock_unique_islands = false; bool m_editing_mode = false; // Is editing mode active? @@ -120,15 +111,11 @@ private: PickingModel m_sphere; PickingModel m_cone; std::vector, std::shared_ptr>> m_point_raycasters; - std::vector> m_volume_raycasters; #else GLModel m_cone; GLModel m_sphere; #endif // ENABLE_RAYCAST_PICKING - GLVolumeCollection m_volumes; - bool m_input_enabled{ false }; - // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; @@ -174,7 +161,6 @@ protected: std::string on_get_name() const override; bool on_is_activable() const override; bool on_is_selectable() const override; - virtual CommonGizmosDataID on_get_requirements() const override; void on_load(cereal::BinaryInputArchive& ar) override; void on_save(cereal::BinaryOutputArchive& ar) const override; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 37d1d3de4..ad0e268f6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -683,7 +683,7 @@ void GLGizmosManager::update_after_undo_redo(const UndoRedo::Snapshot& snapshot) m_serializing = false; if (m_current == SlaSupports && snapshot.snapshot_data.flags & UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS) - dynamic_cast(m_gizmos[SlaSupports].get())->reslice_SLA_supports(true); + dynamic_cast(m_gizmos[SlaSupports].get())->reslice_until_step(slaposPad, true); } #if ENABLE_LEGACY_OPENGL_REMOVAL diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d57963e25..9db47ffb0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6370,16 +6370,6 @@ void Plater::reslice() p->preview->reload_print(!clean_gcode_toolpaths); } -void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages) -{ - reslice_SLA_until_step(slaposPad, object, postpone_error_messages); -} - -void Plater::reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages) -{ - reslice_SLA_until_step(slaposDrillHoles, object, postpone_error_messages); -} - void Plater::reslice_until_step_inner(int step, const ModelObject &object, bool postpone_error_messages) { //FIXME Don't reslice if export of G-code or sending to OctoPrint is running. diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 77a401f3f..5818ecf56 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -266,8 +266,6 @@ public: void export_toolpaths_to_obj() const; void reslice(); void reslice_FFF_until_step(PrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false); - void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false); - void reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages = false); void reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false); void clear_before_change_mesh(int obj_idx); From 75a25b5ad7214e90e17838ae0e9215f124ece85d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 24 Nov 2022 12:26:22 +0100 Subject: [PATCH 016/206] Add missing includes to fix compilation on linux --- src/libslic3r/SLA/Hollowing.cpp | 2 ++ src/libslic3r/SlicesToTriangleMesh.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index a35ba511b..2d3606c47 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp index 3ea64e878..9e290d472 100644 --- a/src/libslic3r/SlicesToTriangleMesh.cpp +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -9,6 +9,8 @@ #include #include +#include + namespace Slic3r { // Same as walls() but with identical higher and lower polygons. From 7831b6e6ccf31cf886e62a664479ec2e4cff3116 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 12:51:00 +0100 Subject: [PATCH 017/206] Fix compilation --- src/libslic3r/SLAPrintSteps.cpp | 4 ++-- src/slic3r/GUI/GUI_Factories.cpp | 4 ++-- src/slic3r/GUI/GUI_Factories.hpp | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 4cf420a29..c8dcb9f3b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -423,7 +423,7 @@ static void filter_support_points_by_modifiers( !is_enforced && enf_idx < enforcers[idx].size(); ++enf_idx) { - if (enforcers[idx][enf_idx].contains_b(sp2d)) + if (enforcers[idx][enf_idx].contains(sp2d)) is_enforced = true; } } @@ -434,7 +434,7 @@ static void filter_support_points_by_modifiers( !is_blocked && blk_idx < blockers[idx].size(); ++blk_idx) { - if (blockers[idx][blk_idx].contains_b(sp2d)) + if (blockers[idx][blk_idx].contains(sp2d)) is_blocked = true; } } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 39cf44eb4..a75322028 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -164,12 +164,12 @@ static const constexpr std::array, 5> ADD_ }}; // Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important -const std::vector> MenuFactory::TEXT_VOLUME_ICONS { +static const constexpr std::array, 3> TEXT_VOLUME_ICONS {{ // menu_item Name menu_item bitmap name {L("Add text"), "add_text_part"}, // ~ModelVolumeType::MODEL_PART {L("Add negative text"), "add_text_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME {L("Add text modifier"), "add_text_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER -}; +}}; static Plater* plater() { diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index e418cf675..81798af9c 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -33,8 +33,6 @@ struct SettingsFactory class MenuFactory { public: - static const std::vector> ADD_VOLUME_MENU_ITEMS; - static const std::vector> TEXT_VOLUME_ICONS; static std::vector get_volume_bitmaps(); static std::vector get_text_volume_bitmaps(); From 1e2b4929debaceda386e2f78bf1a2fb06fb3935a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 14:40:22 +0100 Subject: [PATCH 018/206] Prevent crash with disabled supports and enabled pad --- src/libslic3r/SLAPrintSteps.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index c8dcb9f3b..f617c3e69 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -204,6 +204,9 @@ struct csg_inserter { void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po) { po.m_mesh_to_slice.clear(); + po.m_supportdata.reset(); + po.m_hollowing_data.reset(); + csg::model_to_csgmesh(*po.model_object(), po.trafo(), csg_inserter{po.m_mesh_to_slice, slaposAssembly}, csg::mpartsPositive | csg::mpartsNegative); @@ -599,6 +602,12 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // repeated) if(po.m_config.pad_enable.getBool()) { + if (!po.m_supportdata) + po.m_supportdata = + std::make_unique( + po.get_mesh_to_print() + ); + // Get the distilled pad configuration from the config // (Again, despite it was retrieved in the previous step. Note that // on a param change event, the previous step might not be executed From faf20a26503a6b30655ca520f77b8020fcea6f7a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 12 Dec 2022 15:37:36 +0100 Subject: [PATCH 019/206] Fixed crash while reloading scene when using sla printer with multipart objects --- src/slic3r/GUI/GLCanvas3D.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1cb1ca4f3..23e974a55 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1944,9 +1944,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed for (GLVolume* volume : m_volumes.volumes) if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) { - const SLAPrintObject *po = sla_print()->objects()[volume->object_idx()]; - float zoffs = po->get_current_elevation() / sla_print()->relative_correction().z(); - volume->set_sla_shift_z(zoffs); + const SLAPrintObject* po = sla_print()->get_print_object_by_model_object_id(volume->object_idx()); + if (po != nullptr) + volume->set_sla_shift_z(po->get_current_elevation() / sla_print()->relative_correction().z()); } } From 8511a17ad0cf1275d2b0506b0a78dc14cc84971c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 7 Dec 2022 13:29:41 +0100 Subject: [PATCH 020/206] Increase fidelity of openvdb previews and log duration of generating it --- src/libslic3r/SLAPrintSteps.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index f617c3e69..7989e9044 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -138,7 +138,7 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( bench.start(); // update preview mesh - double vscale = 1. / (2. * po.m_config.layer_height.getFloat()); + double vscale = 1. / po.m_config.layer_height.getFloat(); auto voxparams = csg::VoxelizeParams{} .voxel_scale(vscale) .exterior_bandwidth(1.f) @@ -150,7 +150,7 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( bench.stop(); - std::cout << "Preview gen took: " << bench.getElapsedSec() << std::endl; + BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << bench.getElapsedSec(); return m; } From a141a4c0bc9f4816dc34e584be34b23016a96bbe Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 7 Dec 2022 16:35:07 +0100 Subject: [PATCH 021/206] Improve cancellation of new sla backend --- src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 42 +++++--------------- src/libslic3r/OpenVDBUtils.cpp | 47 +++++++++++++++++------ src/libslic3r/OpenVDBUtils.hpp | 27 +++++++++++-- src/libslic3r/SLA/Hollowing.hpp | 24 +++++++++--- src/libslic3r/SLAPrint.cpp | 11 ++---- src/libslic3r/SLAPrint.hpp | 33 +++++++++++++--- src/libslic3r/SLAPrintSteps.cpp | 27 ++++++++----- src/libslic3r/SLAPrintSteps.hpp | 32 +++++++-------- 8 files changed, 151 insertions(+), 92 deletions(-) diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index 9d72e3ea8..003fc2ca0 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -1,52 +1,27 @@ #ifndef VOXELIZECSGMESH_HPP #define VOXELIZECSGMESH_HPP +#include + #include "CSGMesh.hpp" #include "libslic3r/OpenVDBUtils.hpp" namespace Slic3r { namespace csg { -class VoxelizeParams { - float m_voxel_scale = 1.f; - float m_exterior_bandwidth = 3.0f; - float m_interior_bandwidth = 3.0f; - -public: - float voxel_scale() const noexcept { return m_voxel_scale; } - float exterior_bandwidth() const noexcept { return m_exterior_bandwidth; } - float interior_bandwidth() const noexcept { return m_interior_bandwidth; } - - VoxelizeParams &voxel_scale(float val) noexcept - { - m_voxel_scale = val; - return *this; - } - VoxelizeParams &exterior_bandwidth(float val) noexcept - { - m_exterior_bandwidth = val; - return *this; - } - VoxelizeParams &interior_bandwidth(float val) noexcept - { - m_interior_bandwidth = val; - return *this; - } -}; +using VoxelizeParams = MeshToGridParams; // This method can be overriden when a specific CSGPart type supports caching // of the voxel grid template -VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, const VoxelizeParams ¶ms) +VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params) { const indexed_triangle_set *its = csg::get_mesh(csgpart); VoxelGridPtr ret; + params.trafo(params.trafo() * csg::get_transform(csgpart)); + if (its) - ret = mesh_to_grid(*csg::get_mesh(csgpart), - csg::get_transform(csgpart), - params.voxel_scale(), - params.exterior_bandwidth(), - params.interior_bandwidth()); + ret = mesh_to_grid(*csg::get_mesh(csgpart), params); return ret; } @@ -58,6 +33,9 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, VoxelGridPtr ret; for (auto &csgpart : csgrange) { + if (params.statusfn() && params.statusfn()(-1)) + break; + VoxelGridPtr partgrid = get_voxelgrid(csgpart, params); if (!ret && get_operation(csgpart) == CSGType::Union) { diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 8ea110000..2a94d38e0 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -74,11 +74,18 @@ public: : its{m}, trafo{tr} {} }; +struct Interrupter +{ + std::function statusfn; + + void start(const char* name = nullptr) { (void)name; } + void end() {} + + inline bool wasInterrupted(int percent = -1) { return statusfn(percent); } +}; + VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, - const Transform3f &tr, - float voxel_scale, - float exteriorBandWidth, - float interiorBandWidth) + const MeshToGridParams ¶ms) { // Might not be needed but this is now proven to be working openvdb::initialize(); @@ -92,14 +99,22 @@ VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, meshparts.erase(it, meshparts.end()); - Transform3d trafo = tr.cast(); - trafo.prescale(voxel_scale); + Transform3d trafo = params.trafo().cast(); + trafo.prescale(params.voxel_scale()); + + Interrupter interrupter{params.statusfn()}; openvdb::FloatGrid::Ptr grid; for (auto &m : meshparts) { auto subgrid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{m, trafo}, {}, - exteriorBandWidth, interiorBandWidth); + interrupter, + TriangleMeshDataAdapter{m, trafo}, + openvdb::math::Transform{}, + params.exterior_bandwidth(), + params.interior_bandwidth()); + + if (interrupter.wasInterrupted()) + break; if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); @@ -107,16 +122,24 @@ VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, grid = std::move(subgrid); } + if (interrupter.wasInterrupted()) + return {}; + if (meshparts.empty()) { // Splitting failed, fall back to hollow the original mesh grid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{mesh, trafo}, {}, exteriorBandWidth, - interiorBandWidth); + interrupter, + TriangleMeshDataAdapter{mesh, trafo}, + openvdb::math::Transform{}, + params.exterior_bandwidth(), + params.interior_bandwidth()); } + if (interrupter.wasInterrupted()) + return {}; - grid->transform().preScale(1./voxel_scale); - grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); + grid->transform().preScale(1./params.voxel_scale()); + grid->insertMeta("voxel_scale", openvdb::FloatMetadata(params.voxel_scale())); VoxelGridPtr ret = make_voxelgrid(std::move(*grid)); diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index 959cd854d..cb857020f 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -23,6 +23,28 @@ float get_voxel_scale(const VoxelGrid &grid); VoxelGridPtr clone(const VoxelGrid &grid); +class MeshToGridParams { + Transform3f m_tr = Transform3f::Identity(); + float m_voxel_scale = 1.f; + float m_exteriorBandWidth = 3.0f; + float m_interiorBandWidth = 3.0f; + + std::function m_statusfn; + +public: + MeshToGridParams & trafo(const Transform3f &v) { m_tr = v; return *this; } + MeshToGridParams & voxel_scale(float v) { m_voxel_scale = v; return *this; } + MeshToGridParams & exterior_bandwidth(float v) { m_exteriorBandWidth = v; return *this; } + MeshToGridParams & interior_bandwidth(float v) { m_interiorBandWidth = v; return *this; } + MeshToGridParams & statusfn(std::function fn) { m_statusfn = fn; return *this; } + + const Transform3f& trafo() const noexcept { return m_tr; } + float voxel_scale() const noexcept { return m_voxel_scale; } + float exterior_bandwidth() const noexcept { return m_exteriorBandWidth; } + float interior_bandwidth() const noexcept { return m_interiorBandWidth; } + const std::function& statusfn() const noexcept { return m_statusfn; } +}; + // Here voxel_scale defines the scaling of voxels which affects the voxel count. // 1.0 value means a voxel for every unit cube. 2 means the model is scaled to // be 2x larger and the voxel count is increased by the increment in the scaled @@ -31,10 +53,7 @@ VoxelGridPtr clone(const VoxelGrid &grid); // The resulting grid will contain the voxel_scale in its metadata under the // "voxel_scale" key to be used in grid_to_mesh function. VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, - const Transform3f &tr = Transform3f::Identity(), - float voxel_scale = 1.f, - float exteriorBandWidth = 3.0f, - float interiorBandWidth = 3.0f); + const MeshToGridParams ¶ms = {}); indexed_triangle_set grid_to_mesh(const VoxelGrid &grid, double isovalue = 0.0, diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 6ff4660ba..bcc26981e 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -84,16 +84,24 @@ InteriorPtr generate_interior(const VoxelGrid &mesh, const JobController &ctl = {}); inline InteriorPtr generate_interior(const indexed_triangle_set &mesh, - const HollowingConfig &hc = {}, - const JobController &ctl = {}) + const HollowingConfig &hc = {}, + const JobController &ctl = {}) { auto voxel_scale = get_voxel_scale(its_volume(mesh), hc); - auto grid = mesh_to_grid(mesh, Transform3f::Identity(), voxel_scale, 1.f, 1.f); + auto statusfn = [&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); }; + auto grid = mesh_to_grid(mesh, MeshToGridParams{} + .voxel_scale(voxel_scale) + .exterior_bandwidth(1.f) + .interior_bandwidth(1.f) + .statusfn(statusfn)); + + if (!grid || (ctl.stopcondition && ctl.stopcondition())) + return {}; if (its_is_splittable(mesh)) grid = redistance_grid(*grid, 0.0f, 6.f / voxel_scale, 6.f / voxel_scale); - return generate_interior(*grid, hc, ctl); + return grid ? generate_interior(*grid, hc, ctl) : InteriorPtr{}; } template @@ -109,17 +117,21 @@ InteriorPtr generate_interior(const Range &csgparts, auto params = csg::VoxelizeParams{} .voxel_scale(get_voxel_scale(mesh_vol, hc)) .exterior_bandwidth(1.f) - .interior_bandwidth(1.f); + .interior_bandwidth(1.f) + .statusfn([&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); }); auto ptr = csg::voxelize_csgmesh(csgparts, params); + if (!ptr || (ctl.stopcondition && ctl.stopcondition())) + return {}; + if (csgparts.size() > 1 || its_is_splittable(*csg::get_mesh(*csgparts.begin()))) ptr = redistance_grid(*ptr, 0.0f, 6.f / params.voxel_scale(), 6.f / params.voxel_scale()); - return generate_interior(*ptr, hc, ctl); + return ptr ? generate_interior(*ptr, hc, ctl) : InteriorPtr{}; } // Will do the hollowing diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index b8000f384..fdb264b5e 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1160,19 +1160,16 @@ inline bool operator==(const VoxelizeParams &a, const VoxelizeParams &b) return h(a) == h(b); } -VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, const VoxelizeParams &p) +VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, VoxelizeParams p) { VoxelGridPtr &ret = part.gridcache[p]; if (!ret) { - ret = mesh_to_grid(*csg::get_mesh(part), - csg::get_transform(part), - p.voxel_scale(), - p.exterior_bandwidth(), - p.interior_bandwidth()); + p.trafo(csg::get_transform(part)); + ret = mesh_to_grid(*csg::get_mesh(part), p); } - return clone(*ret); + return ret ? clone(*ret) : nullptr; } } // namespace csg diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index ca74cb391..37aeb7b1c 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -13,6 +13,8 @@ #include "libslic3r/CSGMesh/CSGMesh.hpp" #include "libslic3r/OpenVDBUtils.hpp" +#include + namespace Slic3r { enum SLAPrintStep : unsigned int { @@ -51,11 +53,32 @@ namespace std { template<> struct hash { size_t operator() (const Slic3r::csg::VoxelizeParams &p) const { - std::string str = Slic3r::float_to_string_decimal_point(p.voxel_scale()); - str += Slic3r::float_to_string_decimal_point(p.exterior_bandwidth()); - str += Slic3r::float_to_string_decimal_point(p.interior_bandwidth()); + int64_t vs = Slic3r::scaled(p.voxel_scale()) >> 10; + int64_t eb = Slic3r::scaled(p.exterior_bandwidth()) >> 10; + int64_t ib = Slic3r::scaled(p.interior_bandwidth()) >> 10; - return std::hash{}(str); + size_t h = 0; + boost::hash_combine(h, vs); + boost::hash_combine(h, eb); + boost::hash_combine(h, ib); + + return h; + } +}; + +template<> struct equal_to { + size_t operator() (const Slic3r::csg::VoxelizeParams &p1, + const Slic3r::csg::VoxelizeParams &p2) const { + + int64_t vs1 = Slic3r::scaled(p1.voxel_scale()) >> 10; + int64_t eb1 = Slic3r::scaled(p1.exterior_bandwidth()) >> 10; + int64_t ib1 = Slic3r::scaled(p1.interior_bandwidth()) >> 10; + + int64_t vs2 = Slic3r::scaled(p2.voxel_scale()) >> 10; + int64_t eb2 = Slic3r::scaled(p2.exterior_bandwidth()) >> 10; + int64_t ib2 = Slic3r::scaled(p2.interior_bandwidth()) >> 10; + + return vs1 == vs2 && eb1 == eb2 && ib1 == ib2; } }; @@ -91,7 +114,7 @@ struct CSGPartForStep : public csg::CSGPart namespace csg { -VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, const VoxelizeParams &p); +VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, VoxelizeParams p); } // namespace csg diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 7989e9044..6c26c6f7b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -41,14 +41,14 @@ namespace Slic3r { namespace { const std::array OBJ_STEP_LEVELS = { - 5, // slaposAssembly - 5, // slaposHollowing, - 10, // slaposDrillHoles - 10, // slaposObjectSlice, - 20, // slaposSupportPoints, - 10, // slaposSupportTree, - 10, // slaposPad, - 30, // slaposSliceSupports, + 13, // slaposAssembly + 13, // slaposHollowing, + 13, // slaposDrillHoles + 13, // slaposObjectSlice, + 13, // slaposSupportPoints, + 13, // slaposSupportTree, + 11, // slaposPad, + 11, // slaposSliceSupports, }; std::string OBJ_STEP_LABELS(size_t idx) @@ -144,13 +144,20 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( .exterior_bandwidth(1.f) .interior_bandwidth(1.f); + voxparams.statusfn([&po](int){ + return po.m_print->cancel_status() != CancelStatus::NOT_CANCELED; + }); + auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); - indexed_triangle_set m = grid_to_mesh(*grid); + auto m = grid ? grid_to_mesh(*grid) : indexed_triangle_set{}; bench.stop(); - BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << bench.getElapsedSec(); + if (!m.empty()) + BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << bench.getElapsedSec(); + else + BOOST_LOG_TRIVIAL(error) << "Preview failed!"; return m; } diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index 3d63cbbad..a1b3ef42d 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -15,34 +15,34 @@ class SLAPrint::Steps private: SLAPrint *m_print = nullptr; std::mt19937 m_rng; - -public: + +public: // where the per object operations start and end - static const constexpr unsigned min_objstatus = 0; - static const constexpr unsigned max_objstatus = 50; - + static const constexpr unsigned min_objstatus = 0; + static const constexpr unsigned max_objstatus = 70; + private: const size_t objcount; - + // shortcut to initial layer height const double ilhd; const float ilh; const coord_t ilhs; - + // the coefficient that multiplies the per object status values which // are set up for <0, 100>. They need to be scaled into the whole process const double objectstep_scale; - + template void report_status(Args&&...args) { m_print->m_report_status(*m_print, std::forward(args)...); } - + double current_status() const { return m_print->m_report_status.status(); } void throw_if_canceled() const { m_print->throw_if_canceled(); } bool canceled() const { return m_print->canceled(); } void initialize_printer_input(); - + void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o); void generate_preview(SLAPrintObject &po, SLAPrintObjectStep step); @@ -50,7 +50,7 @@ private: public: explicit Steps(SLAPrint *print); - + void mesh_assembly(SLAPrintObject &po); void hollow_model(SLAPrintObject &po); void drill_holes (SLAPrintObject &po); @@ -59,20 +59,20 @@ public: void support_tree(SLAPrintObject& po); void generate_pad(SLAPrintObject& po); void slice_supports(SLAPrintObject& po); - + void merge_slices_and_eval_stats(); void rasterize(); - + void execute(SLAPrintObjectStep step, SLAPrintObject &obj); void execute(SLAPrintStep step); - + static std::string label(SLAPrintObjectStep step); static std::string label(SLAPrintStep step); - + double progressrange(SLAPrintObjectStep step) const; double progressrange(SLAPrintStep step) const; }; -} +} // namespace Slic3r #endif // SLAPRINTSTEPS_HPP From 9dc091d1a8ab413a636c468aa619e2d6b2db5c01 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 8 Dec 2022 14:20:06 +0100 Subject: [PATCH 022/206] Fix redundant token after include --- src/libslic3r/SLAPrintSteps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 6c26c6f7b..6ab0f6ec7 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -18,7 +18,7 @@ #include #include #include -#include > +#include #include #include #include From 2cf48683de2d4d36fd12667058dafd0614339f42 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 9 Dec 2022 17:23:31 +0100 Subject: [PATCH 023/206] enable split to parts in sla --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 871ed5e4f..85a781e5c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4961,7 +4961,7 @@ bool Plater::priv::can_split_to_objects() const bool Plater::priv::can_split_to_volumes() const { - return (printer_technology != ptSLA) && q->can_split(false); + return q->can_split(false); } bool Plater::priv::can_arrange() const From 3a7af1c1de798afb0c95ec0238bfe00267e1bc10 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 12 Dec 2022 14:07:28 +0100 Subject: [PATCH 024/206] SLA Backend: skip voxelization if the original mesh is usable as preview --- src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 2 +- src/libslic3r/SLAPrintSteps.cpp | 31 ++++++++++------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index 003fc2ca0..f1fd73981 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -21,7 +21,7 @@ VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params) params.trafo(params.trafo() * csg::get_transform(csgpart)); if (its) - ret = mesh_to_grid(*csg::get_mesh(csgpart), params); + ret = mesh_to_grid(*its, params); return ret; } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 6ab0f6ec7..177c4e984 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -148,9 +148,17 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( return po.m_print->cancel_status() != CancelStatus::NOT_CANCELED; }); - auto grid = csg::voxelize_csgmesh(range(po.m_mesh_to_slice), voxparams); - - auto m = grid ? grid_to_mesh(*grid) : indexed_triangle_set{}; + auto r = range(po.m_mesh_to_slice); + auto m = indexed_triangle_set{}; + if (r.size() > 1) { + auto grid = csg::voxelize_csgmesh(r, voxparams); + m = grid ? grid_to_mesh(*grid) : indexed_triangle_set{}; + } else if (!r.empty()) { + m = *(csg::get_mesh(*r.begin())); + auto tr = csg::get_transform(*r.begin()); + for (auto &v : m.vertices) + v = tr * v; + } bench.stop(); @@ -165,21 +173,6 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) { - // TODO: enable when this works reliably. Currently, perform_csgmesh_booleans - // can generate incorrect result despite not throwing any exception. -// MeshBoolean::cgal::CGALMeshPtr cgalptr; - -// try { -// cgalptr = csg::perform_csgmesh_booleans(range(po.m_mesh_to_slice)); -// } catch(...) { -// cgalptr = nullptr; -// } - -// if (cgalptr) { -// po.m_preview_meshes[step] = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalptr); -// } else -// po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; - po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; for (size_t i = size_t(step) + 1; i < slaposCount; ++i) @@ -286,6 +279,8 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // update preview mesh if (r.first != r.second) generate_preview(po, slaposDrillHoles); + else + po.m_preview_meshes[slaposDrillHoles] = po.get_mesh_to_print(); } template From 7b207aaf5c988c4b7a70f805d660178f1a9eb8eb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 12 Dec 2022 16:23:33 +0100 Subject: [PATCH 025/206] Add "Enforcers only" option into support combo box And also make it work --- src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 7 +++++ src/libslic3r/PrintConfig.hpp | 3 ++ src/libslic3r/SLAPrint.cpp | 5 +-- src/libslic3r/SLAPrintSteps.cpp | 44 +++++++++++++++++---------- src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Plater.cpp | 7 +++-- src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index bd70ee739..49b8c99a2 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -502,6 +502,7 @@ static std::vector s_Preset_sla_print_options { "support_max_bridges_on_pillar", "support_pillar_connection_mode", "support_buildplate_only", + "support_enforcers_only", "support_pillar_widening_factor", "support_base_diameter", "support_base_height", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fe35161b0..b1a98d5ad 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3661,6 +3661,13 @@ void PrintConfigDef::init_sla_params() def->mode = comSimple; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("support_enforcers_only", coBool); + def->label = L("Support only in enforced regions"); + def->category = L("Supports"); + def->tooltip = L("Only create support if it lies in a support enforcer."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("support_pillar_widening_factor", coFloat); def->label = L("Pillar widening factor"); def->category = L("Supports"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 3258de005..d67296646 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -858,6 +858,9 @@ PRINT_CONFIG_CLASS_DEFINE( // Generate only ground facing supports ((ConfigOptionBool, support_buildplate_only)) + // Generate only ground facing supports + ((ConfigOptionBool, support_enforcers_only)) + // TODO: unimplemented at the moment. This coefficient will have an impact // when bridges and pillars are merged. The resulting pillar should be a bit // thicker than the ones merging into it. How much thicker? I don't know diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index fdb264b5e..89c881ebf 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -35,7 +35,7 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) { sla::SupportTreeConfig scfg; - + scfg.enabled = c.supports_enable.getBool(); scfg.tree_type = c.support_tree_type.value; scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); @@ -58,7 +58,7 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) scfg.pillar_base_safety_distance_mm = c.support_base_safety_distance.getFloat() < EPSILON ? scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); - + scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); return scfg; @@ -827,6 +827,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector &blockers, - const std::vector &enforcers, - const std::vector &slice_grid) + +struct SuppPtMask { + const std::vector &blockers; + const std::vector &enforcers; + bool enforcers_only = false; +}; + +static void filter_support_points_by_modifiers(sla::SupportPoints &pts, + const SuppPtMask &mask, + const std::vector &slice_grid) { assert((blockers.empty() || blockers.size() == slice_grid.size()) && (enforcers.empty() || enforcers.size() == slice_grid.size())); @@ -423,24 +428,30 @@ static void filter_support_points_by_modifiers( if (it != slice_grid.end()) { size_t idx = std::distance(slice_grid.begin(), it); bool is_enforced = false; - if (idx < enforcers.size()) { + if (idx < mask.enforcers.size()) { for (size_t enf_idx = 0; - !is_enforced && enf_idx < enforcers[idx].size(); + !is_enforced && enf_idx < mask.enforcers[idx].size(); ++enf_idx) { - if (enforcers[idx][enf_idx].contains(sp2d)) + if (mask.enforcers[idx][enf_idx].contains(sp2d)) is_enforced = true; } } bool is_blocked = false; - if (!is_enforced && idx < blockers.size()) { - for (size_t blk_idx = 0; - !is_blocked && blk_idx < blockers[idx].size(); - ++blk_idx) - { - if (blockers[idx][blk_idx].contains(sp2d)) - is_blocked = true; + if (!is_enforced) { + if (!mask.enforcers_only) { + if (idx < mask.blockers.size()) { + for (size_t blk_idx = 0; + !is_blocked && blk_idx < mask.blockers[idx].size(); + ++blk_idx) + { + if (mask.blockers[idx][blk_idx].contains(sp2d)) + is_blocked = true; + } + } + } else { + is_blocked = true; } } @@ -527,7 +538,8 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) return vol->is_support_enforcer(); }); - filter_support_points_by_modifiers(points, blockers, enforcers, po.m_model_height_levels); + SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()}; + filter_support_points_by_modifiers(points, mask, po.m_model_height_levels); po.m_supportdata->input.pts = points; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 50dd4e743..876228bd9 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -374,6 +374,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) toggle_field("support_pillar_connection_mode", supports_en && is_default_tree); toggle_field("support_tree_type", supports_en); toggle_field("support_buildplate_only", supports_en); + toggle_field("support_enforcers_only", supports_en); toggle_field("support_base_diameter", supports_en); toggle_field("support_base_height", supports_en); toggle_field("support_base_safety_distance", supports_en); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 85a781e5c..7d197e1e3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -545,10 +545,15 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : const bool supports_enable = selection == _("None") ? false : true; new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); + new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); + if (selection == _("Everywhere")) new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); else if (selection == _("Support on build plate only")) new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); + else if (selection == _("For support enforcers only")) { + new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(true)); + } } tab->load_config(new_conf); @@ -559,8 +564,6 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : ConfigOptionDef support_def_sla = support_def; support_def_sla.set_default_value(new ConfigOptionStrings{ "None" }); - assert(support_def_sla.enum_labels[2] == L("For support enforcers only")); - support_def_sla.enum_labels.erase(support_def_sla.enum_labels.begin() + 2); option = Option(support_def_sla, "support"); option.opt.full_width = true; line.append_option(option); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9515fa2b6..d36c25d39 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4777,6 +4777,7 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_pillar_connection_mode"); optgroup->append_single_option_line("support_buildplate_only"); + optgroup->append_single_option_line("support_enforcers_only"); optgroup->append_single_option_line("support_pillar_widening_factor"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); From 0dadde6ae3eb16a1a109b20f992a2d2ff2931f94 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 8 Dec 2022 11:20:05 +0100 Subject: [PATCH 026/206] MenuFactory: SLA specific: Fixed adding of the "Edit text" menu item --- src/slic3r/GUI/GUI_Factories.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index a75322028..53b15a321 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1018,16 +1018,19 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu) wxString name = _L("Edit text"); auto can_edit_text = []() { - const auto& sel = plater()->get_selection(); - if (sel.volumes_count() != 1) return false; - auto cid = sel.get_volume(*sel.get_volume_idxs().begin()); - const ModelVolume* vol = plater()->canvas3D()->get_model() - ->objects[cid->object_idx()]->volumes[cid->volume_idx()]; - return vol->text_configuration.has_value(); + if (plater() != nullptr) { + const Selection& sel = plater()->get_selection(); + if (sel.volumes_count() == 1) { + const GLVolume* gl_vol = sel.get_first_volume(); + const ModelVolume* vol = plater()->model().objects[gl_vol->object_idx()]->volumes[gl_vol->volume_idx()]; + return vol->text_configuration.has_value(); + } + } + return false; }; - if (menu == &m_object_menu) { - auto menu_item_id = menu->FindItem(name); + if (menu != &m_text_part_menu) { + const int menu_item_id = menu->FindItem(name); if (menu_item_id != wxNOT_FOUND) menu->Destroy(menu_item_id); if (!can_edit_text()) From 9de057ddebb1ccda85b4f47349e3b744e4054e94 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 13 Dec 2022 13:23:59 +0100 Subject: [PATCH 027/206] Fix in sla mode: wrong part menu and split submenu --- src/slic3r/GUI/GUI_Factories.cpp | 20 ++++++++++++-------- src/slic3r/GUI/GUI_Factories.hpp | 1 + src/slic3r/GUI/Plater.cpp | 10 +++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 53b15a321..0250d2164 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1088,22 +1088,28 @@ void MenuFactory::create_common_object_menu(wxMenu* menu) append_menu_items_mirror(menu); } -void MenuFactory::create_object_menu() +void MenuFactory::append_menu_items_split(wxMenu *menu) { - create_common_object_menu(&m_object_menu); wxMenu* split_menu = new wxMenu(); if (!split_menu) return; append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"), - [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", &m_object_menu, + [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", menu, []() { return plater()->can_split(true); }, m_parent); append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual parts"), - [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", &m_object_menu, + [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", menu, []() { return plater()->can_split(false); }, m_parent); - append_submenu(&m_object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "", + append_submenu(menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "", []() { return plater()->can_split(true); }, m_parent); +} + +void MenuFactory::create_object_menu() +{ + create_common_object_menu(&m_object_menu); + + append_menu_items_split(&m_object_menu); m_object_menu.AppendSeparator(); // "Height range Modifier" and "Add (volumes)" menu items will be added later in append_menu_items_add_volume() @@ -1112,9 +1118,7 @@ void MenuFactory::create_object_menu() void MenuFactory::create_sla_object_menu() { create_common_object_menu(&m_sla_object_menu); - append_menu_item(&m_sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"), - [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", nullptr, - []() { return plater()->can_split(true); }, m_parent); + append_menu_items_split(&m_sla_object_menu); m_sla_object_menu.AppendSeparator(); append_menu_items_add_sla_volume(&m_sla_object_menu); diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 81798af9c..34725d6c1 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -113,6 +113,7 @@ private: void append_menu_item_edit_text(wxMenu *menu); void append_menu_items_instance_manipulation(wxMenu *menu); void update_menu_items_instance_manipulation(MenuType type); + void append_menu_items_split(wxMenu *menu); }; }} diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7d197e1e3..9b179be66 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4484,9 +4484,9 @@ void Plater::priv::on_right_click(RBtnEvent& evt) // so this selection should be updated before menu creation wxGetApp().obj_list()->update_selections(); - if (printer_technology == ptSLA) - menu = menus.sla_object_menu(); - else { +// if (printer_technology == ptSLA) +// menu = menus.sla_object_menu(); +// else { const Selection& selection = get_selection(); // show "Object menu" for each one or several FullInstance instead of FullObject const bool is_some_full_instances = selection.is_single_full_instance() || @@ -4498,12 +4498,12 @@ void Plater::priv::on_right_click(RBtnEvent& evt) const bool is_part = selection.is_single_volume() || selection.is_single_modifier(); #endif // ENABLE_WORLD_COORDINATE if (is_some_full_instances) - menu = menus.object_menu(); + menu = printer_technology == ptSLA ? menus.sla_object_menu() : menus.object_menu(); else if (is_part) menu = selection.is_single_text() ? menus.text_part_menu() : menus.part_menu(); else menu = menus.multi_selection_menu(); - } +// } } if (q != nullptr && menu) { From 5561e21e7781675b68f3530c806ca7cf73a64834 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 13 Dec 2022 14:53:27 +0100 Subject: [PATCH 028/206] object export to stl will work the same way as in FFF TODO might make more sense to execute background processing up until the necessary step and then export backend data --- src/slic3r/GUI/Plater.cpp | 94 +++++---------------------------------- 1 file changed, 12 insertions(+), 82 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9b179be66..45f1012ef 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6112,92 +6112,22 @@ void Plater::export_stl_obj(bool extended, bool selection_only) }; TriangleMesh mesh; - if (p->printer_technology == ptFFF) { - if (selection_only) { - const ModelObject* model_object = p->model.objects[obj_idx]; - if (selection.get_mode() == Selection::Instance) - mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); - else { - const GLVolume* volume = selection.get_first_volume(); - mesh = model_object->volumes[volume->volume_idx()]->mesh(); - mesh.transform(volume->get_volume_transformation().get_matrix(), true); - } - - if (!selection.is_single_full_object() || model_object->instances.size() == 1) - mesh.translate(-model_object->origin_translation.cast()); - } + if (selection_only) { + const ModelObject* model_object = p->model.objects[obj_idx]; + if (selection.get_mode() == Selection::Instance) + mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); else { - for (const ModelObject* o : p->model.objects) { - mesh.merge(mesh_to_export(*o, -1)); - } + const GLVolume* volume = selection.get_first_volume(); + mesh = model_object->volumes[volume->volume_idx()]->mesh(); + mesh.transform(volume->get_volume_transformation().get_matrix(), true); } + + if (!selection.is_single_full_object() || model_object->instances.size() == 1) + mesh.translate(-model_object->origin_translation.cast()); } else { - // This is SLA mode, all objects have only one volume. - // However, we must have a look at the backend to load - // hollowed mesh and/or supports - - const PrintObjects& objects = p->sla_print.objects(); - for (const SLAPrintObject* object : objects) { - const ModelObject* model_object = object->model_object(); - if (selection_only) { - if (model_object->id() != p->model.objects[obj_idx]->id()) - continue; - } - const Transform3d mesh_trafo_inv = object->trafo().inverse(); - const bool is_left_handed = object->is_left_handed(); - - auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{}; - pad_mesh = object->pad_mesh(); - pad_mesh.transform(mesh_trafo_inv); - - auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{}; - supports_mesh.transform(mesh_trafo_inv); - - const std::vector& obj_instances = object->instances(); - for (const SLAPrintObject::Instance& obj_instance : obj_instances) { - auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), - [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); - assert(it != model_object->instances.end()); - - if (it != model_object->instances.end()) { - const bool one_inst_only = selection_only && ! selection.is_single_full_object(); - - const int instance_idx = it - model_object->instances.begin(); - const Transform3d& inst_transform = one_inst_only - ? Transform3d::Identity() - : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); - - TriangleMesh inst_mesh; - - if (!pad_mesh.empty()) { - TriangleMesh inst_pad_mesh = pad_mesh; - inst_pad_mesh.transform(inst_transform, is_left_handed); - inst_mesh.merge(inst_pad_mesh); - } - - if (!supports_mesh.empty()) { - TriangleMesh inst_supports_mesh = supports_mesh; - inst_supports_mesh.transform(inst_transform, is_left_handed); - inst_mesh.merge(inst_supports_mesh); - } - - TriangleMesh inst_object_mesh = object->get_mesh_to_print(); - inst_object_mesh.transform(mesh_trafo_inv); - inst_object_mesh.transform(inst_transform, is_left_handed); - - inst_mesh.merge(inst_object_mesh); - - // ensure that the instance lays on the bed - inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min.z()); - - // merge instance with global mesh - mesh.merge(inst_mesh); - - if (one_inst_only) - break; - } - } + for (const ModelObject* o : p->model.objects) { + mesh.merge(mesh_to_export(*o, -1)); } } From 9143d9891a7974adceae0f6fe5abbf199f4da8c8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 13 Dec 2022 15:13:31 +0100 Subject: [PATCH 029/206] In SLA mode, upon export to stl, export preview mesh, if it exists --- src/slic3r/GUI/Plater.cpp | 77 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 45f1012ef..f3eae5633 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6089,7 +6089,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only) return; // Following lambda generates a combined mesh for export with normals pointing outwards. - auto mesh_to_export = [](const ModelObject& mo, int instance_id) { + auto mesh_to_export_fff = [](const ModelObject& mo, int instance_id) { TriangleMesh mesh; for (const ModelVolume* v : mo.volumes) if (v->is_model_part()) { @@ -6111,6 +6111,81 @@ void Plater::export_stl_obj(bool extended, bool selection_only) return mesh; }; + auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) { + TriangleMesh mesh; + + const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); + + if (object->get_mesh_to_print().empty()) + mesh = mesh_to_export_fff(mo, instance_id); + else { + const Transform3d mesh_trafo_inv = object->trafo().inverse(); + const bool is_left_handed = object->is_left_handed(); + + auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{}; + pad_mesh = object->pad_mesh(); + pad_mesh.transform(mesh_trafo_inv); + + auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{}; + supports_mesh.transform(mesh_trafo_inv); + + const std::vector& obj_instances = object->instances(); + for (const SLAPrintObject::Instance& obj_instance : obj_instances) { + auto it = std::find_if(object->model_object()->instances.begin(), object->model_object()->instances.end(), + [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); + assert(it != model_object->instances.end()); + + if (it != object->model_object()->instances.end()) { + const bool one_inst_only = selection_only && ! selection.is_single_full_object(); + + const int instance_idx = it - object->model_object()->instances.begin(); + const Transform3d& inst_transform = one_inst_only + ? Transform3d::Identity() + : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); + + TriangleMesh inst_mesh; + + if (!pad_mesh.empty()) { + TriangleMesh inst_pad_mesh = pad_mesh; + inst_pad_mesh.transform(inst_transform, is_left_handed); + inst_mesh.merge(inst_pad_mesh); + } + + if (!supports_mesh.empty()) { + TriangleMesh inst_supports_mesh = supports_mesh; + inst_supports_mesh.transform(inst_transform, is_left_handed); + inst_mesh.merge(inst_supports_mesh); + } + + TriangleMesh inst_object_mesh = object->get_mesh_to_print(); + inst_object_mesh.transform(mesh_trafo_inv); + inst_object_mesh.transform(inst_transform, is_left_handed); + + inst_mesh.merge(inst_object_mesh); + + // ensure that the instance lays on the bed + inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min.z()); + + // merge instance with global mesh + mesh.merge(inst_mesh); + + if (one_inst_only) + break; + } + } + } + + return mesh; + }; + + std::function + mesh_to_export; + + if (p->printer_technology == ptFFF ) + mesh_to_export = mesh_to_export_fff; + else + mesh_to_export = mesh_to_export_sla; + TriangleMesh mesh; if (selection_only) { const ModelObject* model_object = p->model.objects[obj_idx]; From f987caa63d60ef0e98ad376a7b424e0ad7054727 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Dec 2022 09:55:09 +0100 Subject: [PATCH 030/206] Fix build in debug mode --- src/libslic3r/SLAPrintSteps.cpp | 4 ++-- src/slic3r/GUI/Plater.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index b3e47fe14..630f6f85f 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -415,8 +415,8 @@ static void filter_support_points_by_modifiers(sla::SupportPoints &pts, const SuppPtMask &mask, const std::vector &slice_grid) { - assert((blockers.empty() || blockers.size() == slice_grid.size()) && - (enforcers.empty() || enforcers.size() == slice_grid.size())); + assert((mask.blockers.empty() || mask.blockers.size() == slice_grid.size()) && + (mask.enforcers.empty() || mask.enforcers.size() == slice_grid.size())); auto new_pts = reserve_vector(pts.size()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f3eae5633..734f6140e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6133,7 +6133,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only) for (const SLAPrintObject::Instance& obj_instance : obj_instances) { auto it = std::find_if(object->model_object()->instances.begin(), object->model_object()->instances.end(), [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); - assert(it != model_object->instances.end()); + assert(it != object->model_object()->instances.end()); if (it != object->model_object()->instances.end()) { const bool one_inst_only = selection_only && ! selection.is_single_full_object(); From a3403c51cf0955e334b98d12d5812a469caf6e65 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 14 Dec 2022 13:14:56 +0100 Subject: [PATCH 031/206] SLA mode: Updated object menu and "Change type" dialog + ObjectList: Suppress to add modifiers to the object --- src/slic3r/GUI/GUI_Factories.cpp | 100 +++++++----------------------- src/slic3r/GUI/GUI_Factories.hpp | 7 +-- src/slic3r/GUI/GUI_ObjectList.cpp | 63 ++++++++++++++----- src/slic3r/GUI/Plater.cpp | 1 - 4 files changed, 72 insertions(+), 99 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 0250d2164..6dece9a22 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -532,8 +532,12 @@ void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, } } -void MenuFactory::append_menu_items_add_volume(wxMenu* menu) +void MenuFactory::append_menu_items_add_volume(MenuType menu_type) { + wxMenu* menu = menu_type == mtObjectFFF ? &m_object_menu : menu_type == mtObjectSLA ? &m_sla_object_menu : nullptr; + if (!menu) + return; + // Update "add" items(delete old & create new) items popupmenu for (auto& item : ADD_VOLUME_MENU_ITEMS) { const wxString item_name = _(item.first); @@ -569,9 +573,11 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu) return; } - int type = 0; - for (auto& item : ADD_VOLUME_MENU_ITEMS) { - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type++)); + for (size_t type = 0; type < ADD_VOLUME_MENU_ITEMS.size(); type++) { + auto& item = ADD_VOLUME_MENU_ITEMS[type]; + if (menu_type == mtObjectSLA && ModelVolumeType(type) == ModelVolumeType::PARAMETER_MODIFIER) + continue; + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type)); append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, [type]() { bool can_add = type < size_t(ModelVolumeType::PARAMETER_MODIFIER) ? !obj_list()->is_selected_object_cut() : true; @@ -579,56 +585,8 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu) }, m_parent); } - append_menu_item_layers_editing(menu); -} - -void MenuFactory::append_menu_items_add_sla_volume(wxMenu *menu) -{ - // Update "add" items(delete old & create new) settings popupmenu - for (auto& item : ADD_VOLUME_MENU_ITEMS) { - const auto settings_id = menu->FindItem(_(item.first)); - if (settings_id != wxNOT_FOUND) - menu->Destroy(settings_id); - } - - const ConfigOptionMode mode = wxGetApp().get_mode(); - - if (mode == comAdvanced) { - append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", - [](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); }, - ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, - []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); - } else { - auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)]; - - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::MODEL_PART); - append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, - []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); - } - - { - auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::NEGATIVE_VOLUME)]; - - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::NEGATIVE_VOLUME); - append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, - []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); - } - - { - auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)]; - - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::SUPPORT_ENFORCER); - append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, - []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); - } - - { - auto& item = ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)]; - - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::SUPPORT_BLOCKER); - append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, - []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); - } + if (menu_type == mtObjectFFF) + append_menu_item_layers_editing(menu); } wxMenuItem* MenuFactory::append_menu_item_layers_editing(wxMenu* menu) @@ -1086,6 +1044,9 @@ void MenuFactory::create_common_object_menu(wxMenu* menu) append_menu_item_fix_through_netfabb(menu); append_menu_item_simplify(menu); append_menu_items_mirror(menu); + + append_menu_items_split(menu); + menu->AppendSeparator(); } void MenuFactory::append_menu_items_split(wxMenu *menu) @@ -1105,26 +1066,6 @@ void MenuFactory::append_menu_items_split(wxMenu *menu) []() { return plater()->can_split(true); }, m_parent); } -void MenuFactory::create_object_menu() -{ - create_common_object_menu(&m_object_menu); - - append_menu_items_split(&m_object_menu); - m_object_menu.AppendSeparator(); - - // "Height range Modifier" and "Add (volumes)" menu items will be added later in append_menu_items_add_volume() -} - -void MenuFactory::create_sla_object_menu() -{ - create_common_object_menu(&m_sla_object_menu); - append_menu_items_split(&m_sla_object_menu); - - m_sla_object_menu.AppendSeparator(); - append_menu_items_add_sla_volume(&m_sla_object_menu); - m_sla_object_menu.AppendSeparator(); -} - void MenuFactory::append_immutable_part_menu_items(wxMenu* menu) { append_menu_items_mirror(menu); @@ -1184,8 +1125,8 @@ void MenuFactory::init(wxWindow* parent) m_parent = parent; create_default_menu(); - create_object_menu(); - create_sla_object_menu(); + create_common_object_menu(&m_object_menu); + create_common_object_menu(&m_sla_object_menu); create_part_menu(); create_text_part_menu(); create_instance_menu(); @@ -1194,7 +1135,7 @@ void MenuFactory::init(wxWindow* parent) void MenuFactory::update() { update_default_menu(); - update_object_menu(); + update_objects_menu(); } wxMenu* MenuFactory::default_menu() @@ -1331,9 +1272,10 @@ void MenuFactory::update_menu_items_instance_manipulation(MenuType type) } } -void MenuFactory::update_object_menu() +void MenuFactory::update_objects_menu() { - append_menu_items_add_volume(&m_object_menu); + append_menu_items_add_volume(mtObjectFFF); + append_menu_items_add_volume(mtObjectSLA); } void MenuFactory::update_default_menu() diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 34725d6c1..515311d0d 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -41,7 +41,7 @@ public: void init(wxWindow* parent); void update(); - void update_object_menu(); + void update_objects_menu(); void update_default_menu(); void sys_color_changed(); @@ -79,8 +79,6 @@ private: void create_default_menu(); void create_common_object_menu(wxMenu *menu); - void create_object_menu(); - void create_sla_object_menu(); void append_immutable_part_menu_items(wxMenu* menu); void append_mutable_part_menu_items(wxMenu* menu); void create_part_menu(); @@ -89,8 +87,7 @@ private: wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); void append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item = true); - void append_menu_items_add_volume(wxMenu* menu); - void append_menu_items_add_sla_volume(wxMenu* menu); + void append_menu_items_add_volume(MenuType type); wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8c1546410..a5ddcf94c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2992,7 +2992,8 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st int volume_idx{ -1 }; for (const ModelVolume* volume : object->volumes) { ++volume_idx; - if (object->is_cut() && volume->is_cut_connector()) + if ((object->is_cut() && volume->is_cut_connector()) || + (printer_technology() == ptSLA && volume->type() == ModelVolumeType::PARAMETER_MODIFIER)) continue; const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, from_u8(volume->name), @@ -4262,17 +4263,32 @@ void ObjectList::change_part_type() } const bool is_cut_object = obj->is_cut(); - wxArrayString names; - if (!is_cut_object) - for (const wxString& type : { _L("Part"), _L("Negative Volume") }) - names.Add(type); - names.Add(_L("Modifier")); - if (!volume->text_configuration.has_value()) - for (const wxString& name : { _L("Support Blocker"), _L("Support Enforcer") }) + wxArrayString names; + std::vector types; + types.reserve(5); + if (!is_cut_object) { + for (const wxString& name : { _L("Part"), _L("Negative Volume") }) names.Add(name); + for (const ModelVolumeType type_id : { ModelVolumeType::MODEL_PART, ModelVolumeType::NEGATIVE_VOLUME }) + types.emplace_back(type_id); + } - const int type_shift = is_cut_object ? 2 : 0; - auto new_type = ModelVolumeType(type_shift + wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, int(type) - type_shift)); + if (printer_technology() != ptSLA) { + names.Add(_L("Modifier")); + types.emplace_back(ModelVolumeType::PARAMETER_MODIFIER); + } + + if (!volume->text_configuration.has_value()) { + for (const wxString& name : { _L("Support Blocker"), _L("Support Enforcer") }) + names.Add(name); + for (const ModelVolumeType type_id : { ModelVolumeType::SUPPORT_BLOCKER, ModelVolumeType::SUPPORT_ENFORCER }) + types.emplace_back(type_id); + } + + int selection = 0; + if (auto it = std::find(types.begin(), types.end(), type); it != types.end()) + selection = it - types.begin(); + const auto new_type = types[wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, selection)]; if (new_type == type || new_type == ModelVolumeType::INVALID) return; @@ -4357,8 +4373,9 @@ void ObjectList::update_object_list_by_printer_technology() m_objects_model->GetChildren(wxDataViewItem(nullptr), object_items); for (auto& object_item : object_items) { + const int obj_idx = m_objects_model->GetObjectIdByItem(object_item); // update custom supports info - update_info_items(m_objects_model->GetObjectIdByItem(object_item), &sel); + update_info_items(obj_idx, &sel); // Update Settings Item for object update_settings_item_and_selection(object_item, sel); @@ -4366,10 +4383,26 @@ void ObjectList::update_object_list_by_printer_technology() // Update settings for Volumes wxDataViewItemArray all_object_subitems; m_objects_model->GetChildren(object_item, all_object_subitems); + + bool was_selected_some_subitem = false; for (auto item : all_object_subitems) - if (m_objects_model->GetItemType(item) & itVolume) - // update settings for volume - update_settings_item_and_selection(item, sel); + if (m_objects_model->GetItemType(item) & itVolume) { + if (sel.Index(item) != wxNOT_FOUND) { + sel.Remove(item); + was_selected_some_subitem = true; + } + else if (const wxDataViewItem vol_settings_item = m_objects_model->GetSettingsItem(item); + sel.Index(vol_settings_item) != wxNOT_FOUND) { + sel.Remove(vol_settings_item); + was_selected_some_subitem = true; + break; + } + } + if (was_selected_some_subitem) + sel.Add(object_item); + + // Update volumes list in respect to the print mode + add_volumes_to_object_in_list(obj_idx); // Update Layers Items wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); @@ -4411,6 +4444,8 @@ void ObjectList::update_object_list_by_printer_technology() // restore selection: SetSelections(sel); m_prevent_canvas_selection_update = false; + + update_selections_on_canvas(); } void ObjectList::instances_to_separated_object(const int obj_idx, const std::set& inst_idxs) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 734f6140e..8a9e93485 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1529,7 +1529,6 @@ void Sidebar::update_mode() p->object_list->unselect_objects(); p->object_list->update_selections(); -// p->object_list->update_object_menu(); Layout(); } From 3f7dcf744bde9da5f114779ed095b88343bf0e9e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Dec 2022 18:19:04 +0100 Subject: [PATCH 032/206] wip on support points elevation bug --- src/slic3r/GUI/GLCanvas3D.cpp | 18 +++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 7 +++++++ src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 2 ++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 23e974a55..a119e0938 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1940,15 +1940,15 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } } - if (printer_technology == ptSLA) { - // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed - for (GLVolume* volume : m_volumes.volumes) - if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) { - const SLAPrintObject* po = sla_print()->get_print_object_by_model_object_id(volume->object_idx()); - if (po != nullptr) - volume->set_sla_shift_z(po->get_current_elevation() / sla_print()->relative_correction().z()); - } - } +// if (printer_technology == ptSLA) { +// // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed +// for (GLVolume* volume : m_volumes.volumes) +// if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) { +// const SLAPrintObject* po = sla_print()->objects()[volume->object_idx()];//sla_print()->get_print_object_by_model_object_id(volume->object_idx()); +//// if (po != nullptr) +//// volume->set_sla_shift_z(po->get_current_elevation() / sla_print()->relative_correction().z()); +// } +// } if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) { // Should the wipe tower be visualized ? diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp index 7bcc01b71..9b5e9f469 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp @@ -42,6 +42,8 @@ protected: bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); + const GLVolumeCollection &volumes() const { return m_volumes; } + private: GLVolumeCollection m_volumes; bool m_input_enabled{ false }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 01d01f1b8..d41120b11 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -129,6 +129,13 @@ void SelectionInfo::on_release() m_model_volume = nullptr; } +ModelInstance *SelectionInfo::model_instance() const +{ + int inst_idx = get_active_instance(); + return int(m_model_object->instances.size()) < inst_idx ? + m_model_object->instances[get_active_instance()] : nullptr; +} + int SelectionInfo::get_active_instance() const { return get_pool()->get_canvas()->get_selection().get_instance_idx(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 2ae30a71b..21e1bb73c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -9,6 +9,7 @@ namespace Slic3r { class ModelObject; +class ModelInstance; class SLAPrintObject; class ModelVolume; @@ -160,6 +161,7 @@ public: const SLAPrintObject *print_object() const { return m_print_object; } // Returns a non-null pointer if the selection is a single volume ModelVolume* model_volume() const { return m_model_volume; } + ModelInstance *model_instance() const; int get_active_instance() const; float get_sla_shift() const { return m_z_shift; } From cf63c60c22b758210b83ac910bf68828e580d3c2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Dec 2022 18:47:56 +0100 Subject: [PATCH 033/206] Support points trafo seems to be OK now --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 19 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 45013744f..362912607 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -148,8 +148,15 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) shader->start_using(); ScopeGuard guard([shader]() { shader->stop_using(); }); - const GLVolume* vol = selection.get_first_volume(); - const Geometry::Transformation transformation(vol->world_matrix()); + auto *inst = m_c->selection_info()->model_instance(); + if (!inst) + return; + + double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); + Transform3d trafo(inst->get_transformation().get_matrix()); + trafo.translate(Vec3d{0., 0., shift_z}); + const Geometry::Transformation transformation{trafo}; + #if ENABLE_WORLD_COORDINATE const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); #else @@ -197,7 +204,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. const Transform3d support_matrix = Geometry::translation_transform(support_point.pos.cast()) * instance_scaling_matrix_inverse; - if (vol->is_left_handed()) + if (transformation.is_left_handed()) glsafe(::glFrontFace(GL_CW)); // Matrices set, we can render the point mark now. @@ -210,7 +217,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); const Eigen::AngleAxisd aa(q); - const Transform3d model_matrix = vol->world_matrix() * support_matrix * Transform3d(aa.toRotationMatrix()) * + const Transform3d model_matrix = transformation.get_matrix() * support_matrix * Transform3d(aa.toRotationMatrix()) * Geometry::translation_transform((CONE_HEIGHT + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ()) * Geometry::rotation_transform({ double(PI), 0.0, 0.0 }) * Geometry::scale_transform({ CONE_RADIUS, CONE_RADIUS, CONE_HEIGHT }); @@ -221,13 +228,13 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) } const double radius = (double)support_point.head_front_radius * RenderPointScale; - const Transform3d model_matrix = vol->world_matrix() * support_matrix * Geometry::scale_transform(radius); + const Transform3d model_matrix = transformation.get_matrix() * support_matrix * Geometry::scale_transform(radius); shader->set_uniform("view_model_matrix", view_matrix * model_matrix); const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); shader->set_uniform("view_normal_matrix", view_normal_matrix); m_sphere.model.render(); - if (vol->is_left_handed()) + if (transformation.is_left_handed()) glsafe(::glFrontFace(GL_CCW)); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index d41120b11..f0734b445 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -132,7 +132,7 @@ void SelectionInfo::on_release() ModelInstance *SelectionInfo::model_instance() const { int inst_idx = get_active_instance(); - return int(m_model_object->instances.size()) < inst_idx ? + return inst_idx < int(m_model_object->instances.size()) ? m_model_object->instances[get_active_instance()] : nullptr; } From 45fffed309dac9c7b1641b6f31c0e1c03fe35d69 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 09:50:11 +0100 Subject: [PATCH 034/206] Remove leftover commented code --- src/slic3r/GUI/GLCanvas3D.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a119e0938..2aa96369d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1940,16 +1940,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } } -// if (printer_technology == ptSLA) { -// // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed -// for (GLVolume* volume : m_volumes.volumes) -// if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) { -// const SLAPrintObject* po = sla_print()->objects()[volume->object_idx()];//sla_print()->get_print_object_by_model_object_id(volume->object_idx()); -//// if (po != nullptr) -//// volume->set_sla_shift_z(po->get_current_elevation() / sla_print()->relative_correction().z()); -// } -// } - if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) { // Should the wipe tower be visualized ? unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); From 999c49c8e814c075ec4e82daed4bb314712c8495 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 11:43:39 +0100 Subject: [PATCH 035/206] Remove leftover comments in SLAPrint --- src/libslic3r/SLAPrint.cpp | 51 -------------------------------------- src/libslic3r/SLAPrint.hpp | 9 ------- 2 files changed, 60 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 89c881ebf..dc05148fc 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -994,44 +994,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const return idx >= v.size() ? EMPTY_SLICE : v[idx]; } -//bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const -//{ -// switch (step) { -// case slaposAssembly: -// return true; -// case slaposObjectSlice: -// return ! this->m_mesh_from_slices.empty(); -// case slaposDrillHoles: -// return ! this->m_mesh_from_slices.empty(); -// case slaposSupportTree: -// return ! this->support_mesh().empty(); -// case slaposPad: -// return ! this->pad_mesh().empty(); -// default: -// return false; -// } -//} - -//TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const -//{ -// switch (step) { -// case slaposAssembly: -// return m_transformed_rmesh; -// case slaposObjectSlice: -// return this->m_mesh_from_slices; -// case slaposSupportTree: -// return this->support_mesh(); -// case slaposPad: -// return this->pad_mesh(); -// case slaposDrillHoles: -//// if (m_hollowing_data) -// return get_mesh_to_print(); -// [[fallthrough]]; -// default: -// return TriangleMesh(); -// } -//} - const TriangleMesh& SLAPrintObject::support_mesh() const { if (m_config.supports_enable.getBool() && @@ -1068,19 +1030,6 @@ const TriangleMesh &SLAPrintObject::get_mesh_to_print() const return *ret; } -//const indexed_triangle_set &SLAPrintObject::hollowed_interior_mesh() const -//{ -// if (m_hollowing_data && m_hollowing_data->interior && -// m_config.hollowing_enable.getBool()) -// return sla::get_mesh(*m_hollowing_data->interior); - -// return EMPTY_TRIANGLE_SET; -//} - -//const TriangleMesh &SLAPrintObject::transformed_mesh() const { -// return m_transformed_rmesh; -//} - sla::SupportPoints SLAPrintObject::transformed_support_points() const { assert(m_model_object != nullptr); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 37aeb7b1c..6379575bf 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -145,26 +145,17 @@ public: }; const std::vector& instances() const { return m_instances; } -// bool has_mesh(SLAPrintObjectStep step) const; -// TriangleMesh get_mesh(SLAPrintObjectStep step) const; - // Get a support mesh centered around origin in XY, and with zero rotation around Z applied. // Support mesh is only valid if this->is_step_done(slaposSupportTree) is true. const TriangleMesh& support_mesh() const; // Get a pad mesh centered around origin in XY, and with zero rotation around Z applied. // Support mesh is only valid if this->is_step_done(slaposPad) is true. const TriangleMesh& pad_mesh() const; - -// // Ready after this->is_step_done(slaposDrillHoles) is true -// const indexed_triangle_set &hollowed_interior_mesh() const; // Get the mesh that is going to be printed with all the modifications // like hollowing and drilled holes. const TriangleMesh & get_mesh_to_print() const; -// // This will return the transformed mesh which is cached -// const TriangleMesh& transformed_mesh() const; - sla::SupportPoints transformed_support_points() const; sla::DrainHoles transformed_drainhole_points() const; From 473c8a26a409fa66f786c6fea3c32e4295c0132d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 15 Dec 2022 11:21:41 +0100 Subject: [PATCH 036/206] Suppress to switch to SLA-technology printer, if an object has a modifier(s) + WIP: ptSLA : Hide modifiers on 3DScene. Waiting for @enricoturri1966 review/reworking --- src/libslic3r/Model.cpp | 9 +++++++++ src/libslic3r/Model.hpp | 3 +++ src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++++ src/slic3r/GUI/GUI_App.cpp | 9 +++++++++ src/slic3r/GUI/Selection.cpp | 32 ++++++++++++++++++++++++++++++-- src/slic3r/GUI/Tab.cpp | 4 ++-- 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 76668d0d2..6572f7484 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2586,6 +2586,15 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); }); } +bool model_has_parameter_modifiers_in_objects(const Model &model) +{ + for (const auto& model_object : model.objects) + for (const auto& volume : model_object->volumes) + if (volume->is_modifier()) + return true; + return false; +} + bool model_has_multi_part_objects(const Model &model) { for (const ModelObject *model_object : model.objects) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 1cd6c3414..74d95b20f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1388,6 +1388,9 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo // The function assumes that volumes list is synchronized. extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); +// If the model has object(s) which contains a modofoer, then it is currently not supported by the SLA mode. +// Either the model cannot be loaded, or a SLA printer has to be activated. +bool model_has_parameter_modifiers_in_objects(const Model& model); // If the model has multi-part objects, then it is currently not supported by the SLA mode. // Either the model cannot be loaded, or a SLA printer has to be activated. bool model_has_multi_part_objects(const Model &model); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2aa96369d..ea6376463 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2049,6 +2049,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re raycaster->set_active(v->is_active); } + for (GLVolume* volume : m_volumes.volumes) + if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) { + if (volume->is_modifier && m_model->objects[volume->object_idx()]->volumes[volume->volume_idx()]->is_modifier()) + volume->is_active = printer_technology != ptSLA; + } + // refresh gizmo elements raycasters for picking GLGizmoBase* curr_gizmo = m_gizmos.get_current(); if (curr_gizmo != nullptr) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 161784d95..5f5d0384a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2884,6 +2884,14 @@ void GUI_App::open_web_page_localized(const std::string &http_address) // Because of we can't to print the multi-part objects with SLA technology. bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) { + if (model_has_parameter_modifiers_in_objects(model())) { + show_info(nullptr, + _L("It's impossible to print object(s) which contains parameter modifiers with SLA technology.") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } +/* if (model_has_multi_part_objects(model())) { show_info(nullptr, _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + @@ -2898,6 +2906,7 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) caption); return false; } +*/ return true; } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 31e09003c..a70698b43 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -157,6 +157,11 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec return; const GLVolume* volume = (*m_volumes)[volume_idx]; + + if (wxGetApp().plater()->printer_technology() == ptSLA && volume->is_modifier && + m_model->objects[volume->object_idx()]->volumes[volume->volume_idx()]->is_modifier()) + return; + // wipe tower is already selected if (is_wipe_tower() && volume->is_wipe_tower) return; @@ -482,8 +487,14 @@ void Selection::instances_changed(const std::vector &instance_ids_select assert(m_valid); assert(m_mode == Instance); m_list.clear(); + + const PrinterTechnology pt = wxGetApp().plater()->printer_technology(); + for (unsigned int volume_idx = 0; volume_idx < (unsigned int)m_volumes->size(); ++ volume_idx) { const GLVolume *volume = (*m_volumes)[volume_idx]; + if (pt == ptSLA && volume->is_modifier && + m_model->objects[volume->object_idx()]->volumes[volume->volume_idx()]->is_modifier()) + continue; auto it = std::lower_bound(instance_ids_selected.begin(), instance_ids_selected.end(), volume->geometry_id.second); if (it != instance_ids_selected.end() && *it == volume->geometry_id.second) this->do_add_volume(volume_idx); @@ -1940,9 +1951,16 @@ std::vector Selection::get_volume_idxs_from_object(unsigned int ob { std::vector idxs; + const PrinterTechnology pt = wxGetApp().plater()->printer_technology(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - if ((*m_volumes)[i]->object_idx() == (int)object_idx) + const GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == (int)object_idx) { + if (pt == ptSLA && v->is_modifier && + m_model->objects[object_idx]->volumes[v->volume_idx()]->is_modifier()) + continue; idxs.push_back(i); + } } return idxs; @@ -1952,8 +1970,13 @@ std::vector Selection::get_volume_idxs_from_instance(unsigned int { std::vector idxs; + const PrinterTechnology pt = wxGetApp().plater()->printer_technology(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { const GLVolume* v = (*m_volumes)[i]; + if (pt == ptSLA && v->is_modifier && + m_model->objects[object_idx]->volumes[i]->is_modifier()) + continue; if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) idxs.push_back(i); } @@ -2911,7 +2934,12 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const return false; unsigned int count = (unsigned int)std::count_if(m_list.begin(), m_list.end(), SameInstance(object_idx, volume->instance_idx(), *m_volumes)); - return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); + + PrinterTechnology pt = wxGetApp().plater()->printer_technology(); + const ModelVolumePtrs& volumes = m_model->objects[object_idx]->volumes; + const unsigned int vol_cnt = (unsigned int)std::count_if(volumes.begin(), volumes.end(), [pt](const ModelVolume* volume) { return pt == ptFFF || !volume->is_modifier(); }); + + return count == vol_cnt; } void Selection::paste_volumes_from_clipboard() diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d36c25d39..21529579b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3279,9 +3279,9 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset); PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); - /*if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset selected"))) + if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset selected"))) canceled = true; - else */{ + else { struct PresetUpdate { Preset::Type tab_type; PresetCollection *presets; From fa2fef9283f79e9f6806340a9169244b249a8ed4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 12:27:53 +0100 Subject: [PATCH 037/206] Optimize csg slicing with large number of positive parts --- src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 36 ++++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index f8a735420..76ca22ec4 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -4,6 +4,7 @@ #include "CSGMesh.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" namespace Slic3r { namespace csg { @@ -18,6 +19,8 @@ std::vector slice_csgmesh_ex( MeshSlicingParamsEx params_cpy = params; auto trafo = params.trafo; + auto nonempty_indices = reserve_vector(slicegrid.size()); + for (const auto &m : csg) { const indexed_triangle_set *its = csg::get_mesh(m); if (!its) @@ -30,13 +33,18 @@ std::vector slice_csgmesh_ex( assert(slices.size() == slicegrid.size()); + nonempty_indices.clear(); for (size_t i = 0; i < slicegrid.size(); ++i) { + if (get_operation(m) == CSGType::Intersection || !slices[i].empty()) + nonempty_indices.emplace_back(i); + } + + auto mergefn = [&m, &slices, &ret](size_t i){ switch(get_operation(m)) { case CSGType::Union: for (ExPolygon &expoly : slices[i]) ret[i].emplace_back(std::move(expoly)); - ret[i] = union_ex(ret[i]); break; case CSGType::Difference: ret[i] = diff_ex(ret[i], slices[i]); @@ -45,19 +53,25 @@ std::vector slice_csgmesh_ex( ret[i] = intersection_ex(ret[i], slices[i]); break; } - } + }; - for (ExPolygons &slice : ret) { - auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){ - return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON); - }); - - // Hopefully, ExPolygons are moved, not copied to new positions - // and that is cheap for expolygons - slice.erase(it, slice.end()); - } + execution::for_each(ex_tbb, + nonempty_indices.begin(), nonempty_indices.end(), + mergefn, + execution::max_concurrency(ex_tbb)); } + execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) { + auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){ + return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON); + }); + + // Hopefully, ExPolygons are moved, not copied to new positions + // and that is cheap for expolygons + slice.erase(it, slice.end()); + slice = union_ex(slice); + }, execution::max_concurrency(ex_tbb)); + return ret; } From 6323e27cdca4d3fabc2ee22d78dd7efb23adcadb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 13:05:02 +0100 Subject: [PATCH 038/206] csg voxelization speedup with additional parallelism --- src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index f1fd73981..0f2e8c898 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -5,6 +5,7 @@ #include "CSGMesh.hpp" #include "libslic3r/OpenVDBUtils.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" namespace Slic3r { namespace csg { @@ -32,11 +33,24 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, { VoxelGridPtr ret; + std::vector grids (csgrange.size()); + + execution::for_each(ex_tbb, size_t(0), csgrange.size(), [&](size_t csgidx) { + if (params.statusfn() && params.statusfn()(-1)) + return; + + auto it = csgrange.begin(); + std::advance(it, csgidx); + auto &csgpart = *it; + grids[csgidx] = get_voxelgrid(csgpart, params); + }, execution::max_concurrency(ex_tbb)); + + size_t csgidx = 0; for (auto &csgpart : csgrange) { if (params.statusfn() && params.statusfn()(-1)) break; - VoxelGridPtr partgrid = get_voxelgrid(csgpart, params); + auto &partgrid = grids[csgidx++]; if (!ret && get_operation(csgpart) == CSGType::Union) { ret = std::move(partgrid); From 7832d600ff095a41845be37afa3244bded4582ab Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 15 Dec 2022 13:21:57 +0100 Subject: [PATCH 039/206] CutGizmo: Allow to add connectors for SLA --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 29d578fba..599a54b3f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1627,12 +1627,10 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) if (auto oc = m_c->object_clipper(); oc && m_is_contour_changed) oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); - if (wxGetApp().plater()->printer_technology() == ptFFF) { - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); - if (m_imgui->button(_L("Add/Edit connectors"))) - set_connectors_editing(true); - m_imgui->disabled_end(); - } + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); + if (m_imgui->button(_L("Add/Edit connectors"))) + set_connectors_editing(true); + m_imgui->disabled_end(); ImGui::Separator(); @@ -1755,7 +1753,7 @@ void GLGizmoCut3D::render_input_window_warning() const { if (m_is_contour_changed) return; - if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector) { + if (m_has_invalid_connector) { wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":"; if (m_info_stats.outside_cut_contour > size_t(0)) out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour), From fdf51c8a5e49b6ee403722eef83288feab71687a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 13:50:19 +0100 Subject: [PATCH 040/206] Another fix for support points trafo --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 362912607..b117a2beb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -153,8 +153,8 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) return; double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); - Transform3d trafo(inst->get_transformation().get_matrix()); - trafo.translate(Vec3d{0., 0., shift_z}); + Transform3d trafo(inst->get_transformation().get_matrix() * inst->get_object()->volumes.front()->get_matrix()); + trafo.translation()(2) += shift_z; const Geometry::Transformation transformation{trafo}; #if ENABLE_WORLD_COORDINATE From b8b462df5ef772d679d9845017d6991d450f0606 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 14:07:01 +0100 Subject: [PATCH 041/206] Fix trafo for drillholes --- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index a209b51de..ec6ea0a1f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -117,11 +117,17 @@ void GLGizmoHollow::render_points(const Selection& selection) shader->start_using(); ScopeGuard guard([shader]() { shader->stop_using(); }); - const GLVolume* vol = selection.get_first_volume(); - const Transform3d trafo = vol->world_matrix(); + auto *inst = m_c->selection_info()->model_instance(); + if (!inst) + return; + + double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); + Transform3d trafo(inst->get_transformation().get_matrix() * inst->get_object()->volumes.front()->get_matrix()); + trafo.translation()(2) += shift_z; + const Geometry::Transformation transformation{trafo}; #if ENABLE_WORLD_COORDINATE - const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse(); + const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); #else const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); #endif // ENABLE_WORLD_COORDINATE @@ -152,7 +158,7 @@ void GLGizmoHollow::render_points(const Selection& selection) // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. const Transform3d hole_matrix = Geometry::translation_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; - if (vol->is_left_handed()) + if (transformation.is_left_handed()) glsafe(::glFrontFace(GL_CW)); // Matrices set, we can render the point mark now. @@ -166,7 +172,7 @@ void GLGizmoHollow::render_points(const Selection& selection) shader->set_uniform("view_normal_matrix", view_normal_matrix); m_cylinder.model.render(); - if (vol->is_left_handed()) + if (transformation.is_left_handed()) glsafe(::glFrontFace(GL_CCW)); } } From 9606473c16f1e877f235c1519b9a4b30c2ce617b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 16:06:56 +0100 Subject: [PATCH 042/206] Try to fix build on msvc (perl xs) --- src/libslic3r/Execution/ExecutionTBB.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Execution/ExecutionTBB.hpp b/src/libslic3r/Execution/ExecutionTBB.hpp index 2250b8e32..db4ee243f 100644 --- a/src/libslic3r/Execution/ExecutionTBB.hpp +++ b/src/libslic3r/Execution/ExecutionTBB.hpp @@ -53,7 +53,7 @@ public: I to, const T &init, MergeFn &&mergefn, - AccessFn &&access, + AccessFn &&accessfn, size_t granularity = 1 ) { @@ -61,7 +61,7 @@ public: tbb::blocked_range{from, to, granularity}, init, [&](const auto &range, T subinit) { T acc = subinit; - loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); }); + loop_(range, [&](auto &i) { acc = mergefn(acc, accessfn(i)); }); return acc; }, std::forward(mergefn)); From 743d431574ff5083099f1e0b7bb46926f4fcbc10 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 16 Dec 2022 10:49:37 +0100 Subject: [PATCH 043/206] Remove redundant bb calculation in AABBTree --- src/libslic3r/AABBMesh.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libslic3r/AABBMesh.cpp b/src/libslic3r/AABBMesh.cpp index b6dc68aae..ca7042c60 100644 --- a/src/libslic3r/AABBMesh.cpp +++ b/src/libslic3r/AABBMesh.cpp @@ -68,8 +68,6 @@ public: template void AABBMesh::init(const M &mesh, bool calculate_epsilon) { - BoundingBoxf3 bb = bounding_box(mesh); - // Build the AABB accelaration tree m_aabb->init(*m_tm, calculate_epsilon); } From e8faa71fcf36530ff218203fa872f23297f5eb5f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 19 Dec 2022 12:41:45 +0100 Subject: [PATCH 044/206] Decrease quality of preview meshes to improve processing time --- src/libslic3r/SLAPrintSteps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 630f6f85f..ff827b575 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -138,7 +138,7 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( bench.start(); // update preview mesh - double vscale = 1. / po.m_config.layer_height.getFloat(); + double vscale = 1. / (1.5 * po.m_config.layer_height.getFloat()); auto voxparams = csg::VoxelizeParams{} .voxel_scale(vscale) .exterior_bandwidth(1.f) From f997609db6607e77c4885592ff50e3aeb01b00d1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 19 Dec 2022 14:15:38 +0100 Subject: [PATCH 045/206] Fix stl export of object with unwanted pad --- src/slic3r/GUI/Plater.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8a9e93485..dbdd24d15 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6122,7 +6122,6 @@ void Plater::export_stl_obj(bool extended, bool selection_only) const bool is_left_handed = object->is_left_handed(); auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{}; - pad_mesh = object->pad_mesh(); pad_mesh.transform(mesh_trafo_inv); auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{}; From faf4d8e6b0d5d59468caef9c3bc075b58f7cc622 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 19 Dec 2022 18:49:53 +0100 Subject: [PATCH 046/206] Fix triangle orientation after openvdb mesh generation --- src/libslic3r/OpenVDBUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 2a94d38e0..44a5b172e 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -49,7 +49,7 @@ template VoxelGridPtr make_voxelgrid<>(); inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; } inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast(); } -inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } +inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[2]), int(v[1]), int(v[0])}; } class TriangleMeshDataAdapter { public: @@ -170,7 +170,7 @@ indexed_triangle_set grid_to_mesh(const VoxelGrid &vgrid, for (auto &v : triangles) ret.indices.emplace_back(to_vec3i(v)); for (auto &quad : quads) { ret.indices.emplace_back(quad(2), quad(1), quad(0)); - ret.indices.emplace_back(quad(0), quad(3), quad(2)); + ret.indices.emplace_back(quad(3), quad(2), quad(0)); } return ret; From 8470d4594de589e7b5eda0fd710c693ff99e2f40 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 19 Dec 2022 18:50:16 +0100 Subject: [PATCH 047/206] Fix crash in debug mode with empty preview --- src/slic3r/GUI/GLCanvas3D.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ea6376463..419e5fbc9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6648,14 +6648,17 @@ void GLCanvas3D::_load_sla_shells() for (const SLAPrintObject* obj : print->objects()) { unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); for (const SLAPrintObject::Instance& instance : obj->instances()) { - add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); - // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when - // through the update_volumes_colors_by_extruder() call. - m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); - if (auto &tree_mesh = obj->support_mesh(); !tree_mesh.empty()) - add_volume(*obj, -int(slaposSupportTree), instance, tree_mesh, GLVolume::SLA_SUPPORT_COLOR, true); - if (auto &pad_mesh = obj->pad_mesh(); !pad_mesh.empty()) - add_volume(*obj, -int(slaposPad), instance, pad_mesh, GLVolume::SLA_PAD_COLOR, false); + auto & m = obj->get_mesh_to_print(); + if (!m.empty()) { + add_volume(*obj, 0, instance, m, GLVolume::MODEL_COLOR[0], true); + // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when + // through the update_volumes_colors_by_extruder() call. + m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); + if (auto &tree_mesh = obj->support_mesh(); !tree_mesh.empty()) + add_volume(*obj, -int(slaposSupportTree), instance, tree_mesh, GLVolume::SLA_SUPPORT_COLOR, true); + if (auto &pad_mesh = obj->pad_mesh(); !pad_mesh.empty()) + add_volume(*obj, -int(slaposPad), instance, pad_mesh, GLVolume::SLA_PAD_COLOR, false); + } } double shift_z = obj->get_current_elevation(); for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { From 495a3f35573c2b723257230272727a36fc15dfc4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 20 Dec 2022 11:25:48 +0100 Subject: [PATCH 048/206] Fix unshifted cut slices in sla gizmos --- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index f0734b445..ac3750871 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -119,7 +119,7 @@ void SelectionInfo::on_update() if (m_model_object) m_print_object = get_pool()->get_canvas()->sla_print()->get_print_object_by_model_object_id(m_model_object->id()); - m_z_shift = selection.get_first_volume()->get_sla_shift_z(); + m_z_shift = m_print_object ? m_print_object->get_current_elevation() : selection.get_first_volume()->get_sla_shift_z(); } } From d3a4edd37f3d494c8f894afe05bda667ae4decfd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 20 Dec 2022 12:51:04 +0100 Subject: [PATCH 049/206] Skip voxelization for csg meshes with positive parts only. --- src/libslic3r/SLAPrintSteps.cpp | 47 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index ff827b575..e92cc6c54 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -130,15 +130,49 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin } } +template bool is_all_positive(const Cont &csgmesh) +{ + bool is_all_pos = + std::all_of(csgmesh.begin(), + csgmesh.end(), + [](auto &part) { + return csg::get_operation(part) == csg::CSGType::Union; + }); + + return is_all_pos; +} + +template +static indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh) +{ + indexed_triangle_set m; + for (auto &csgpart : csgmesh) { + auto op = csg::get_operation(csgpart); + const indexed_triangle_set * pmesh = csg::get_mesh(csgpart); + if (pmesh && op == csg::CSGType::Union) { + indexed_triangle_set mcpy = *pmesh; + its_transform(mcpy, csg::get_transform(csgpart)); + its_merge(m, mcpy); + } + } + + return m; +} + indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( SLAPrintObject &po, SLAPrintObjectStep step) { + // Empirical upper limit to not get excessive performance hit + constexpr double MaxPreviewVoxelScale = 18.; + Benchmark bench; bench.start(); // update preview mesh - double vscale = 1. / (1.5 * po.m_config.layer_height.getFloat()); + double vscale = std::min(MaxPreviewVoxelScale, + 1. / po.m_config.layer_height.getFloat()); + auto voxparams = csg::VoxelizeParams{} .voxel_scale(vscale) .exterior_bandwidth(1.f) @@ -150,14 +184,13 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( auto r = range(po.m_mesh_to_slice); auto m = indexed_triangle_set{}; - if (r.size() > 1) { + if (r.size() > 1 && !is_all_positive(r)) { auto grid = csg::voxelize_csgmesh(r, voxparams); m = grid ? grid_to_mesh(*grid) : indexed_triangle_set{}; - } else if (!r.empty()) { - m = *(csg::get_mesh(*r.begin())); - auto tr = csg::get_transform(*r.begin()); - for (auto &v : m.vertices) - v = tr * v; +// float loss_less_max_error = 1e-6; +// its_quadric_edge_collapse(m, 0U, &loss_less_max_error); + } else { + m = csgmesh_merge_positive_parts(r); } bench.stop(); From 4fabd0b3072bb11f208b792b70f284be3dacc469 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 20 Dec 2022 12:51:55 +0100 Subject: [PATCH 050/206] Delete hollowing interior grid after the mesh is extracted It was needed for the triangle trimming but that is gone now --- src/libslic3r/SLAPrintSteps.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index e92cc6c54..215a3bdce 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -277,7 +277,10 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); po.m_hollowing_data->interior = std::move(interior); - indexed_triangle_set &m = sla::get_mesh(*po.m_hollowing_data->interior); + indexed_triangle_set m = sla::get_mesh(*po.m_hollowing_data->interior); + + // Release the data, won't be needed anymore, takes huge amount of ram + po.m_hollowing_data->interior.reset(); if (!m.empty()) { // simplify mesh lossless @@ -291,8 +294,7 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) // Put the interior into the target mesh as a negative po.m_mesh_to_slice .emplace(slaposHollowing, - csg::CSGPart{&sla::get_mesh(*po.m_hollowing_data->interior), - csg::CSGType::Difference}); + csg::CSGPart{std::make_unique(std::move(m)), csg::CSGType::Difference}); generate_preview(po, slaposHollowing); } From 25ca46e3eb14b2349fba7b5c9980f99de94fe9ec Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 20 Dec 2022 17:38:46 +0100 Subject: [PATCH 051/206] Add support for csg operation stacking --- src/libslic3r/CSGMesh/CSGMesh.hpp | 12 +++ src/libslic3r/CSGMesh/ModelToCSGMesh.hpp | 41 ++++++-- src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 117 ++++++++++++++++------ src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 49 +++++++-- src/libslic3r/SLAPrint.cpp | 2 +- src/libslic3r/SLAPrintSteps.cpp | 13 ++- 6 files changed, 185 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp index 4655f684a..f30905066 100644 --- a/src/libslic3r/CSGMesh/CSGMesh.hpp +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -18,6 +18,7 @@ namespace Slic3r { namespace csg { // Supported CSG operation types enum class CSGType { Union, Difference, Intersection }; +enum class CSGStackOp { Push, Continue, Pop }; // Get the CSG operation of the part. Can be overriden for any type template CSGType get_operation(const CSGPartT &part) @@ -25,6 +26,11 @@ template CSGType get_operation(const CSGPartT &part) return part.operation; } +template CSGStackOp get_stack_operation(const CSGPartT &part) +{ + return part.stack_operation; +} + // Get the mesh for the part. Can be overriden for any type template const indexed_triangle_set *get_mesh(const CSGPartT &part) @@ -48,6 +54,11 @@ inline CSGType get_operation(const indexed_triangle_set &part) return CSGType::Union; } +inline CSGStackOp get_stack_operation(const indexed_triangle_set &part) +{ + return CSGStackOp::Continue; +} + inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part) { return ∂ @@ -63,6 +74,7 @@ struct CSGPart { AnyPtr its_ptr; Transform3f trafo; CSGType operation; + CSGStackOp stack_operation; CSGPart(AnyPtr ptr = {}, CSGType op = CSGType::Union, diff --git a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp index f7247ea70..3d1941294 100644 --- a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp @@ -5,13 +5,15 @@ #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" +#include "libslic3r/MeshSplitImpl.hpp" namespace Slic3r { namespace csg { enum ModelParts { mpartsPositive = 1, mpartsNegative = 2, - mpartsDrillHoles = 4 + mpartsDrillHoles = 4, + mpartsDoSplits = 8 }; template @@ -22,20 +24,43 @@ void model_to_csgmesh(const ModelObject &mo, int parts_to_include = mpartsPositive ) { - bool do_positives = parts_to_include & mpartsPositive; - bool do_negatives = parts_to_include & mpartsNegative; + bool do_positives = parts_to_include & mpartsPositive; + bool do_negatives = parts_to_include & mpartsNegative; bool do_drillholes = parts_to_include & mpartsDrillHoles; + bool do_splits = parts_to_include & mpartsDoSplits; for (const ModelVolume *vol : mo.volumes) { if (vol && vol->mesh_ptr() && ((do_positives && vol->is_model_part()) || (do_negatives && vol->is_negative_volume()))) { - CSGPart part{&(vol->mesh().its), - vol->is_model_part() ? CSGType::Union : CSGType::Difference, - (trafo * vol->get_matrix()).cast()}; - *out = std::move(part); - ++out; + if (do_splits && its_is_splittable(vol->mesh().its)) { + CSGPart part_begin{{}, vol->is_model_part() ? CSGType::Union : CSGType::Difference}; + part_begin.stack_operation = CSGStackOp::Push; + *out = std::move(part_begin); + ++out; + + its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) { + CSGPart part{std::make_unique(std::move(its)), + CSGType::Union, + (trafo * vol->get_matrix()).cast()}; + + *out = std::move(part); + ++out; + }}); + + CSGPart part_end{{}}; + part_end.stack_operation = CSGStackOp::Pop; + *out = std::move(part_end); + ++out; + } else { + CSGPart part{&(vol->mesh().its), + vol->is_model_part() ? CSGType::Union : CSGType::Difference, + (trafo * vol->get_matrix()).cast()}; + + *out = std::move(part); + ++out; + } } } diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index 76ca22ec4..06302faa4 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -2,6 +2,9 @@ #define SLICECSGMESH_HPP #include "CSGMesh.hpp" + +#include + #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Execution/ExecutionTBB.hpp" @@ -10,57 +13,107 @@ namespace Slic3r { namespace csg { template std::vector slice_csgmesh_ex( - const Range &csg, + const Range &csgrange, const std::vector &slicegrid, const MeshSlicingParamsEx ¶ms, const std::function &throw_on_cancel = [] {}) { - std::vector ret(slicegrid.size()); + struct Frame { CSGType op; std::vector slices; }; + + std::stack opstack{std::vector{}}; MeshSlicingParamsEx params_cpy = params; auto trafo = params.trafo; auto nonempty_indices = reserve_vector(slicegrid.size()); - for (const auto &m : csg) { - const indexed_triangle_set *its = csg::get_mesh(m); - if (!its) - continue; + if (!csgrange.empty() && csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) + opstack.push({CSGType::Union, std::vector(slicegrid.size())}); - params_cpy.trafo = trafo * csg::get_transform(m).template cast(); - std::vector slices = slice_mesh_ex(*its, - slicegrid, params_cpy, - throw_on_cancel); + for (const auto &csgpart : csgrange) { + const indexed_triangle_set *its = csg::get_mesh(csgpart); - assert(slices.size() == slicegrid.size()); + auto op = get_operation(csgpart); - nonempty_indices.clear(); - for (size_t i = 0; i < slicegrid.size(); ++i) { - if (get_operation(m) == CSGType::Intersection || !slices[i].empty()) - nonempty_indices.emplace_back(i); + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push({op, std::vector(slicegrid.size())}); + op = CSGType::Union; } - auto mergefn = [&m, &slices, &ret](size_t i){ - switch(get_operation(m)) { - case CSGType::Union: - for (ExPolygon &expoly : slices[i]) - ret[i].emplace_back(std::move(expoly)); + Frame *top = &opstack.top(); - break; - case CSGType::Difference: - ret[i] = diff_ex(ret[i], slices[i]); - break; - case CSGType::Intersection: - ret[i] = intersection_ex(ret[i], slices[i]); - break; + if (its) { + params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast(); + std::vector slices = slice_mesh_ex(*its, + slicegrid, params_cpy, + throw_on_cancel); + + assert(slices.size() == slicegrid.size()); + + nonempty_indices.clear(); + for (size_t i = 0; i < slicegrid.size(); ++i) { + if (op == CSGType::Intersection || !slices[i].empty()) + nonempty_indices.emplace_back(i); } - }; - execution::for_each(ex_tbb, - nonempty_indices.begin(), nonempty_indices.end(), - mergefn, - execution::max_concurrency(ex_tbb)); + auto mergefn = [&csgpart, &slices, &top](size_t i){ + switch(get_operation(csgpart)) { + case CSGType::Union: + for (ExPolygon &expoly : slices[i]) + top->slices[i].emplace_back(std::move(expoly)); + + break; + case CSGType::Difference: + top->slices[i] = diff_ex(top->slices[i], slices[i]); + break; + case CSGType::Intersection: + top->slices[i] = intersection_ex(top->slices[i], slices[i]); + break; + } + }; + + execution::for_each(ex_tbb, + nonempty_indices.begin(), nonempty_indices.end(), + mergefn, + execution::max_concurrency(ex_tbb)); + } + + if (get_stack_operation(csgpart) == CSGStackOp::Pop) { + std::vector popslices = std::move(top->slices); + auto popop = opstack.top().op; + opstack.pop(); + std::vector &prev_slices = opstack.top().slices; + + nonempty_indices.clear(); + for (size_t i = 0; i < slicegrid.size(); ++i) { + if (popop == CSGType::Intersection || !popslices[i].empty()) + nonempty_indices.emplace_back(i); + } + + auto mergefn2 = [&popslices, &prev_slices, popop](size_t i){ + switch(popop) { + case CSGType::Union: + for (ExPolygon &expoly : popslices[i]) + prev_slices[i].emplace_back(std::move(expoly)); + + break; + case CSGType::Difference: + prev_slices[i] = diff_ex(prev_slices[i], popslices[i]); + break; + case CSGType::Intersection: + prev_slices[i] = intersection_ex(prev_slices[i], popslices[i]); + break; + } + }; + + execution::for_each(ex_tbb, + nonempty_indices.begin(), nonempty_indices.end(), + mergefn2, + execution::max_concurrency(ex_tbb)); + } } + std::vector ret = std::move(opstack.top().slices); + execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) { auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){ return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON); diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index 0f2e8c898..ac54cf922 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -2,6 +2,7 @@ #define VOXELIZECSGMESH_HPP #include +#include #include "CSGMesh.hpp" #include "libslic3r/OpenVDBUtils.hpp" @@ -46,29 +47,65 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, }, execution::max_concurrency(ex_tbb)); size_t csgidx = 0; + struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; }; + std::stack opstack{std::vector{}}; + + if (!csgrange.empty() && csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) + opstack.push({}); + for (auto &csgpart : csgrange) { if (params.statusfn() && params.statusfn()(-1)) break; auto &partgrid = grids[csgidx++]; - if (!ret && get_operation(csgpart) == CSGType::Union) { - ret = std::move(partgrid); - } else if (ret) { + auto op = get_operation(csgpart); + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push({op, nullptr}); + op = CSGType::Union; + } + + Frame *top = &opstack.top(); + + if (!top->grid && op == CSGType::Union) { + top->grid = std::move(partgrid); + } else if (top->grid && partgrid) { switch (get_operation(csgpart)) { case CSGType::Union: - grid_union(*ret, *partgrid); + grid_union(*(top->grid), *partgrid); break; case CSGType::Difference: - grid_difference(*ret, *partgrid); + grid_difference(*(top->grid), *partgrid); break; case CSGType::Intersection: - grid_intersection(*ret, *partgrid); + grid_intersection(*(top->grid), *partgrid); + break; + } + } + + if (get_stack_operation(csgpart) == CSGStackOp::Pop) { + VoxelGridPtr popgrid = std::move(top->grid); + auto popop = opstack.top().op; + opstack.pop(); + VoxelGridPtr &grid = opstack.top().grid; + + switch (popop) { + case CSGType::Union: + grid_union(*grid, *popgrid); + break; + case CSGType::Difference: + grid_difference(*grid, *popgrid); + break; + case CSGType::Intersection: + grid_intersection(*grid, *popgrid); break; } } } + ret = std::move(opstack.top().grid); + return ret; } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index dc05148fc..f37638fb4 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1114,7 +1114,7 @@ VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, VoxelizeParams p) { VoxelGridPtr &ret = part.gridcache[p]; - if (!ret) { + if (!ret && csg::get_mesh(part)) { p.trafo(csg::get_transform(part)); ret = mesh_to_grid(*csg::get_mesh(part), p); } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 215a3bdce..623dee4e6 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -242,7 +242,7 @@ void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po) csg::model_to_csgmesh(*po.model_object(), po.trafo(), csg_inserter{po.m_mesh_to_slice, slaposAssembly}, - csg::mpartsPositive | csg::mpartsNegative); + csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits); generate_preview(po, slaposAssembly); } @@ -352,9 +352,18 @@ template BoundingBoxf3 csgmesh_positive_bb(const Cont &csg) bounding_box(*csg::get_mesh(*csg.begin()), csg::get_transform(*csg.begin())); + bool skip = false; for (const auto &m : csg) { - if (m.operation == csg::CSGType::Union) + auto op = csg::get_operation(m); + auto stackop = csg::get_stack_operation(m); + if (stackop == csg::CSGStackOp::Push && op != csg::CSGType::Union) + skip = true; + + if (!skip && csg::get_mesh(m) && op == csg::CSGType::Union) bb3d.merge(bounding_box(*csg::get_mesh(m), csg::get_transform(m))); + + if (stackop == csg::CSGStackOp::Pop) + skip = false; } return bb3d; From d75b951c39e8219b1f3deab8bf95a4719f8cabc3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Dec 2022 13:53:33 +0100 Subject: [PATCH 052/206] FInish csg operation stacking in cgal side Broken slicing --- .../CSGMesh/PerformCSGMeshBooleans.hpp | 148 +++++++++++++++--- src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 99 ++++++------ src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 49 +++--- src/libslic3r/SLAPrintSteps.cpp | 10 +- 4 files changed, 213 insertions(+), 93 deletions(-) diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index da1ddce3f..733cdd1fe 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -1,8 +1,12 @@ #ifndef PERFORMCSGMESHBOOLEANS_HPP #define PERFORMCSGMESHBOOLEANS_HPP +#include +#include + #include "CSGMesh.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" #include "libslic3r/MeshBoolean.hpp" namespace Slic3r { namespace csg { @@ -24,26 +28,134 @@ MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart) return ret; } -template -void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMesh &cgalm, - const Range &csgparts) +namespace detail_cgal { + +using MeshBoolean::cgal::CGALMeshPtr; + +inline void perform_csg(CSGType op, CGALMeshPtr &dst, CGALMeshPtr &src) { - for (auto &csgpart : csgparts) { - auto m = get_cgalmesh(csgpart); - if (m) { - switch (get_operation(csgpart)) { - case CSGType::Union: - MeshBoolean::cgal::plus(cgalm, *m); - break; - case CSGType::Difference: - MeshBoolean::cgal::minus(cgalm, *m); - break; - case CSGType::Intersection: - MeshBoolean::cgal::intersect(cgalm, *m); - break; - } + switch (op) { + case CSGType::Union: + MeshBoolean::cgal::plus(*dst, *src); + break; + case CSGType::Difference: + MeshBoolean::cgal::minus(*dst, *src); + break; + case CSGType::Intersection: + MeshBoolean::cgal::intersect(*dst, *src); + break; + } +} + +template +std::vector get_cgalptrs(Ex policy, const Range &csgrange) +{ + std::vector ret(csgrange.size()); + execution::for_each(policy, size_t(0), csgrange.size(), + [&csgrange, &ret](size_t i) { + auto it = csgrange.begin(); + std::advance(it, i); + auto &csgpart = *it; + ret[i] = get_cgalmesh(csgpart); + }); + + return ret; +} + +} // namespace detail + +template +void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMeshPtr &cgalm, + const Range &csgrange) +{ + using MeshBoolean::cgal::CGALMesh; + using MeshBoolean::cgal::CGALMeshPtr; + using namespace detail_cgal; + + struct Frame { CSGType op = CSGType::Union; CGALMeshPtr cgalptr; }; + + std::stack opstack{std::vector{}}; + + if (!csgrange.empty() && + csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) + opstack.push({}); + + std::vector cgalmeshes = get_cgalptrs(ex_tbb, csgrange); + + size_t csgidx = 0; + for (auto &csgpart : csgrange) { + + auto op = get_operation(csgpart); + CGALMeshPtr &cgalptr = cgalmeshes[csgidx++]; + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push({op, nullptr}); + op = CSGType::Union; + } + + Frame *top = &opstack.top(); + + if (!top->cgalptr && op == CSGType::Union) { + top->cgalptr = std::move(cgalptr); + } else if (top->cgalptr && cgalptr) { + perform_csg(get_operation(csgpart), top->cgalptr, cgalptr); + } + + if (get_stack_operation(csgpart) == CSGStackOp::Pop) { + CGALMeshPtr src = std::move(top->cgalptr); + auto popop = opstack.top().op; + opstack.pop(); + CGALMeshPtr &dst = opstack.top().cgalptr; + perform_csg(popop, dst, src); } } + + cgalm = std::move(opstack.top().cgalptr); +} + +template +It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) +{ + using namespace detail_cgal; + + std::vector cgalmeshes(csgrange.size()); + auto check_part = [&csgrange, &cgalmeshes](size_t i) + { + auto it = csgrange.begin(); + std::advance(it, i); + auto &csgpart = *it; + auto m = get_cgalmesh(csgpart); + + if (!m || MeshBoolean::cgal::empty(*m)) + return; + + if (!MeshBoolean::cgal::does_bound_a_volume(*m) || + MeshBoolean::cgal::does_self_intersect(*m)) + return; + + cgalmeshes[i] = std::move(m); + }; + execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part); + + It ret = csgrange.end(); + for (size_t i = 0; i < csgrange.size(); ++i) { + if (!cgalmeshes[i]) { + auto it = csgrange.begin(); + std::advance(it, i); + vfn(it); + + if (it == csgrange.end()) + ret = it; + } + } + + return csgrange.end(); +} + +template +It check_csgmesh_booleans(const Range &csgrange) +{ + return check_csgmesh_booleans(csgrange, [](auto &) {}); } template @@ -51,7 +163,7 @@ MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range &csgpart { auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}); if (ret) - perform_csgmesh_booleans(*ret, csgparts); + perform_csgmesh_booleans(ret, csgparts); return ret; } diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index 06302faa4..d9df97e95 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -11,6 +11,40 @@ namespace Slic3r { namespace csg { +namespace detail { + +void merge_slices(csg::CSGType op, size_t i, + std::vector &target, + std::vector &source) +{ + switch(op) { + case CSGType::Union: + for (ExPolygon &expoly : source[i]) + target[i].emplace_back(std::move(expoly)); + break; + case CSGType::Difference: + target[i] = diff_ex(target[i], source[i]); + break; + case CSGType::Intersection: + target[i] = intersection_ex(target[i], source[i]); + break; + } +} + +void collect_nonempty_indices(csg::CSGType op, + const std::vector &slicegrid, + const std::vector &slices, + std::vector indices) +{ + indices.clear(); + for (size_t i = 0; i < slicegrid.size(); ++i) { + if (op == CSGType::Intersection || !slices[i].empty()) + indices.emplace_back(i); + } +} + +} // namespace detail + template std::vector slice_csgmesh_ex( const Range &csgrange, @@ -18,6 +52,8 @@ std::vector slice_csgmesh_ex( const MeshSlicingParamsEx ¶ms, const std::function &throw_on_cancel = [] {}) { + using namespace detail; + struct Frame { CSGType op; std::vector slices; }; std::stack opstack{std::vector{}}; @@ -49,32 +85,13 @@ std::vector slice_csgmesh_ex( assert(slices.size() == slicegrid.size()); - nonempty_indices.clear(); - for (size_t i = 0; i < slicegrid.size(); ++i) { - if (op == CSGType::Intersection || !slices[i].empty()) - nonempty_indices.emplace_back(i); - } + collect_nonempty_indices(op, slicegrid, slices, nonempty_indices); - auto mergefn = [&csgpart, &slices, &top](size_t i){ - switch(get_operation(csgpart)) { - case CSGType::Union: - for (ExPolygon &expoly : slices[i]) - top->slices[i].emplace_back(std::move(expoly)); - - break; - case CSGType::Difference: - top->slices[i] = diff_ex(top->slices[i], slices[i]); - break; - case CSGType::Intersection: - top->slices[i] = intersection_ex(top->slices[i], slices[i]); - break; - } - }; - - execution::for_each(ex_tbb, - nonempty_indices.begin(), nonempty_indices.end(), - mergefn, - execution::max_concurrency(ex_tbb)); + execution::for_each( + ex_tbb, nonempty_indices.begin(), nonempty_indices.end(), + [op, &slices, &top](size_t i) { + merge_slices(op, i, top->slices, slices); + }, execution::max_concurrency(ex_tbb)); } if (get_stack_operation(csgpart) == CSGStackOp::Pop) { @@ -83,37 +100,19 @@ std::vector slice_csgmesh_ex( opstack.pop(); std::vector &prev_slices = opstack.top().slices; - nonempty_indices.clear(); - for (size_t i = 0; i < slicegrid.size(); ++i) { - if (popop == CSGType::Intersection || !popslices[i].empty()) - nonempty_indices.emplace_back(i); - } + collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices); - auto mergefn2 = [&popslices, &prev_slices, popop](size_t i){ - switch(popop) { - case CSGType::Union: - for (ExPolygon &expoly : popslices[i]) - prev_slices[i].emplace_back(std::move(expoly)); - - break; - case CSGType::Difference: - prev_slices[i] = diff_ex(prev_slices[i], popslices[i]); - break; - case CSGType::Intersection: - prev_slices[i] = intersection_ex(prev_slices[i], popslices[i]); - break; - } - }; - - execution::for_each(ex_tbb, - nonempty_indices.begin(), nonempty_indices.end(), - mergefn2, - execution::max_concurrency(ex_tbb)); + execution::for_each( + ex_tbb, nonempty_indices.begin(), nonempty_indices.end(), + [&popslices, &prev_slices, popop](size_t i) { + merge_slices(popop, i, prev_slices, popslices); + }, execution::max_concurrency(ex_tbb)); } } std::vector ret = std::move(opstack.top().slices); + // TODO: verify if this part can be omitted or not. execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) { auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){ return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON); diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index ac54cf922..43019ef85 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -28,10 +28,31 @@ VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params) return ret; } +namespace detail { + +inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src) +{ + switch (op) { + case CSGType::Union: + grid_union(*dst, *src); + break; + case CSGType::Difference: + grid_difference(*dst, *src); + break; + case CSGType::Intersection: + grid_intersection(*dst, *src); + break; + } +} + +} // namespace detail + template VoxelGridPtr voxelize_csgmesh(const Range &csgrange, const VoxelizeParams ¶ms = {}) { + using namespace detail; + VoxelGridPtr ret; std::vector grids (csgrange.size()); @@ -50,7 +71,8 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; }; std::stack opstack{std::vector{}}; - if (!csgrange.empty() && csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) + if (!csgrange.empty() && + csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) opstack.push({}); for (auto &csgpart : csgrange) { @@ -71,17 +93,7 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, if (!top->grid && op == CSGType::Union) { top->grid = std::move(partgrid); } else if (top->grid && partgrid) { - switch (get_operation(csgpart)) { - case CSGType::Union: - grid_union(*(top->grid), *partgrid); - break; - case CSGType::Difference: - grid_difference(*(top->grid), *partgrid); - break; - case CSGType::Intersection: - grid_intersection(*(top->grid), *partgrid); - break; - } + perform_csg(get_operation(csgpart), top->grid, partgrid); } if (get_stack_operation(csgpart) == CSGStackOp::Pop) { @@ -89,18 +101,7 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, auto popop = opstack.top().op; opstack.pop(); VoxelGridPtr &grid = opstack.top().grid; - - switch (popop) { - case CSGType::Union: - grid_union(*grid, *popgrid); - break; - case CSGType::Difference: - grid_difference(*grid, *popgrid); - break; - case CSGType::Intersection: - grid_intersection(*grid, *popgrid); - break; - } + perform_csg(popop, grid, popgrid); } } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 623dee4e6..4e34ffbed 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -206,7 +206,15 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) { - po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; + auto r = range(po.m_mesh_to_slice); + if (csg::check_csgmesh_booleans(r) == r.end()) { + auto cgalmesh = csg::perform_csgmesh_booleans(r); + po.m_preview_meshes[step] = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalmesh); + } else { + po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + L("Can't do proper mesh booleans!")); + po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; + } for (size_t i = size_t(step) + 1; i < slaposCount; ++i) { From 1efc8191a23248c17d33a25e9babd465ac20c3f7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Dec 2022 18:09:31 +0100 Subject: [PATCH 053/206] Full support of csg op stack. Preferring cgal in preview if feasible --- src/libslic3r/CSGMesh/CSGMesh.hpp | 5 +- .../CSGMesh/PerformCSGMeshBooleans.hpp | 67 +++++++++++++----- src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 5 +- src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 15 ++-- src/libslic3r/MeshBoolean.cpp | 45 ++++++++++-- src/libslic3r/MeshBoolean.hpp | 7 +- src/libslic3r/SLA/Hollowing.hpp | 27 ++++++-- src/libslic3r/SLAPrintSteps.cpp | 69 +++++++++---------- 8 files changed, 162 insertions(+), 78 deletions(-) diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp index f30905066..f44119c92 100644 --- a/src/libslic3r/CSGMesh/CSGMesh.hpp +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -79,7 +79,10 @@ struct CSGPart { CSGPart(AnyPtr ptr = {}, CSGType op = CSGType::Union, const Transform3f &tr = Transform3f::Identity()) - : its_ptr{std::move(ptr)}, operation{op}, trafo{tr} + : its_ptr{std::move(ptr)} + , operation{op} + , stack_operation{CSGStackOp::Continue} + , trafo{tr} {} }; diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index 733cdd1fe..0a880c8fa 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -7,6 +7,7 @@ #include "CSGMesh.hpp" #include "libslic3r/Execution/ExecutionTBB.hpp" +//#include "libslic3r/Execution/ExecutionSeq.hpp" #include "libslic3r/MeshBoolean.hpp" namespace Slic3r { namespace csg { @@ -17,12 +18,21 @@ template MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart) { const indexed_triangle_set *its = csg::get_mesh(csgpart); + indexed_triangle_set dummy; + + if (!its) + its = &dummy; + MeshBoolean::cgal::CGALMeshPtr ret; - if (its) { - indexed_triangle_set m = *its; - its_transform(m, get_transform(csgpart)); + indexed_triangle_set m = *its; + its_transform(m, get_transform(csgpart)); + + try { ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m); + } catch (...) { + // errors are ignored, simply return null + ret = nullptr; } return ret; @@ -34,6 +44,14 @@ using MeshBoolean::cgal::CGALMeshPtr; inline void perform_csg(CSGType op, CGALMeshPtr &dst, CGALMeshPtr &src) { + if (!dst && op == CSGType::Union && src) { + dst = std::move(src); + return; + } + + if (!dst || !src) + return; + switch (op) { case CSGType::Union: MeshBoolean::cgal::plus(*dst, *src); @@ -72,13 +90,17 @@ void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMeshPtr &cgalm, using MeshBoolean::cgal::CGALMeshPtr; using namespace detail_cgal; - struct Frame { CSGType op = CSGType::Union; CGALMeshPtr cgalptr; }; + struct Frame { + CSGType op; CGALMeshPtr cgalptr; + explicit Frame(CSGType csgop = CSGType::Union) + : op{csgop} + , cgalptr{MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{})} + {} + }; std::stack opstack{std::vector{}}; - if (!csgrange.empty() && - csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) - opstack.push({}); + opstack.push(Frame{}); std::vector cgalmeshes = get_cgalptrs(ex_tbb, csgrange); @@ -89,17 +111,13 @@ void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMeshPtr &cgalm, CGALMeshPtr &cgalptr = cgalmeshes[csgidx++]; if (get_stack_operation(csgpart) == CSGStackOp::Push) { - opstack.push({op, nullptr}); + opstack.push(Frame{op}); op = CSGType::Union; } Frame *top = &opstack.top(); - if (!top->cgalptr && op == CSGType::Union) { - top->cgalptr = std::move(cgalptr); - } else if (top->cgalptr && cgalptr) { - perform_csg(get_operation(csgpart), top->cgalptr, cgalptr); - } + perform_csg(get_operation(csgpart), top->cgalptr, cgalptr); if (get_stack_operation(csgpart) == CSGStackOp::Pop) { CGALMeshPtr src = std::move(top->cgalptr); @@ -126,12 +144,23 @@ It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) auto &csgpart = *it; auto m = get_cgalmesh(csgpart); - if (!m || MeshBoolean::cgal::empty(*m)) + // mesh can be nullptr if this is a stack push or pull + if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) { + cgalmeshes[i] = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}); return; + } - if (!MeshBoolean::cgal::does_bound_a_volume(*m) || - MeshBoolean::cgal::does_self_intersect(*m)) - return; + try { + if (!m || MeshBoolean::cgal::empty(*m)) + return; + + if (!MeshBoolean::cgal::does_bound_a_volume(*m)) + return; + + if (MeshBoolean::cgal::does_self_intersect(*m)) + return; + } + catch (...) { return; } cgalmeshes[i] = std::move(m); }; @@ -144,12 +173,12 @@ It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) std::advance(it, i); vfn(it); - if (it == csgrange.end()) + if (ret == csgrange.end()) ret = it; } } - return csgrange.end(); + return ret; } template diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index d9df97e95..041acedb0 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -34,7 +34,7 @@ void merge_slices(csg::CSGType op, size_t i, void collect_nonempty_indices(csg::CSGType op, const std::vector &slicegrid, const std::vector &slices, - std::vector indices) + std::vector &indices) { indices.clear(); for (size_t i = 0; i < slicegrid.size(); ++i) { @@ -62,8 +62,7 @@ std::vector slice_csgmesh_ex( auto trafo = params.trafo; auto nonempty_indices = reserve_vector(slicegrid.size()); - if (!csgrange.empty() && csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) - opstack.push({CSGType::Union, std::vector(slicegrid.size())}); + opstack.push({CSGType::Union, std::vector(slicegrid.size())}); for (const auto &csgpart : csgrange) { const indexed_triangle_set *its = csg::get_mesh(csgpart); diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index 43019ef85..c76187723 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -32,6 +32,9 @@ namespace detail { inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src) { + if (!dst || !src) + return; + switch (op) { case CSGType::Union: grid_union(*dst, *src); @@ -71,9 +74,7 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; }; std::stack opstack{std::vector{}}; - if (!csgrange.empty() && - csg::get_stack_operation(*csgrange.begin()) != CSGStackOp::Push) - opstack.push({}); + opstack.push({CSGType::Union, mesh_to_grid({}, params)}); for (auto &csgpart : csgrange) { if (params.statusfn() && params.statusfn()(-1)) @@ -84,17 +85,13 @@ VoxelGridPtr voxelize_csgmesh(const Range &csgrange, auto op = get_operation(csgpart); if (get_stack_operation(csgpart) == CSGStackOp::Push) { - opstack.push({op, nullptr}); + opstack.push({op, mesh_to_grid({}, params)}); op = CSGType::Union; } Frame *top = &opstack.top(); - if (!top->grid && op == CSGType::Union) { - top->grid = std::move(partgrid); - } else if (top->grid && partgrid) { - perform_csg(get_operation(csgpart), top->grid, partgrid); - } + perform_csg(get_operation(csgpart), top->grid, partgrid); if (get_stack_operation(csgpart) == CSGStackOp::Pop) { VoxelGridPtr popgrid = std::move(top->grid); diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 86b50d156..3373616a4 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -137,7 +137,8 @@ inline Vec3f to_vec3f(const _EpecMesh::Point& v) return { float(iv.x()), float(iv.y()), float(iv.z()) }; } -template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) +template +indexed_triangle_set cgal_to_indexed_triangle_set(const _Mesh &cgalmesh) { indexed_triangle_set its; its.vertices.reserve(cgalmesh.num_vertices()); @@ -167,7 +168,7 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) its.indices.emplace_back(facet); } - return TriangleMesh(std::move(its)); + return its; } std::unique_ptr @@ -181,7 +182,12 @@ triangle_mesh_to_cgal(const std::vector &V, TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh) { - return cgal_to_triangle_mesh(cgalmesh.m); + return TriangleMesh{cgal_to_indexed_triangle_set(cgalmesh.m)}; +} + +indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh) +{ + return cgal_to_indexed_triangle_set(cgalmesh.m); } // ///////////////////////////////////////////////////////////////////////////// @@ -236,16 +242,28 @@ bool does_self_intersect(const CGALMesh &mesh) { return CGALProc::does_self_inte // Now the public functions for TriangleMesh input: // ///////////////////////////////////////////////////////////////////////////// +template void _mesh_boolean_do(Op &&op, indexed_triangle_set &A, const indexed_triangle_set &B) +{ + CGALMesh meshA; + CGALMesh meshB; + triangle_mesh_to_cgal(A.vertices, A.indices, meshA.m); + triangle_mesh_to_cgal(B.vertices, B.indices, meshB.m); + + _cgal_do(op, meshA, meshB); + + A = cgal_to_indexed_triangle_set(meshA.m); +} + template void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B) { CGALMesh meshA; CGALMesh meshB; triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m); triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m); - + _cgal_do(op, meshA, meshB); - - A = cgal_to_triangle_mesh(meshA.m); + + A = cgal_to_triangle_mesh(meshA); } void minus(TriangleMesh &A, const TriangleMesh &B) @@ -263,6 +281,21 @@ void intersect(TriangleMesh &A, const TriangleMesh &B) _mesh_boolean_do(_cgal_intersection, A, B); } +void minus(indexed_triangle_set &A, const indexed_triangle_set &B) +{ + _mesh_boolean_do(_cgal_diff, A, B); +} + +void plus(indexed_triangle_set &A, const indexed_triangle_set &B) +{ + _mesh_boolean_do(_cgal_union, A, B); +} + +void intersect(indexed_triangle_set &A, const indexed_triangle_set &B) +{ + _mesh_boolean_do(_cgal_intersection, A, B); +} + bool does_self_intersect(const TriangleMesh &mesh) { CGALMesh cgalm; diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 441fc78c1..4210bfe86 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -42,12 +42,17 @@ inline CGALMeshPtr triangle_mesh_to_cgal(const TriangleMesh &M) } TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh); - +indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh); + // Do boolean mesh difference with CGAL bypassing igl. void minus(TriangleMesh &A, const TriangleMesh &B); void plus(TriangleMesh &A, const TriangleMesh &B); void intersect(TriangleMesh &A, const TriangleMesh &B); +void minus(indexed_triangle_set &A, const indexed_triangle_set &B); +void plus(indexed_triangle_set &A, const indexed_triangle_set &B); +void intersect(indexed_triangle_set &A, const indexed_triangle_set &B); + void minus(CGALMesh &A, CGALMesh &B); void plus(CGALMesh &A, CGALMesh &B); void intersect(CGALMesh &A, CGALMesh &B); diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index bcc26981e..aa43b021b 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -104,15 +104,34 @@ inline InteriorPtr generate_interior(const indexed_triangle_set &mesh, return grid ? generate_interior(*grid, hc, ctl) : InteriorPtr{}; } +template double csgmesh_positive_maxvolume(const Cont &csg) +{ + double mesh_vol = 0; + + bool skip = false; + for (const auto &m : csg) { + auto op = csg::get_operation(m); + auto stackop = csg::get_stack_operation(m); + if (stackop == csg::CSGStackOp::Push && op != csg::CSGType::Union) + skip = true; + + if (!skip && csg::get_mesh(m) && op == csg::CSGType::Union) + mesh_vol = std::max(mesh_vol, + double(its_volume(*(csg::get_mesh(m))))); + + if (stackop == csg::CSGStackOp::Pop) + skip = false; + } + + return mesh_vol; +} + template InteriorPtr generate_interior(const Range &csgparts, const HollowingConfig &hc = {}, const JobController &ctl = {}) { - double mesh_vol = 0; - for (auto &part : csgparts) - mesh_vol = std::max(mesh_vol, - double(its_volume(*(csg::get_mesh(part))))); + double mesh_vol = csgmesh_positive_maxvolume(csgparts); auto params = csg::VoxelizeParams{} .voxel_scale(get_voxel_scale(mesh_vol, hc)) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 4e34ffbed..c1b035681 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -163,11 +163,7 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( SLAPrintObject &po, SLAPrintObjectStep step) { // Empirical upper limit to not get excessive performance hit - constexpr double MaxPreviewVoxelScale = 18.; - - Benchmark bench; - - bench.start(); + constexpr double MaxPreviewVoxelScale = 12.; // update preview mesh double vscale = std::min(MaxPreviewVoxelScale, @@ -182,15 +178,41 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( return po.m_print->cancel_status() != CancelStatus::NOT_CANCELED; }); + auto r = range(po.m_mesh_to_slice); + auto grid = csg::voxelize_csgmesh(r, voxparams); + auto m = grid ? grid_to_mesh(*grid, 0., 0.01) : indexed_triangle_set{}; + float loss_less_max_error = 1e-6; + its_quadric_edge_collapse(m, 0U, &loss_less_max_error); + + return m; +} + + +void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) +{ + Benchmark bench; + + bench.start(); + auto r = range(po.m_mesh_to_slice); auto m = indexed_triangle_set{}; - if (r.size() > 1 && !is_all_positive(r)) { - auto grid = csg::voxelize_csgmesh(r, voxparams); - m = grid ? grid_to_mesh(*grid) : indexed_triangle_set{}; -// float loss_less_max_error = 1e-6; -// its_quadric_edge_collapse(m, 0U, &loss_less_max_error); - } else { + if (r.size() == 1 || is_all_positive(r)) { m = csgmesh_merge_positive_parts(r); + } else if (csg::check_csgmesh_booleans(r) == r.end()) { + auto cgalmeshptr = csg::perform_csgmesh_booleans(r); + if (cgalmeshptr) + m = MeshBoolean::cgal::cgal_to_indexed_triangle_set(*cgalmeshptr); + } else { + po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + L("Can't do proper mesh booleans!")); + m = generate_preview_vdb(po, step); + } + + po.m_preview_meshes[step] = TriangleMesh{std::move(m)}; + + for (size_t i = size_t(step) + 1; i < slaposCount; ++i) + { + po.m_preview_meshes[i] = {}; } bench.stop(); @@ -200,27 +222,6 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( else BOOST_LOG_TRIVIAL(error) << "Preview failed!"; - return m; -} - - -void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) -{ - auto r = range(po.m_mesh_to_slice); - if (csg::check_csgmesh_booleans(r) == r.end()) { - auto cgalmesh = csg::perform_csgmesh_booleans(r); - po.m_preview_meshes[step] = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalmesh); - } else { - po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, - L("Can't do proper mesh booleans!")); - po.m_preview_meshes[step] = TriangleMesh{generate_preview_vdb(po, step)}; - } - - for (size_t i = size_t(step) + 1; i < slaposCount; ++i) - { - po.m_preview_meshes[i] = {}; - } - using namespace std::string_literals; report_status(-2, "Reload preview from step "s + std::to_string(int(step)), SlicingStatus::RELOAD_SLA_PREVIEW); @@ -356,9 +357,7 @@ template BoundingBoxf3 csgmesh_positive_bb(const Cont &csg) { // Calculate the biggest possible bounding box of the mesh to be sliced // from all the positive parts that it contains. - auto bb3d = csg.empty() ? BoundingBoxf3{} : - bounding_box(*csg::get_mesh(*csg.begin()), - csg::get_transform(*csg.begin())); + BoundingBoxf3 bb3d; bool skip = false; for (const auto &m : csg) { From bfb1ec073d7ab4b498698823c40feb0b9704466d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 2 Jan 2023 10:29:55 +0100 Subject: [PATCH 054/206] Comments for CSG stack ops --- src/libslic3r/CSGMesh/CSGMesh.hpp | 18 ++++++++++++++++++ src/libslic3r/CSGMesh/ModelToCSGMesh.hpp | 16 +++++++++------- .../CSGMesh/PerformCSGMeshBooleans.hpp | 1 + 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp index f44119c92..00b60e4fc 100644 --- a/src/libslic3r/CSGMesh/CSGMesh.hpp +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -18,6 +18,23 @@ namespace Slic3r { namespace csg { // Supported CSG operation types enum class CSGType { Union, Difference, Intersection }; + +// A CSG part can instruct the processing to push the sub-result until a new +// csg part with a pop instruction appears. This can be used to implement +// parentheses in a CSG expression represented by the collection of csg parts. +// A CSG part can not contain another CSG collection, only a mesh, this is why +// its easier to do this stacking instead of recursion in the data definition. +// CSGStackOp::Continue means no stack operation required. +// When a CSG part contains a Push instruction, it is expected that the CSG +// operation it contains refers to the whole collection spanning to the nearest +// part with a Pop instruction. +// e.g.: +// { +// CUBE1: { mesh: cube, op: Union, stack op: Continue }, +// CUBE2: { mesh: cube, op: Difference, stack op: Push}, +// CUBE3: { mesh: cube, op: Union, stack op: Pop} +// } +// is a collection of csg parts representing the expression CUBE1 - (CUBE2 + CUBE3) enum class CSGStackOp { Push, Continue, Pop }; // Get the CSG operation of the part. Can be overriden for any type @@ -26,6 +43,7 @@ template CSGType get_operation(const CSGPartT &part) return part.operation; } +// Get the stack operation required by the CSG part. template CSGStackOp get_stack_operation(const CSGPartT &part) { return part.stack_operation; diff --git a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp index 3d1941294..8a3773bfd 100644 --- a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp @@ -9,18 +9,20 @@ namespace Slic3r { namespace csg { +// Flags to select which parts to export from Model into a csg part collection. +// These flags can be chained with the | operator enum ModelParts { - mpartsPositive = 1, - mpartsNegative = 2, - mpartsDrillHoles = 4, - mpartsDoSplits = 8 + mpartsPositive = 1, // Include positive parts + mpartsNegative = 2, // Include negative parts + mpartsDrillHoles = 4, // Include drill holes + mpartsDoSplits = 8 // Split each splitable mesh and export as a union of csg parts }; template void model_to_csgmesh(const ModelObject &mo, - const Transform3d &trafo, - OutIt out, - // values of ModelParts ORed + const Transform3d &trafo, // Applies to all exported parts + OutIt out, // Output iterator + // values of ModelParts OR-ed int parts_to_include = mpartsPositive ) { diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index 0a880c8fa..f81a44541 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -82,6 +82,7 @@ std::vector get_cgalptrs(Ex policy, const Range &csgrange) } // namespace detail +// Process the sequence of CSG parts with CGAL. template void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMeshPtr &cgalm, const Range &csgrange) From 159fc4e28e963db2f120b93f276c4528c9e32c78 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 2 Jan 2023 13:40:04 +0100 Subject: [PATCH 055/206] Minor improvements Fix bad function call exception with empty interrupter (OpenVDB) --- src/libslic3r/CSGMesh/ModelToCSGMesh.hpp | 3 +++ src/libslic3r/OpenVDBUtils.cpp | 2 +- src/libslic3r/SLAPrintSteps.cpp | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp index 8a3773bfd..ecf9d8168 100644 --- a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp @@ -43,6 +43,9 @@ void model_to_csgmesh(const ModelObject &mo, ++out; its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) { + if (its.empty()) + return; + CSGPart part{std::make_unique(std::move(its)), CSGType::Union, (trafo * vol->get_matrix()).cast()}; diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 44a5b172e..5d00fac0d 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -81,7 +81,7 @@ struct Interrupter void start(const char* name = nullptr) { (void)name; } void end() {} - inline bool wasInterrupted(int percent = -1) { return statusfn(percent); } + inline bool wasInterrupted(int percent = -1) { return statusfn && statusfn(percent); } }; VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index c1b035681..1c56b8946 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -196,7 +196,7 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st auto r = range(po.m_mesh_to_slice); auto m = indexed_triangle_set{}; - if (r.size() == 1 || is_all_positive(r)) { + if (is_all_positive(r)) { m = csgmesh_merge_positive_parts(r); } else if (csg::check_csgmesh_booleans(r) == r.end()) { auto cgalmeshptr = csg::perform_csgmesh_booleans(r); @@ -204,7 +204,9 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st m = MeshBoolean::cgal::cgal_to_indexed_triangle_set(*cgalmeshptr); } else { po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, - L("Can't do proper mesh booleans!")); + L("Can't perform full mesh booleans! " + "Some parts of the print will be previewed with approximated meshes. " + "This does not affect the quality of slices or the physical print in any way.")); m = generate_preview_vdb(po, step); } From 39197ecd2dd32ae0e22cf9f19a00ea169798af24 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 2 Jan 2023 14:36:34 +0100 Subject: [PATCH 056/206] Caching of cgal data instead of voxel grid --- src/libslic3r/MeshBoolean.cpp | 5 +++++ src/libslic3r/MeshBoolean.hpp | 2 ++ src/libslic3r/SLAPrint.cpp | 18 +++++----------- src/libslic3r/SLAPrint.hpp | 40 +++-------------------------------- 4 files changed, 15 insertions(+), 50 deletions(-) diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 3373616a4..c7ebcbd19 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -315,6 +315,11 @@ bool empty(const CGALMesh &mesh) return mesh.m.is_empty(); } +CGALMeshPtr clone(const CGALMesh &m) +{ + return CGALMeshPtr{new CGALMesh{m}}; +} + } // namespace cgal } // namespace MeshBoolean diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 4210bfe86..e20425c1c 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -28,6 +28,8 @@ struct CGALMesh; struct CGALMeshDeleter { void operator()(CGALMesh *ptr); }; using CGALMeshPtr = std::unique_ptr; +CGALMeshPtr clone(const CGALMesh &m); + CGALMeshPtr triangle_mesh_to_cgal( const std::vector &V, const std::vector &F); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f37638fb4..0e1f8b430 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,5 +1,6 @@ #include "SLAPrint.hpp" #include "SLAPrintSteps.hpp" +#include "CSGMesh/PerformCSGMeshBooleans.hpp" #include "Geometry.hpp" #include "Thread.hpp" @@ -1104,22 +1105,13 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p, namespace csg { -inline bool operator==(const VoxelizeParams &a, const VoxelizeParams &b) +MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartForStep &part) { - std::hash h; - return h(a) == h(b); -} - -VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, VoxelizeParams p) -{ - VoxelGridPtr &ret = part.gridcache[p]; - - if (!ret && csg::get_mesh(part)) { - p.trafo(csg::get_transform(part)); - ret = mesh_to_grid(*csg::get_mesh(part), p); + if (!part.cgalcache && csg::get_mesh(part)) { + part.cgalcache = csg::get_cgalmesh(static_cast(part)); } - return ret ? clone(*ret) : nullptr; + return part.cgalcache? clone(*part.cgalcache) : nullptr; } } // namespace csg diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 6379575bf..182c2f911 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -11,6 +11,7 @@ #include "Format/SLAArchiveWriter.hpp" #include "GCode/ThumbnailData.hpp" #include "libslic3r/CSGMesh/CSGMesh.hpp" +#include "libslic3r/MeshBoolean.hpp" #include "libslic3r/OpenVDBUtils.hpp" #include @@ -49,41 +50,6 @@ enum SliceOrigin { soSupport, soModel }; } // namespace Slic3r -namespace std { - -template<> struct hash { - size_t operator() (const Slic3r::csg::VoxelizeParams &p) const { - int64_t vs = Slic3r::scaled(p.voxel_scale()) >> 10; - int64_t eb = Slic3r::scaled(p.exterior_bandwidth()) >> 10; - int64_t ib = Slic3r::scaled(p.interior_bandwidth()) >> 10; - - size_t h = 0; - boost::hash_combine(h, vs); - boost::hash_combine(h, eb); - boost::hash_combine(h, ib); - - return h; - } -}; - -template<> struct equal_to { - size_t operator() (const Slic3r::csg::VoxelizeParams &p1, - const Slic3r::csg::VoxelizeParams &p2) const { - - int64_t vs1 = Slic3r::scaled(p1.voxel_scale()) >> 10; - int64_t eb1 = Slic3r::scaled(p1.exterior_bandwidth()) >> 10; - int64_t ib1 = Slic3r::scaled(p1.interior_bandwidth()) >> 10; - - int64_t vs2 = Slic3r::scaled(p2.voxel_scale()) >> 10; - int64_t eb2 = Slic3r::scaled(p2.exterior_bandwidth()) >> 10; - int64_t ib2 = Slic3r::scaled(p2.interior_bandwidth()) >> 10; - - return vs1 == vs2 && eb1 == eb2 && ib1 == ib2; - } -}; - -} // namespace std - namespace Slic3r { // Each sla object step can hold a collection of csg operations on the @@ -95,7 +61,7 @@ namespace Slic3r { struct CSGPartForStep : public csg::CSGPart { SLAPrintObjectStep key; - mutable std::unordered_map gridcache; + mutable MeshBoolean::cgal::CGALMeshPtr cgalcache; CSGPartForStep(SLAPrintObjectStep k, CSGPart &&p = {}) : key{k}, CSGPart{std::move(p)} @@ -114,7 +80,7 @@ struct CSGPartForStep : public csg::CSGPart namespace csg { -VoxelGridPtr get_voxelgrid(const CSGPartForStep &part, VoxelizeParams p); +MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartForStep &part); } // namespace csg From 7c834de6abe7ce00ac113b63301b7a8845559a6d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 10 Jan 2023 09:42:53 +0100 Subject: [PATCH 057/206] Fix crash when selecting an object issue no. 11 --- src/slic3r/GUI/GLCanvas3D.cpp | 13 +++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/Selection.cpp | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 419e5fbc9..fa94e6abf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7055,5 +7055,18 @@ void GLCanvas3D::GizmoHighlighter::blink() invalidate(); } +const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) +{ + const ModelVolume * ret = nullptr; + + if (model.objects.size() < v.object_idx()) { + const ModelObject *obj = model.objects[v.object_idx()]; + if (obj->volumes.size() < v.volume_idx()) + ret = obj->volumes[v.volume_idx()]; + } + + return ret; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7b5a1084c..198ecb1d8 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1046,6 +1046,8 @@ private: float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } }; +const ModelVolume * get_model_volume(const GLVolume &v, const Model &model); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a70698b43..42331f3a7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1974,8 +1974,8 @@ std::vector Selection::get_volume_idxs_from_instance(unsigned int for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { const GLVolume* v = (*m_volumes)[i]; - if (pt == ptSLA && v->is_modifier && - m_model->objects[object_idx]->volumes[i]->is_modifier()) + const ModelVolume *mv = get_model_volume(*v, *m_model); + if (pt == ptSLA && v->is_modifier && mv && mv->is_modifier()) continue; if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) idxs.push_back(i); From b2ef76f4d0148065e096e2ccc904d8188d00a02a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 10 Jan 2023 14:39:03 +0100 Subject: [PATCH 058/206] Resurrect the old hollowing and hole drilling functions. Apply them if generic cgal fails and there are no negative volumes. --- src/libslic3r/SLA/Hollowing.cpp | 169 ++++++++++++++++++++++++++++++-- src/libslic3r/SLA/Hollowing.hpp | 23 +++++ src/libslic3r/SLAPrintSteps.cpp | 93 +++++++++++++++++- src/libslic3r/SLAPrintSteps.hpp | 1 - 4 files changed, 270 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 2d3606c47..31289e7ba 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -2,11 +2,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -14,6 +16,8 @@ #include #include +#include + #include #include @@ -291,6 +295,19 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) mesh.merge(inter); } +void hollow_mesh(indexed_triangle_set &mesh, const Interior &interior, int flags) +{ + if (mesh.empty() || interior.mesh.empty()) return; + + if (flags & hfRemoveInsideTriangles && interior.gridptr) + remove_inside_triangles(mesh, interior); + + indexed_triangle_set interi = interior.mesh; + sla::swap_normals(interi); + + its_merge(mesh, interi); +} + // Get the distance of p to the interior's zero iso_surface. Interior should // have its zero isosurface positioned at offset + closing_distance inwards form // the model surface. @@ -378,14 +395,14 @@ void divide_triangle(const DivFace &face, Fn &&visitor) divide_triangle(child2, std::forward(visitor)); } -void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, +void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior, const std::vector &exclude_mask) { enum TrPos { posInside, posTouch, posOutside }; - auto &faces = mesh.its.indices; - auto &vertices = mesh.its.vertices; - auto bb = mesh.bounding_box(); + auto &faces = mesh.indices; + auto &vertices = mesh.vertices; + auto bb = bounding_box(mesh); //mesh.bounding_box(); bool use_exclude_mask = faces.size() == exclude_mask.size(); auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) { @@ -421,8 +438,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, // or not. std::vector to_remove; - MeshMods(const TriangleMesh &mesh): - to_remove(mesh.its.indices.size(), false) {} + MeshMods(const indexed_triangle_set &mesh): + to_remove(mesh.indices.size(), false) {} // Number of triangles that need to be removed. size_t to_remove_cnt() const @@ -484,7 +501,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, const Vec3i &face = faces[face_idx]; // If the triangle is excluded, we need to keep it. - if (is_excluded(face_idx)) return; + if (is_excluded(face_idx)) + return; std::array pts = {vertices[face(0)], vertices[face(1)], vertices[face(2)]}; @@ -492,7 +510,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, BoundingBoxf3 facebb{pts.begin(), pts.end()}; // Face is certainly outside the cavity - if (!facebb.intersects(bb)) return; + if (!facebb.intersects(bb)) + return; DivFace df{face, pts, long(face_idx)}; @@ -525,10 +544,16 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, faces.swap(new_faces); new_faces = {}; - mesh = TriangleMesh{mesh.its}; +// mesh = TriangleMesh{mesh.its}; //FIXME do we want to repair the mesh? Are there duplicate vertices or flipped triangles? } +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask) +{ + remove_inside_triangles(mesh.its, interior, exclude_mask); +} + struct FaceHash { // A 64 bit number's max hex digits @@ -590,8 +615,10 @@ struct FaceHash { FaceHash (const indexed_triangle_set &its): facehash(its.indices.size()) { - for (const Vec3i &face : its.indices) + for (Vec3i face : its.indices) { + std::swap(face(0), face(2)); facehash.insert(facekey(face, its.vertices)); + } } bool find(const std::string &key) @@ -747,4 +774,126 @@ double get_voxel_scale(double mesh_volume, const HollowingConfig &hc) return voxel_scale; } +// The same as its_compactify_vertices, but returns a new mesh, doesn't touch +// the original +static indexed_triangle_set +remove_unconnected_vertices(const indexed_triangle_set &its) +{ + if (its.indices.empty()) {}; + + indexed_triangle_set M; + + std::vector vtransl(its.vertices.size(), -1); + int vcnt = 0; + for (auto &f : its.indices) { + + for (int i = 0; i < 3; ++i) + if (vtransl[size_t(f(i))] < 0) { + + M.vertices.emplace_back(its.vertices[size_t(f(i))]); + vtransl[size_t(f(i))] = vcnt++; + } + + std::array new_f = { + vtransl[size_t(f(0))], + vtransl[size_t(f(1))], + vtransl[size_t(f(2))] + }; + + M.indices.emplace_back(new_f[0], new_f[1], new_f[2]); + } + + return M; +} + +int hollow_mesh_and_drill(indexed_triangle_set &hollowed_mesh, + const Interior &interior, + const DrainHoles &drainholes, + std::function on_hole_fail) +{ + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + hollowed_mesh.vertices, + hollowed_mesh.indices + ); + + std::uniform_real_distribution dist(0., float(EPSILON)); + auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}, {}); + indexed_triangle_set part_to_drill = hollowed_mesh; + + std::mt19937 m_rng{std::random_device{}()}; + + for (size_t i = 0; i < drainholes.size(); ++i) { + sla::DrainHole holept = drainholes[i]; + + holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; + holept.normal.normalize(); + holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; + indexed_triangle_set m = holept.to_mesh(); + + part_to_drill.indices.clear(); + auto bb = bounding_box(m); + Eigen::AlignedBox ebb{bb.min.cast(), + bb.max.cast()}; + + AABBTreeIndirect::traverse( + tree, + AABBTreeIndirect::intersecting(ebb), + [&part_to_drill, &hollowed_mesh](const auto& node) + { + part_to_drill.indices.emplace_back(hollowed_mesh.indices[node.idx]); + // continue traversal + return true; + }); + + auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal( + remove_unconnected_vertices(part_to_drill)); + + if (MeshBoolean::cgal::does_self_intersect(*cgal_meshpart)) { + on_hole_fail(i); + continue; + } + + auto cgal_hole = MeshBoolean::cgal::triangle_mesh_to_cgal(m); + MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_hole); + } + + auto ret = static_cast(HollowMeshResult::Ok); + + if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) { + ret |= static_cast(HollowMeshResult::DrillingFailed); + } + + auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); + + if (!MeshBoolean::cgal::does_bound_a_volume(*hollowed_mesh_cgal)) { + ret |= static_cast(HollowMeshResult::FaultyMesh); + } + + if (!MeshBoolean::cgal::empty(*holes_mesh_cgal) + && !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) { + ret |= static_cast(HollowMeshResult::FaultyHoles); + } + + // Don't even bother + if (ret & static_cast(HollowMeshResult::DrillingFailed)) + return ret; + + try { + if (!MeshBoolean::cgal::empty(*holes_mesh_cgal)) + MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); + + hollowed_mesh = + MeshBoolean::cgal::cgal_to_indexed_triangle_set(*hollowed_mesh_cgal); + + std::vector exclude_mask = + create_exclude_mask(hollowed_mesh, interior, drainholes); + + sla::remove_inside_triangles(hollowed_mesh, interior, exclude_mask); + } catch (const Slic3r::RuntimeError &) { + ret |= static_cast(HollowMeshResult::DrillingFailed); + } + + return ret; +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index aa43b021b..c21bdff4e 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -159,9 +159,32 @@ void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); // Hollowing prepared in "interior", merge with original mesh void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); +// Will do the hollowing +void hollow_mesh(indexed_triangle_set &mesh, const HollowingConfig &cfg, int flags = 0); + +// Hollowing prepared in "interior", merge with original mesh +void hollow_mesh(indexed_triangle_set &mesh, const Interior &interior, int flags = 0); + +enum class HollowMeshResult { + Ok = 0, + FaultyMesh = 1, + FaultyHoles = 2, + DrillingFailed = 4 +}; + +// Return HollowMeshResult codes OR-ed. +int hollow_mesh_and_drill( + indexed_triangle_set &mesh, + const Interior& interior, + const DrainHoles &holes, + std::function on_hole_fail = [](size_t){}); + void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, const std::vector &exclude_mask = {}); +void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior, + const std::vector &exclude_mask = {}); + sla::DrainHoles transformed_drainhole_points(const ModelObject &mo, const Transform3d &trafo); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 1c56b8946..c7567528b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -87,7 +87,6 @@ std::string PRINT_STEP_LABELS(size_t idx) SLAPrint::Steps::Steps(SLAPrint *print) : m_print{print} - , m_rng{std::random_device{}()} , objcount{m_print->m_objects.size()} , ilhd{m_print->m_material_config.initial_layer_height.getFloat()} , ilh{float(ilhd)} @@ -187,6 +186,12 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( return m; } +inline auto parts_to_slice(const std::multiset &parts, + SLAPrintObjectStep step) +{ + auto r = parts.equal_range(step); + return Range{r.first, r.second}; +} void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) { @@ -196,13 +201,91 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st auto r = range(po.m_mesh_to_slice); auto m = indexed_triangle_set{}; + + bool handled = false; + if (is_all_positive(r)) { m = csgmesh_merge_positive_parts(r); + handled = true; } else if (csg::check_csgmesh_booleans(r) == r.end()) { auto cgalmeshptr = csg::perform_csgmesh_booleans(r); - if (cgalmeshptr) + if (cgalmeshptr) { m = MeshBoolean::cgal::cgal_to_indexed_triangle_set(*cgalmeshptr); + handled = true; + } } else { + // Normal cgal processing failed. If there are no negative volumes, + // the hollowing can be tried with the old algorithm which didn't handled volumes. + // If that fails for any of the drillholes, the voxelization fallback is + // used. + + bool is_pure_model = is_all_positive(parts_to_slice(po.m_mesh_to_slice, slaposAssembly)); + bool can_hollow = po.m_hollowing_data && po.m_hollowing_data->interior && + !sla::get_mesh(*po.m_hollowing_data->interior).empty(); + + + bool hole_fail = false; + if (step == slaposHollowing && is_pure_model) { + if (can_hollow) { + m = csgmesh_merge_positive_parts(r); + sla::hollow_mesh(m, *po.m_hollowing_data->interior, + sla::hfRemoveInsideTriangles); + } + + handled = true; + } else if (step == slaposDrillHoles && is_pure_model) { + if (po.m_model_object->sla_drain_holes.empty()) { + m = po.m_preview_meshes[slaposHollowing].its; + handled = true; + } else if (can_hollow) { + m = csgmesh_merge_positive_parts(r); + sla::hollow_mesh(m, *po.m_hollowing_data->interior); + sla::DrainHoles drainholes = po.transformed_drainhole_points(); + + auto ret = sla::hollow_mesh_and_drill( + m, *po.m_hollowing_data->interior, drainholes, + [/*&po, &drainholes, */&hole_fail](size_t i) + { + hole_fail = /*drainholes[i].failed = + po.model_object()->sla_drain_holes[i].failed =*/ true; + }); + + if (ret & static_cast(sla::HollowMeshResult::FaultyMesh)) { + po.active_step_add_warning( + PrintStateBase::WarningLevel::NON_CRITICAL, + L("Mesh to be hollowed is not suitable for hollowing (does not " + "bound a volume).")); + } + + if (ret & static_cast(sla::HollowMeshResult::FaultyHoles)) { + po.active_step_add_warning( + PrintStateBase::WarningLevel::NON_CRITICAL, + L("Unable to drill the current configuration of holes into the " + "model.")); + } + + handled = true; + + if (ret & static_cast(sla::HollowMeshResult::DrillingFailed)) { + po.active_step_add_warning( + PrintStateBase::WarningLevel::NON_CRITICAL, L( + "Drilling holes into the mesh failed. " + "This is usually caused by broken model. Try to fix it first.")); + + handled = false; + } + + if (hole_fail) { + po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + L("Failed to drill some holes into the model")); + + handled = false; + } + } + } + } + + if (!handled) { // Last resort to voxelization. po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, L("Can't perform full mesh booleans! " "Some parts of the print will be previewed with approximated meshes. " @@ -290,9 +373,6 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) indexed_triangle_set m = sla::get_mesh(*po.m_hollowing_data->interior); - // Release the data, won't be needed anymore, takes huge amount of ram - po.m_hollowing_data->interior.reset(); - if (!m.empty()) { // simplify mesh lossless float loss_less_max_error = 2*std::numeric_limits::epsilon(); @@ -327,6 +407,9 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) generate_preview(po, slaposDrillHoles); else po.m_preview_meshes[slaposDrillHoles] = po.get_mesh_to_print(); + + // Release the data, won't be needed anymore, takes huge amount of ram + po.m_hollowing_data->interior.reset(); } template diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index a1b3ef42d..32d10a424 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -14,7 +14,6 @@ class SLAPrint::Steps { private: SLAPrint *m_print = nullptr; - std::mt19937 m_rng; public: // where the per object operations start and end From 0989a6f5df4df1495b04cb7031c6a1c8c5de7b69 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 10 Jan 2023 16:12:54 +0100 Subject: [PATCH 059/206] Fix crash --- src/libslic3r/SLAPrintSteps.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index c7567528b..88b7926d1 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -409,7 +409,8 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) po.m_preview_meshes[slaposDrillHoles] = po.get_mesh_to_print(); // Release the data, won't be needed anymore, takes huge amount of ram - po.m_hollowing_data->interior.reset(); + if (po.m_hollowing_data && po.m_hollowing_data->interior) + po.m_hollowing_data->interior.reset(); } template From 897e5673c94a4ba9b4587eebadf82cca187425d1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 10 Jan 2023 16:13:13 +0100 Subject: [PATCH 060/206] Export interface for csgparts in SLAPrintObject --- src/libslic3r/SLAPrint.hpp | 17 +++++++++++++---- src/libslic3r/SLAPrintSteps.cpp | 9 +-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 182c2f911..0cf796d07 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -88,6 +88,7 @@ class SLAPrintObject : public _SLAPrintObjectBase { private: // Prevents erroneous use by other classes. using Inherited = _SLAPrintObjectBase; + using CSGContainer = std::multiset; public: @@ -122,6 +123,17 @@ public: // like hollowing and drilled holes. const TriangleMesh & get_mesh_to_print() const; + const Range get_parts_to_slice() const + { + return range(m_mesh_to_slice); + } + + const Range get_parts_to_slice(SLAPrintObjectStep step) const + { + auto r = m_mesh_to_slice.equal_range(step); + return {r.first, r.second}; + } + sla::SupportPoints transformed_support_points() const; sla::DrainHoles transformed_drainhole_points() const; @@ -331,9 +343,6 @@ private: std::vector m_model_height_levels; - // Caching the transformed (m_trafo) raw mesh of the object -// TriangleMesh m_transformed_rmesh; - struct SupportData { sla::SupportableMesh input; // the input @@ -362,7 +371,7 @@ private: std::unique_ptr m_supportdata; // Holds CSG operations for the printed object, prioritized by print steps. - std::multiset m_mesh_to_slice; + CSGContainer m_mesh_to_slice; // Holds the preview of the object to be printed (as it will look like with // all its holes and cavities, negatives and positive volumes unified. diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 88b7926d1..f0e2e5db0 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -186,13 +186,6 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( return m; } -inline auto parts_to_slice(const std::multiset &parts, - SLAPrintObjectStep step) -{ - auto r = parts.equal_range(step); - return Range{r.first, r.second}; -} - void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) { Benchmark bench; @@ -219,7 +212,7 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st // If that fails for any of the drillholes, the voxelization fallback is // used. - bool is_pure_model = is_all_positive(parts_to_slice(po.m_mesh_to_slice, slaposAssembly)); + bool is_pure_model = is_all_positive(po.get_parts_to_slice(slaposAssembly)); bool can_hollow = po.m_hollowing_data && po.m_hollowing_data->interior && !sla::get_mesh(*po.m_hollowing_data->interior).empty(); From a4e50f8219884e7ac963e35e092dc41a76b71e72 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 10 Jan 2023 17:45:48 +0100 Subject: [PATCH 061/206] Fix gizmo cut previews When using legacy hole drilling algorithm --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/CSGMesh/CSGMesh.hpp | 23 ----- src/libslic3r/CSGMesh/CSGMeshCopy.hpp | 69 ++++++++++++++ src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 4 +- src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp | 95 +++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 33 ++++--- src/slic3r/GUI/MeshUtils.cpp | 42 ++++++-- src/slic3r/GUI/MeshUtils.hpp | 29 +++++- 8 files changed, 246 insertions(+), 51 deletions(-) create mode 100644 src/libslic3r/CSGMesh/CSGMeshCopy.hpp create mode 100644 src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 4bb4fee9e..da561d411 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -44,6 +44,8 @@ set(SLIC3R_SOURCES CSGMesh/ModelToCSGMesh.hpp CSGMesh/PerformCSGMeshBooleans.hpp CSGMesh/VoxelizeCSGMesh.hpp + CSGMesh/TriangleMeshAdapter.hpp + CSGMesh/CSGMeshCopy.hpp EdgeGrid.cpp EdgeGrid.hpp ElephantFootCompensation.cpp diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp index 00b60e4fc..ef555a38b 100644 --- a/src/libslic3r/CSGMesh/CSGMesh.hpp +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -64,29 +64,6 @@ Transform3f get_transform(const CSGPartT &part) return part.trafo; } -// Provide default overloads for indexed_triangle_set to be usable as a plain -// CSGPart with an implicit union operation - -inline CSGType get_operation(const indexed_triangle_set &part) -{ - return CSGType::Union; -} - -inline CSGStackOp get_stack_operation(const indexed_triangle_set &part) -{ - return CSGStackOp::Continue; -} - -inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part) -{ - return ∂ -} - -inline Transform3f get_transform(const indexed_triangle_set &part) -{ - return Transform3f::Identity(); -} - // Default implementation struct CSGPart { AnyPtr its_ptr; diff --git a/src/libslic3r/CSGMesh/CSGMeshCopy.hpp b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp new file mode 100644 index 000000000..b296ecbfa --- /dev/null +++ b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp @@ -0,0 +1,69 @@ +#ifndef CSGMESHCOPY_HPP +#define CSGMESHCOPY_HPP + +#include "CSGMesh.hpp" + +namespace Slic3r { namespace csg { + +// Copy a csg range but for the meshes, only copy the pointers. +template +void copy_csgrange_shallow(const Range &csgrange, OutIt out) +{ + for (const auto &part : csgrange) { + CSGPart cpy{AnyPtr{get_mesh(part)}, + get_operation(part), + get_transform(part)}; + + cpy.stack_operation = get_stack_operation(part); + + *out = std::move(cpy); + ++out; + } +} + +// Copy the csg range, allocating new meshes +template +void copy_csgrange_deep(const Range &csgrange, OutIt out) +{ + for (const auto &part : csgrange) { + + CSGPart cpy{{}, get_operation(part), get_transform(part)}; + + if (auto meshptr = get_mesh(part)) { + cpy.its_ptr = std::make_unique(*meshptr); + } + + cpy.stack_operation = get_stack_operation(part); + + *out = std::move(cpy); + ++out; + } +} + +template +bool is_same(const Range &A, const Range &B) +{ + bool ret = true; + + size_t s = A.size(); + + if (B.size() != s) + ret = false; + + size_t i = 0; + auto itA = A.begin(); + auto itB = B.begin(); + for (; ret && i < s; ++itA, ++itB, ++i) { + ret = ret && + get_mesh(*itA) == get_mesh(*itB) && + get_operation(*itA) == get_operation(*itB) && + get_stack_operation(*itA) == get_stack_operation(*itB) && + get_transform(*itA).isApprox(get_transform(*itB)); + } + + return ret; +} + +}} // namespace Slic3r::csg + +#endif // CSGCOPY_HPP diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index 041acedb0..9d7b9a077 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -13,7 +13,7 @@ namespace Slic3r { namespace csg { namespace detail { -void merge_slices(csg::CSGType op, size_t i, +inline void merge_slices(csg::CSGType op, size_t i, std::vector &target, std::vector &source) { @@ -31,7 +31,7 @@ void merge_slices(csg::CSGType op, size_t i, } } -void collect_nonempty_indices(csg::CSGType op, +inline void collect_nonempty_indices(csg::CSGType op, const std::vector &slicegrid, const std::vector &slices, std::vector &indices) diff --git a/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp b/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp new file mode 100644 index 000000000..81b05b046 --- /dev/null +++ b/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp @@ -0,0 +1,95 @@ +#ifndef TRIANGLEMESHADAPTER_HPP +#define TRIANGLEMESHADAPTER_HPP + +#include "CSGMesh.hpp" + +#include "libslic3r/TriangleMesh.hpp" + +namespace Slic3r { namespace csg { + +// Provide default overloads for indexed_triangle_set to be usable as a plain +// CSGPart with an implicit union operation + +inline CSGType get_operation(const indexed_triangle_set &part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const indexed_triangle_set &part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part) +{ + return ∂ +} + +inline Transform3f get_transform(const indexed_triangle_set &part) +{ + return Transform3f::Identity(); +} + +inline CSGType get_operation(const indexed_triangle_set *const part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const indexed_triangle_set *const part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const indexed_triangle_set *const part) +{ + return part; +} + +inline Transform3f get_transform(const indexed_triangle_set *const part) +{ + return Transform3f::Identity(); +} + +inline CSGType get_operation(const TriangleMesh &part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const TriangleMesh &part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const TriangleMesh &part) +{ + return &part.its; +} + +inline Transform3f get_transform(const TriangleMesh &part) +{ + return Transform3f::Identity(); +} + +inline CSGType get_operation(const TriangleMesh * const part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const TriangleMesh * const part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const TriangleMesh * const part) +{ + return &part->its; +} + +inline Transform3f get_transform(const TriangleMesh * const part) +{ + return Transform3f::Identity(); +} + +}} // namespace Slic3r::csg + +#endif // TRIANGLEMESHADAPTER_HPP diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index ac3750871..705a13b90 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -172,7 +172,7 @@ void InstancesHider::on_update() for (const TriangleMesh* mesh : meshes) { m_clippers.emplace_back(new MeshClipper); m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - m_clippers.back()->set_mesh(*mesh); + m_clippers.back()->set_mesh(mesh->its); } m_old_meshes = meshes; } @@ -299,8 +299,9 @@ std::vector Raycaster::raycasters() const return mrcs; } +} // namespace GUI - +namespace GUI { void ObjectClipper::on_update() @@ -313,33 +314,39 @@ void ObjectClipper::on_update() std::vector meshes; std::vector trafos; bool force_clipper_regeneration = false; + + std::unique_ptr mc; + Geometry::Transformation mc_tr; if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) { // For sla printers we use the mesh generated by the backend const SLAPrintObject* po = get_pool()->selection_info()->print_object(); assert(po != nullptr); - m_sla_mesh_cache = po->get_mesh_to_print(); - if (!m_sla_mesh_cache.empty()) { - m_sla_mesh_cache.transform(po->trafo().inverse()); - meshes.emplace_back(&m_sla_mesh_cache); - trafos.emplace_back(Geometry::Transformation()); - force_clipper_regeneration = true; + auto partstoslice = po->get_parts_to_slice(); + if (! partstoslice.empty()) { + mc = std::make_unique(); + mc->set_mesh(partstoslice); + mc_tr = Geometry::Transformation{po->trafo().inverse().cast()}; } } - if (meshes.empty()) { + if (!mc && meshes.empty()) { for (const ModelVolume* mv : mo->volumes) { meshes.emplace_back(&mv->mesh()); trafos.emplace_back(mv->get_transformation()); } } - if (force_clipper_regeneration || meshes != m_old_meshes) { + if (mc || force_clipper_regeneration || meshes != m_old_meshes) { m_clippers.clear(); for (size_t i = 0; i < meshes.size(); ++i) { m_clippers.emplace_back(new MeshClipper, trafos[i]); - m_clippers.back().first->set_mesh(*meshes[i]); + m_clippers.back().first->set_mesh(meshes[i]->its); + } + m_old_meshes = std::move(meshes); + + if (mc) { + m_clippers.emplace_back(std::move(mc), mc_tr); } - m_old_meshes = meshes; m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); @@ -470,7 +477,7 @@ void SupportsClipper::on_update() // The timestamp has changed. m_clipper.reset(new MeshClipper); // The mesh should already have the shared vertices calculated. - m_clipper->set_mesh(print_object->support_mesh()); + m_clipper->set_mesh(print_object->support_mesh().its); m_old_timestamp = timestamp; } } diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 39e463a19..faa970c4a 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -5,11 +5,13 @@ #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/CSGMesh/SliceCSGMesh.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Camera.hpp" + #include #include @@ -49,22 +51,38 @@ void MeshClipper::set_limiting_plane(const ClippingPlane& plane) -void MeshClipper::set_mesh(const TriangleMesh& mesh) +void MeshClipper::set_mesh(const indexed_triangle_set& mesh) { - if (m_mesh != &mesh) { + if (m_mesh.get() != &mesh) { m_mesh = &mesh; m_result.reset(); } } -void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) +void MeshClipper::set_mesh(AnyPtr &&ptr) { - if (m_negative_mesh != &mesh) { + if (m_mesh.get() != ptr.get()) { + m_mesh = std::move(ptr); + m_result.reset(); + } +} + +void MeshClipper::set_negative_mesh(const indexed_triangle_set& mesh) +{ + if (m_negative_mesh.get() != &mesh) { m_negative_mesh = &mesh; m_result.reset(); } } +void MeshClipper::set_negative_mesh(AnyPtr &&ptr) +{ + if (m_negative_mesh.get() != ptr.get()) { + m_negative_mesh = std::move(ptr); + m_result.reset(); + } +} + void MeshClipper::set_transformation(const Geometry::Transformation& trafo) @@ -172,13 +190,21 @@ void MeshClipper::recalculate_triangles() MeshSlicingParams slicing_params; slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); - ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); + ExPolygons expolys; - if (m_negative_mesh && !m_negative_mesh->empty()) { - const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params)); - expolys = diff_ex(expolys, neg_expolys); + if (m_csgmesh.empty()) { + if (m_mesh) + expolys = union_ex(slice_mesh(*m_mesh, height_mesh, slicing_params)); + + if (m_negative_mesh && !m_negative_mesh->empty()) { + const ExPolygons neg_expolys = union_ex(slice_mesh(*m_negative_mesh, height_mesh, slicing_params)); + expolys = diff_ex(expolys, neg_expolys); + } + } else { + expolys = std::move(csg::slice_csgmesh_ex(range(m_csgmesh), {height_mesh}, MeshSlicingParamsEx{slicing_params}).front()); } + // Triangulate and rotate the cut into world coords: Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), up); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index b7afbbb89..c9ba916ad 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -5,6 +5,8 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/AABBMesh.hpp" +#include "libslic3r/CSGMesh/TriangleMeshAdapter.hpp" +#include "libslic3r/CSGMesh/CSGMeshCopy.hpp" #include "admesh/stl.h" #include "slic3r/GUI/GLModel.hpp" @@ -14,7 +16,6 @@ #include namespace Slic3r { - namespace GUI { struct Camera; @@ -88,9 +89,25 @@ public: // Which mesh to cut. MeshClipper remembers const * to it, caller // must make sure that it stays valid. - void set_mesh(const TriangleMesh& mesh); + void set_mesh(const indexed_triangle_set& mesh); + void set_mesh(AnyPtr &&ptr); - void set_negative_mesh(const TriangleMesh &mesh); + void set_negative_mesh(const indexed_triangle_set &mesh); + void set_negative_mesh(AnyPtr &&ptr); + + template + void set_mesh(const Range &csgrange, bool copy_meshes = false) + { + if (! csg::is_same(range(m_csgmesh), csgrange)) { + m_csgmesh.clear(); + if (copy_meshes) + csg::copy_csgrange_deep(csgrange, std::back_inserter(m_csgmesh)); + else + csg::copy_csgrange_shallow(csgrange, std::back_inserter(m_csgmesh)); + + m_result.reset(); + } + } // Inform the MeshClipper about the transformation that transforms the mesh // into world coordinates. @@ -110,8 +127,10 @@ private: void recalculate_triangles(); Geometry::Transformation m_trafo; - const TriangleMesh* m_mesh = nullptr; - const TriangleMesh* m_negative_mesh = nullptr; + AnyPtr m_mesh; + AnyPtr m_negative_mesh; + std::vector m_csgmesh; + ClippingPlane m_plane; ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing(); From 440df505b47086e30272547b429b2bd75db99334 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 11 Jan 2023 18:24:44 +0100 Subject: [PATCH 062/206] SLA backend thread-safety improvements - Put AnyPtr into separate header, it deserves one - Add means to handle shared pointers inside AnyPtr - Use shared pointers in sla csg collection for meshes not owned by Model - Add method to get the last completed step in PrintObjectBase - Make SLAPrintObject::get_parts_to_slice() safe to call from UI thread against background thread activity. --- src/libslic3r/AnyPtr.hpp | 130 +++++++++++++++++++++++ src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/CSGMesh/CSGMesh.hpp | 2 +- src/libslic3r/CSGMesh/CSGMeshCopy.hpp | 15 ++- src/libslic3r/CSGMesh/ModelToCSGMesh.hpp | 8 +- src/libslic3r/MTUtils.hpp | 83 --------------- src/libslic3r/PrintBase.hpp | 15 +++ src/libslic3r/SLAPrint.cpp | 37 ++++++- src/libslic3r/SLAPrint.hpp | 20 ++-- src/libslic3r/SLAPrintSteps.cpp | 14 ++- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 2 +- 11 files changed, 218 insertions(+), 109 deletions(-) create mode 100644 src/libslic3r/AnyPtr.hpp diff --git a/src/libslic3r/AnyPtr.hpp b/src/libslic3r/AnyPtr.hpp new file mode 100644 index 000000000..c40d10093 --- /dev/null +++ b/src/libslic3r/AnyPtr.hpp @@ -0,0 +1,130 @@ +#ifndef ANYPTR_HPP +#define ANYPTR_HPP + +#include +#include +#include + +namespace Slic3r { + +// A general purpose pointer holder that can hold any type of smart pointer +// or raw pointer which can own or not own any object they point to. +// In case a raw pointer is stored, it is not destructed so ownership is +// assumed to be foreign. +// +// The stored pointer is not checked for being null when dereferenced. +// +// This is a movable only object due to the fact that it can possibly hold +// a unique_ptr which a non-copy. +template +class AnyPtr { + enum { RawPtr, UPtr, ShPtr, WkPtr }; + + boost::variant, std::shared_ptr, std::weak_ptr> ptr; + + template static T *get_ptr(Self &&s) + { + switch (s.ptr.which()) { + case RawPtr: return boost::get(s.ptr); + case UPtr: return boost::get>(s.ptr).get(); + case ShPtr: return boost::get>(s.ptr).get(); + case WkPtr: { + auto shptr = boost::get>(s.ptr).lock(); + return shptr.get(); + } + } + + return nullptr; + } + +public: + template>> + AnyPtr(TT *p = nullptr) : ptr{p} + {} + template>> + AnyPtr(std::unique_ptr p) : ptr{std::unique_ptr(std::move(p))} + {} + template>> + AnyPtr(std::shared_ptr p) : ptr{std::shared_ptr(std::move(p))} + {} + template>> + AnyPtr(std::weak_ptr p) : ptr{std::weak_ptr(std::move(p))} + {} + + ~AnyPtr() = default; + + AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {} + AnyPtr(const AnyPtr &other) = delete; + + AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; } + AnyPtr &operator=(const AnyPtr &other) = delete; + + template>> + AnyPtr &operator=(TT *p) { ptr = p; return *this; } + + template>> + AnyPtr &operator=(std::unique_ptr p) { ptr = std::move(p); return *this; } + + template>> + AnyPtr &operator=(std::shared_ptr p) { ptr = p; return *this; } + + template>> + AnyPtr &operator=(std::weak_ptr p) { ptr = std::move(p); return *this; } + + const T &operator*() const { return *get_ptr(*this); } + T &operator*() { return *get_ptr(*this); } + + T *operator->() { return get_ptr(*this); } + const T *operator->() const { return get_ptr(*this); } + + T *get() { return get_ptr(*this); } + const T *get() const { return get_ptr(*this); } + + operator bool() const + { + switch (ptr.which()) { + case RawPtr: return bool(boost::get(ptr)); + case UPtr: return bool(boost::get>(ptr)); + case ShPtr: return bool(boost::get>(ptr)); + case WkPtr: { + auto shptr = boost::get>(ptr).lock(); + return bool(shptr); + } + } + + return false; + } + + // If the stored pointer is a shared or weak pointer, returns a reference + // counted copy. Empty shared pointer is returned otherwise. + std::shared_ptr get_shared_cpy() const + { + std::shared_ptr ret; + + switch (ptr.which()) { + case ShPtr: ret = boost::get>(ptr); break; + case WkPtr: ret = boost::get>(ptr).lock(); break; + default: + ; + } + + return ret; + } + + // If the underlying pointer is unique, convert to shared pointer + void convert_unique_to_shared() + { + if (ptr.which() == UPtr) + ptr = std::shared_ptr{std::move(boost::get>(ptr))}; + } + + // Returns true if the data is owned by this AnyPtr instance + bool is_owned() const noexcept + { + return ptr.which() == UPtr || ptr.which() == ShPtr; + } +}; + +} // namespace Slic3r + +#endif // ANYPTR_HPP diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index da561d411..e9aeb68e2 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -22,6 +22,7 @@ set(SLIC3R_SOURCES AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp + AnyPtr.hpp BoundingBox.cpp BoundingBox.hpp BridgeDetector.cpp diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp index ef555a38b..d14ed7659 100644 --- a/src/libslic3r/CSGMesh/CSGMesh.hpp +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -1,7 +1,7 @@ #ifndef CSGMESH_HPP #define CSGMESH_HPP -#include // for AnyPtr +#include #include namespace Slic3r { namespace csg { diff --git a/src/libslic3r/CSGMesh/CSGMeshCopy.hpp b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp index b296ecbfa..78800f9bb 100644 --- a/src/libslic3r/CSGMesh/CSGMeshCopy.hpp +++ b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp @@ -5,17 +5,28 @@ namespace Slic3r { namespace csg { -// Copy a csg range but for the meshes, only copy the pointers. +// Copy a csg range but for the meshes, only copy the pointers. If the copy +// is made from a CSGPart compatible object, and the pointer is a shared one, +// it will be copied with reference counting. template void copy_csgrange_shallow(const Range &csgrange, OutIt out) { for (const auto &part : csgrange) { - CSGPart cpy{AnyPtr{get_mesh(part)}, + CSGPart cpy{{}, get_operation(part), get_transform(part)}; cpy.stack_operation = get_stack_operation(part); + if constexpr (std::is_convertible_v) { + if (auto shptr = part.its_ptr.get_shared_cpy()) { + cpy.its_ptr = shptr; + } + } + + if (!cpy.its_ptr) + cpy.its_ptr = AnyPtr{get_mesh(part)}; + *out = std::move(cpy); ++out; } diff --git a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp index ecf9d8168..9e413594e 100644 --- a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp @@ -12,10 +12,10 @@ namespace Slic3r { namespace csg { // Flags to select which parts to export from Model into a csg part collection. // These flags can be chained with the | operator enum ModelParts { - mpartsPositive = 1, // Include positive parts - mpartsNegative = 2, // Include negative parts - mpartsDrillHoles = 4, // Include drill holes - mpartsDoSplits = 8 // Split each splitable mesh and export as a union of csg parts + mpartsPositive = 1, // Include positive parts + mpartsNegative = 2, // Include negative parts + mpartsDrillHoles = 4, // Include drill holes + mpartsDoSplits = 8, // Split each splitable mesh and export as a union of csg parts }; template diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 74c44aa5e..ab99ea5f6 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "libslic3r.h" @@ -137,88 +136,6 @@ inline std::vector> grid(const T &start, return vals; } -// A general purpose pointer holder that can hold any type of smart pointer -// or raw pointer which can own or not own any object they point to. -// In case a raw pointer is stored, it is not destructed so ownership is -// assumed to be foreign. -// -// The stored pointer is not checked for being null when dereferenced. -// -// This is a movable only object due to the fact that it can possibly hold -// a unique_ptr which a non-copy. -template -class AnyPtr { - enum { RawPtr, UPtr, ShPtr, WkPtr }; - - boost::variant, std::shared_ptr, std::weak_ptr> ptr; - - template static T *get_ptr(Self &&s) - { - switch (s.ptr.which()) { - case RawPtr: return boost::get(s.ptr); - case UPtr: return boost::get>(s.ptr).get(); - case ShPtr: return boost::get>(s.ptr).get(); - case WkPtr: { - auto shptr = boost::get>(s.ptr).lock(); - return shptr.get(); - } - } - - return nullptr; - } - -public: - template>> - AnyPtr(TT *p = nullptr) : ptr{p} - {} - template>> - AnyPtr(std::unique_ptr p) : ptr{std::unique_ptr(std::move(p))} - {} - template>> - AnyPtr(std::shared_ptr p) : ptr{std::shared_ptr(std::move(p))} - {} - template>> - AnyPtr(std::weak_ptr p) : ptr{std::weak_ptr(std::move(p))} - {} - - ~AnyPtr() = default; - - AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {} - AnyPtr(const AnyPtr &other) = delete; - - AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; } - AnyPtr &operator=(const AnyPtr &other) = delete; - - AnyPtr &operator=(T *p) { ptr = p; return *this; } - AnyPtr &operator=(std::unique_ptr p) { ptr = std::move(p); return *this; } - AnyPtr &operator=(std::shared_ptr p) { ptr = p; return *this; } - AnyPtr &operator=(std::weak_ptr p) { ptr = std::move(p); return *this; } - - const T &operator*() const { return *get_ptr(*this); } - T &operator*() { return *get_ptr(*this); } - - T *operator->() { return get_ptr(*this); } - const T *operator->() const { return get_ptr(*this); } - - T *get() { return get_ptr(*this); } - const T *get() const { return get_ptr(*this); } - - operator bool() const - { - switch (ptr.which()) { - case RawPtr: return bool(boost::get(ptr)); - case UPtr: return bool(boost::get>(ptr)); - case ShPtr: return bool(boost::get>(ptr)); - case WkPtr: { - auto shptr = boost::get>(ptr).lock(); - return bool(shptr); - } - } - - return false; - } -}; - } // namespace Slic3r #endif // MTUTILS_HPP diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 13796abba..08268c04b 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -690,6 +690,21 @@ public: PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); } PrintStateBase::StateWithWarnings step_state_with_warnings(PrintObjectStepEnum step) const { return m_state.state_with_warnings(step, PrintObjectBase::state_mutex(m_print)); } + auto last_completed_step() const + { + static_assert(COUNT > 0, "Step count should be > 0"); + auto s = int(COUNT) - 1; + + std::lock_guard lk(state_mutex(m_print)); + while (s >= 0 && ! is_step_done_unguarded(PrintObjectStepEnum(s))) + --s; + + if (s < 0) + s = COUNT; + + return PrintObjectStepEnum(s); + } + protected: PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {} diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 0e1f8b430..79cc2605f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,5 +1,6 @@ #include "SLAPrint.hpp" #include "SLAPrintSteps.hpp" +#include "CSGMesh/CSGMeshCopy.hpp" #include "CSGMesh/PerformCSGMeshBooleans.hpp" #include "Geometry.hpp" @@ -1017,12 +1018,16 @@ const TriangleMesh &SLAPrintObject::get_mesh_to_print() const { const TriangleMesh *ret = nullptr; - int s = SLAPrintObjectStep::slaposCount; + int s = last_completed_step(); - while (s > 0 && !ret) { - --s; - if (is_step_done(SLAPrintObjectStep(s)) && !m_preview_meshes[s].empty()) + if (s == slaposCount) + ret = &EMPTY_MESH; + + while (s >= 0 && !ret) { + if (!m_preview_meshes[s].empty()) ret = &m_preview_meshes[s]; + + --s; } if (!ret) @@ -1031,6 +1036,30 @@ const TriangleMesh &SLAPrintObject::get_mesh_to_print() const return *ret; } +std::vector SLAPrintObject::get_parts_to_slice() const +{ + return get_parts_to_slice(slaposCount); +} + +std::vector +SLAPrintObject::get_parts_to_slice(SLAPrintObjectStep untilstep) const +{ + auto laststep = last_completed_step(); + SLAPrintObjectStep s = std::min(untilstep, laststep); + + if (s == slaposCount) + return {}; + + std::vector ret; + + for (int step = 0; step < s; ++step) { + auto r = m_mesh_to_slice.equal_range(SLAPrintObjectStep(step)); + csg::copy_csgrange_shallow(Range{r.first, r.second}, std::back_inserter(ret)); + } + + return ret; +} + sla::SupportPoints SLAPrintObject::transformed_support_points() const { assert(m_model_object != nullptr); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 0cf796d07..e6a4286ea 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -123,16 +123,9 @@ public: // like hollowing and drilled holes. const TriangleMesh & get_mesh_to_print() const; - const Range get_parts_to_slice() const - { - return range(m_mesh_to_slice); - } + std::vector get_parts_to_slice() const; - const Range get_parts_to_slice(SLAPrintObjectStep step) const - { - auto r = m_mesh_to_slice.equal_range(step); - return {r.first, r.second}; - } + std::vector get_parts_to_slice(SLAPrintObjectStep step) const; sla::SupportPoints transformed_support_points() const; sla::DrainHoles transformed_drainhole_points() const; @@ -373,6 +366,15 @@ private: // Holds CSG operations for the printed object, prioritized by print steps. CSGContainer m_mesh_to_slice; + auto mesh_to_slice(SLAPrintObjectStep s) const + { + auto r = m_mesh_to_slice.equal_range(s); + + return Range{r.first, r.second}; + } + + auto mesh_to_slice() const { return range(m_mesh_to_slice); } + // Holds the preview of the object to be printed (as it will look like with // all its holes and cavities, negatives and positive volumes unified. // Essentially this should be a m_mesh_to_slice after the CSG operations diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index f0e2e5db0..f3824f751 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -212,7 +212,7 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st // If that fails for any of the drillholes, the voxelization fallback is // used. - bool is_pure_model = is_all_positive(po.get_parts_to_slice(slaposAssembly)); + bool is_pure_model = is_all_positive(po.mesh_to_slice(slaposAssembly)); bool can_hollow = po.m_hollowing_data && po.m_hollowing_data->interior && !sla::get_mesh(*po.m_hollowing_data->interior).empty(); @@ -317,7 +317,11 @@ struct csg_inserter { SLAPrintObjectStep key; csg_inserter &operator*() { return *this; } - void operator=(csg::CSGPart &&part) { m.emplace(key, std::move(part)); } + void operator=(csg::CSGPart &&part) + { + part.its_ptr.convert_unique_to_shared(); + m.emplace(key, std::move(part)); + } csg_inserter& operator++() { return *this; } }; @@ -356,7 +360,7 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) ctl.cancelfn = [this]() { throw_if_canceled(); }; sla::InteriorPtr interior = - generate_interior(range(po.m_mesh_to_slice), hlwcfg, ctl); + generate_interior(po.mesh_to_slice(), hlwcfg, ctl); if (!interior || sla::get_mesh(*interior).empty()) BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; @@ -378,7 +382,7 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) // Put the interior into the target mesh as a negative po.m_mesh_to_slice .emplace(slaposHollowing, - csg::CSGPart{std::make_unique(std::move(m)), csg::CSGType::Difference}); + csg::CSGPart{std::make_shared(std::move(m)), csg::CSGType::Difference}); generate_preview(po, slaposHollowing); } @@ -518,7 +522,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; - po.m_model_slices = slice_csgmesh_ex(range(po.m_mesh_to_slice), slice_grid, params, thr); + po.m_model_slices = slice_csgmesh_ex(po.mesh_to_slice(), slice_grid, params, thr); auto mit = slindex_it; for (size_t id = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 705a13b90..62f7eb5fe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -324,7 +324,7 @@ void ObjectClipper::on_update() auto partstoslice = po->get_parts_to_slice(); if (! partstoslice.empty()) { mc = std::make_unique(); - mc->set_mesh(partstoslice); + mc->set_mesh(range(partstoslice)); mc_tr = Geometry::Transformation{po->trafo().inverse().cast()}; } } From bb82ce90c95bb038298d3f6a1340a0e862c498aa Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 12 Jan 2023 19:18:33 +0100 Subject: [PATCH 063/206] Don't allow part type change to modifier type in SLA mode --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index be04d804d..13b1c52ba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1707,17 +1707,19 @@ void GLGizmoEmboss::draw_model_type() } } - ImGui::SameLine(); - if (type == modifier) { - draw_icon(IconType::modifier, IconState::hovered); - } else { - if(draw_button(IconType::modifier, is_last_solid_part)) - new_type = modifier; - if (ImGui::IsItemHovered()) { - if(is_last_solid_part) - ImGui::SetTooltip("%s", _u8L("You can't change a type of the last solid part of the object.").c_str()); - else if (type != modifier) - ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); + if (wxGetApp().plater()->printer_technology() != ptSLA) { + ImGui::SameLine(); + if (type == modifier) { + draw_icon(IconType::modifier, IconState::hovered); + } else { + if(draw_button(IconType::modifier, is_last_solid_part)) + new_type = modifier; + if (ImGui::IsItemHovered()) { + if(is_last_solid_part) + ImGui::SetTooltip("%s", _u8L("You can't change a type of the last solid part of the object.").c_str()); + else if (type != modifier) + ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); + } } } From cf4f07c2205ce5553f8d4db36db589f56d1627d4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 12 Jan 2023 19:38:24 +0100 Subject: [PATCH 064/206] Use shared pointers for SLA preview data To be able to survive a sudden cancellation and subsequent cleanup in the background thread --- src/libslic3r/SLAPrint.cpp | 19 ++++----------- src/libslic3r/SLAPrint.hpp | 4 ++-- src/libslic3r/SLAPrintSteps.cpp | 30 ++++++++++++++++-------- src/slic3r/GUI/GLCanvas3D.cpp | 14 +++++------ src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 3 ++- src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp | 6 ++++- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 5 +++- src/slic3r/GUI/Plater.cpp | 8 +++++-- 8 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 79cc2605f..80857e34a 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1014,26 +1014,15 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const return EMPTY_MESH; } -const TriangleMesh &SLAPrintObject::get_mesh_to_print() const +const std::shared_ptr & +SLAPrintObject::get_mesh_to_print() const { - const TriangleMesh *ret = nullptr; - int s = last_completed_step(); - if (s == slaposCount) - ret = &EMPTY_MESH; - - while (s >= 0 && !ret) { - if (!m_preview_meshes[s].empty()) - ret = &m_preview_meshes[s]; - + while (s > 0 && ! m_preview_meshes[s]) --s; - } - if (!ret) - ret = &EMPTY_MESH; - - return *ret; + return m_preview_meshes[s]; } std::vector SLAPrintObject::get_parts_to_slice() const diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index e6a4286ea..6d35f013d 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -121,7 +121,7 @@ public: // Get the mesh that is going to be printed with all the modifications // like hollowing and drilled holes. - const TriangleMesh & get_mesh_to_print() const; + const std::shared_ptr& get_mesh_to_print() const; std::vector get_parts_to_slice() const; @@ -379,7 +379,7 @@ private: // all its holes and cavities, negatives and positive volumes unified. // Essentially this should be a m_mesh_to_slice after the CSG operations // or an approximation of that. - std::array m_preview_meshes; + std::array, SLAPrintObjectStep::slaposCount + 1> m_preview_meshes; class HollowingData { diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index f3824f751..2f7243a9c 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -228,7 +228,11 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st handled = true; } else if (step == slaposDrillHoles && is_pure_model) { if (po.m_model_object->sla_drain_holes.empty()) { - m = po.m_preview_meshes[slaposHollowing].its; + // Get the last printable preview + auto &meshp = po.get_mesh_to_print(); + if (meshp) + m = *(meshp); + handled = true; } else if (can_hollow) { m = csgmesh_merge_positive_parts(r); @@ -286,7 +290,11 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st m = generate_preview_vdb(po, step); } - po.m_preview_meshes[step] = TriangleMesh{std::move(m)}; + assert(po.m_preview_meshes[step].empty()); + + if (!m.empty()) + po.m_preview_meshes[step] = + std::make_shared(std::move(m)); for (size_t i = size_t(step) + 1; i < slaposCount; ++i) { @@ -604,11 +612,12 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; - if (!po.m_supportdata) + if (!po.m_supportdata) { + auto &meshp = po.get_mesh_to_print(); + assert(meshp); po.m_supportdata = - std::make_unique( - po.get_mesh_to_print() - ); + std::make_unique(*meshp); + } po.m_supportdata->input.zoffset = csgmesh_positive_bb(po.m_mesh_to_slice) .min.z(); @@ -750,11 +759,12 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // repeated) if(po.m_config.pad_enable.getBool()) { - if (!po.m_supportdata) + if (!po.m_supportdata) { + auto &meshp = po.get_mesh_to_print(); + assert(meshp); po.m_supportdata = - std::make_unique( - po.get_mesh_to_print() - ); + std::make_unique(*meshp); + } // Get the distilled pad configuration from the config // (Again, despite it was retrieved in the previous step. Note that diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fa94e6abf..c9eb10060 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6628,7 +6628,7 @@ void GLCanvas3D::_load_sla_shells() return; auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, - const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { + const indexed_triangle_set& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); #if ENABLE_SMOOTH_NORMALS @@ -6641,22 +6641,22 @@ void GLCanvas3D::_load_sla_shells() v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0)); v.set_instance_rotation({ 0.0, 0.0, (double)instance.rotation }); v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); - v.set_convex_hull(mesh.convex_hull_3d()); + v.set_convex_hull(TriangleMesh{its_convex_hull(mesh)}); }; // adds objects' volumes for (const SLAPrintObject* obj : print->objects()) { unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); for (const SLAPrintObject::Instance& instance : obj->instances()) { - auto & m = obj->get_mesh_to_print(); - if (!m.empty()) { - add_volume(*obj, 0, instance, m, GLVolume::MODEL_COLOR[0], true); + std::shared_ptr m = obj->get_mesh_to_print(); + if (m && !m->empty()) { + add_volume(*obj, 0, instance, *m, GLVolume::MODEL_COLOR[0], true); // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when // through the update_volumes_colors_by_extruder() call. m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); - if (auto &tree_mesh = obj->support_mesh(); !tree_mesh.empty()) + if (auto &tree_mesh = obj->support_mesh().its; !tree_mesh.empty()) add_volume(*obj, -int(slaposSupportTree), instance, tree_mesh, GLVolume::SLA_SUPPORT_COLOR, true); - if (auto &pad_mesh = obj->pad_mesh(); !pad_mesh.empty()) + if (auto &pad_mesh = obj->pad_mesh().its; !pad_mesh.empty()) add_volume(*obj, -int(slaposPad), instance, pad_mesh, GLVolume::SLA_PAD_COLOR, false); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index ec6ea0a1f..5bd2d98fd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -55,7 +55,8 @@ void GLGizmoHollow::data_changed() } const SLAPrintObject* po = m_c->selection_info()->print_object(); - if (po != nullptr && po->get_mesh_to_print().empty()) + std::shared_ptr preview_mesh_ptr = po->get_mesh_to_print(); + if (po != nullptr && (!preview_mesh_ptr || preview_mesh_ptr->empty())) reslice_until_step(slaposAssembly); update_volumes(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp index 9e94b9604..a9876bed1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp @@ -48,7 +48,11 @@ void GLGizmoSlaBase::update_volumes() m_input_enabled = false; - TriangleMesh backend_mesh = po->get_mesh_to_print(); + TriangleMesh backend_mesh; + std::shared_ptr preview_mesh_ptr = po->get_mesh_to_print(); + if (preview_mesh_ptr) + backend_mesh = TriangleMesh{*preview_mesh_ptr}; + if (!backend_mesh.empty()) { // The backend has generated a valid mesh. Use it backend_mesh.transform(po->trafo().inverse()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 62f7eb5fe..b33b0b202 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -261,7 +261,10 @@ void Raycaster::on_update() // For sla printers we use the mesh generated by the backend const SLAPrintObject* po = get_pool()->selection_info()->print_object(); assert(po != nullptr); - m_sla_mesh_cache = po->get_mesh_to_print(); + std::shared_ptr preview_mesh_ptr = po->get_mesh_to_print(); + if (preview_mesh_ptr) + m_sla_mesh_cache = TriangleMesh{*preview_mesh_ptr}; + if (!m_sla_mesh_cache.empty()) { m_sla_mesh_cache.transform(po->trafo().inverse()); meshes.emplace_back(&m_sla_mesh_cache); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dbdd24d15..a0ed276af 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6115,7 +6115,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only) const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); - if (object->get_mesh_to_print().empty()) + if (auto m = object->get_mesh_to_print(); !m || m->empty()) mesh = mesh_to_export_fff(mo, instance_id); else { const Transform3d mesh_trafo_inv = object->trafo().inverse(); @@ -6155,7 +6155,11 @@ void Plater::export_stl_obj(bool extended, bool selection_only) inst_mesh.merge(inst_supports_mesh); } - TriangleMesh inst_object_mesh = object->get_mesh_to_print(); + std::shared_ptr m = object->get_mesh_to_print(); + TriangleMesh inst_object_mesh; + if (m) + inst_object_mesh = TriangleMesh{*m}; + inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); From 7858b5d3cd9ea1148a1da9ac5a87eb6ea98a7b53 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 13 Jan 2023 09:45:20 +0100 Subject: [PATCH 065/206] Fix the missing merge option from multi-selection menu in SLA --- src/slic3r/GUI/GUI_ObjectList.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a5ddcf94c..9d0311e63 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2595,9 +2595,6 @@ void ObjectList::delete_all_connectors_for_object(int obj_idx) bool ObjectList::can_merge_to_multipart_object() const { - if (printer_technology() == ptSLA || has_selected_cut_object()) - return false; - wxDataViewItemArray sels; GetSelections(sels); if (sels.IsEmpty()) From 53e358f32c50e5524316d5d82da788a0463a4de7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 Jan 2023 09:59:41 +0100 Subject: [PATCH 066/206] Fix crash when cancelling part type change dialog --- src/slic3r/GUI/GUI_ObjectList.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8a5d6f139..0b03ecabe 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4301,7 +4301,10 @@ void ObjectList::change_part_type() int selection = 0; if (auto it = std::find(types.begin(), types.end(), type); it != types.end()) selection = it - types.begin(); - const auto new_type = types[wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, selection)]; + + auto choice = wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, selection); + const auto new_type = choice >= 0 ? types[choice] : ModelVolumeType::INVALID; + if (new_type == type || new_type == ModelVolumeType::INVALID) return; From 7e74f781c62a0441e3d15f8709d4aa15d12195b4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 Jan 2023 13:41:07 +0100 Subject: [PATCH 067/206] Minor fixes to sla print steps --- src/libslic3r/SLAPrintSteps.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 2f7243a9c..f2496091d 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -205,6 +205,8 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st if (cgalmeshptr) { m = MeshBoolean::cgal::cgal_to_indexed_triangle_set(*cgalmeshptr); handled = true; + } else { + BOOST_LOG_TRIVIAL(warning) << "CSG mesh is not egligible for proper CGAL booleans!"; } } else { // Normal cgal processing failed. If there are no negative volumes, @@ -290,7 +292,7 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st m = generate_preview_vdb(po, step); } - assert(po.m_preview_meshes[step].empty()); + assert(!po.m_preview_meshes[step]); if (!m.empty()) po.m_preview_meshes[step] = @@ -376,7 +378,7 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); po.m_hollowing_data->interior = std::move(interior); - indexed_triangle_set m = sla::get_mesh(*po.m_hollowing_data->interior); + indexed_triangle_set &m = sla::get_mesh(*po.m_hollowing_data->interior); if (!m.empty()) { // simplify mesh lossless @@ -390,7 +392,8 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) // Put the interior into the target mesh as a negative po.m_mesh_to_slice .emplace(slaposHollowing, - csg::CSGPart{std::make_shared(std::move(m)), csg::CSGType::Difference}); + csg::CSGPart{std::make_shared(m), + csg::CSGType::Difference}); generate_preview(po, slaposHollowing); } From 7d6c2cad0a6411c4be5a9322e9c19115e957ba33 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 Jan 2023 19:39:30 +0100 Subject: [PATCH 068/206] Hotfix for hollowing issues --- src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 6 +++++- src/libslic3r/OpenVDBUtils.cpp | 5 +++++ src/libslic3r/OpenVDBUtils.hpp | 2 ++ src/libslic3r/SLA/Hollowing.cpp | 17 ++++++++-------- src/libslic3r/SLA/Hollowing.hpp | 24 +++++++++++------------ src/libslic3r/SLAPrint.cpp | 2 +- 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index c76187723..f64d17b9a 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -37,7 +37,11 @@ inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src) switch (op) { case CSGType::Union: - grid_union(*dst, *src); + if (is_grid_empty(*dst) && !is_grid_empty(*src)) + dst = clone(*src); + else + grid_union(*dst, *src); + break; case CSGType::Difference: grid_difference(*dst, *src); diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 5d00fac0d..21409445f 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -300,4 +300,9 @@ void rescale_grid(VoxelGrid &grid, float scale) // grid.grid.setTransform(tr); } +bool is_grid_empty(const VoxelGrid &grid) +{ + return grid.grid.empty(); +} + } // namespace Slic3r diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index cb857020f..d4996854c 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -77,6 +77,8 @@ void grid_union(VoxelGrid &grid, VoxelGrid &arg); void grid_difference(VoxelGrid &grid, VoxelGrid &arg); void grid_intersection(VoxelGrid &grid, VoxelGrid &arg); +bool is_grid_empty(const VoxelGrid &grid); + } // namespace Slic3r #endif // OPENVDBUTILS_HPP diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 31289e7ba..634cde469 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -75,11 +75,12 @@ InteriorPtr generate_interior(const VoxelGrid &vgrid, const HollowingConfig &hc, const JobController &ctl) { - double offset = hc.min_thickness; - double D = hc.closing_distance; - float in_range = 1.1f * float(offset + D); - auto narrowb = 3.f / get_voxel_scale(vgrid); - float out_range = narrowb; + double voxsc = get_voxel_scale(vgrid); + double offset = hc.min_thickness; // world units + double D = hc.closing_distance; // world units + float in_range = 1.1f * float(offset + D); // world units + float out_range = 1.f / voxsc; // world units + auto narrowb = 1.f; // voxel units (voxel count) if (ctl.stopcondition()) return {}; else ctl.statuscb(0, L("Hollowing")); @@ -91,12 +92,12 @@ InteriorPtr generate_interior(const VoxelGrid &vgrid, double iso_surface = D; if (D > EPSILON) { - in_range = narrowb; - gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, in_range); + gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); - gridptr = dilate_grid(*gridptr, std::ceil(iso_surface), 0.f); + gridptr = dilate_grid(*gridptr, 1.1 * std::ceil(iso_surface), 0.f); out_range = iso_surface; + in_range = narrowb / voxsc; } else { iso_surface = -offset; } diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index c21bdff4e..b6c07aff5 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -91,15 +91,15 @@ inline InteriorPtr generate_interior(const indexed_triangle_set &mesh, auto statusfn = [&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); }; auto grid = mesh_to_grid(mesh, MeshToGridParams{} .voxel_scale(voxel_scale) - .exterior_bandwidth(1.f) - .interior_bandwidth(1.f) + .exterior_bandwidth(3.f) + .interior_bandwidth(3.f) .statusfn(statusfn)); if (!grid || (ctl.stopcondition && ctl.stopcondition())) return {}; - if (its_is_splittable(mesh)) - grid = redistance_grid(*grid, 0.0f, 6.f / voxel_scale, 6.f / voxel_scale); +// if (its_is_splittable(mesh)) + grid = redistance_grid(*grid, 0.0f, 3.f, 3.f); return grid ? generate_interior(*grid, hc, ctl) : InteriorPtr{}; } @@ -132,11 +132,12 @@ InteriorPtr generate_interior(const Range &csgparts, const JobController &ctl = {}) { double mesh_vol = csgmesh_positive_maxvolume(csgparts); + double voxsc = get_voxel_scale(mesh_vol, hc); auto params = csg::VoxelizeParams{} - .voxel_scale(get_voxel_scale(mesh_vol, hc)) - .exterior_bandwidth(1.f) - .interior_bandwidth(1.f) + .voxel_scale(voxsc) + .exterior_bandwidth(3.f) + .interior_bandwidth(3.f) .statusfn([&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); }); auto ptr = csg::voxelize_csgmesh(csgparts, params); @@ -144,11 +145,10 @@ InteriorPtr generate_interior(const Range &csgparts, if (!ptr || (ctl.stopcondition && ctl.stopcondition())) return {}; - if (csgparts.size() > 1 || its_is_splittable(*csg::get_mesh(*csgparts.begin()))) - ptr = redistance_grid(*ptr, - 0.0f, - 6.f / params.voxel_scale(), - 6.f / params.voxel_scale()); + // TODO: figure out issues without the redistance +// if (csgparts.size() > 1 || its_is_splittable(*csg::get_mesh(*csgparts.begin()))) + + ptr = redistance_grid(*ptr, 0.0f, 3.f, 3.f); return ptr ? generate_interior(*ptr, hc, ctl) : InteriorPtr{}; } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 80857e34a..03c0df4ef 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -13,7 +13,7 @@ #include #include -// #define SLAPRINT_DO_BENCHMARK + #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK #include From 0fa9bcc63b6daee4c58d73bb726e3311c78ea66b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 Jan 2023 19:47:35 +0100 Subject: [PATCH 069/206] Fix crash with fff mmu --- src/slic3r/GUI/GLCanvas3D.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6b8499dde..291cc57ec 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7100,9 +7100,12 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) const ModelVolume * ret = nullptr; if (model.objects.size() < v.object_idx()) { - const ModelObject *obj = model.objects[v.object_idx()]; - if (obj->volumes.size() < v.volume_idx()) - ret = obj->volumes[v.volume_idx()]; + if (v.object_idx() < model.objects.size()) { + const ModelObject *obj = model.objects[v.object_idx()]; + if (v.volume_idx() < obj->volumes.size()) { + ret = obj->volumes[v.volume_idx()]; + } + } } return ret; From 9ee71ddd925b1f7ac2a3ca7daffb72f09d347a0e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 17 Jan 2023 11:23:18 +0100 Subject: [PATCH 070/206] WIP fixing trafos --- src/libslic3r/SLA/SupportPoint.hpp | 9 ++++++++- src/libslic3r/SLA/SupportPointGenerator.cpp | 12 ++++++++++++ src/libslic3r/SLAPrint.cpp | 16 ++++++---------- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 ++-- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 71849a364..cf6dbabba 100644 --- a/src/libslic3r/SLA/SupportPoint.hpp +++ b/src/libslic3r/SLA/SupportPoint.hpp @@ -3,7 +3,11 @@ #include -namespace Slic3r { namespace sla { +namespace Slic3r { + +class ModelObject; + +namespace sla { // An enum to keep track of where the current points on the ModelObject came from. enum class PointsStatus { @@ -62,6 +66,9 @@ struct SupportPoint using SupportPoints = std::vector; +SupportPoints transformed_support_points(const ModelObject &mo, + const Transform3d &trafo); + }} // namespace Slic3r::sla #endif // SUPPORTPOINT_HPP diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 6d159b37b..193333bc6 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -661,5 +661,17 @@ void SupportPointGenerator::output_expolygons(const ExPolygons& expolys, const s } #endif +SupportPoints transformed_support_points(const ModelObject &mo, + const Transform3d &trafo) +{ + auto spts = mo.sla_support_points; + Transform3f tr = trafo.cast(); + for (sla::SupportPoint& suppt : spts) { + suppt.pos = tr * suppt.pos; + } + + return spts; +} + } // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 03c0df4ef..71d6e889d 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1051,20 +1051,16 @@ SLAPrintObject::get_parts_to_slice(SLAPrintObjectStep untilstep) const sla::SupportPoints SLAPrintObject::transformed_support_points() const { - assert(m_model_object != nullptr); - auto spts = m_model_object->sla_support_points; - const Transform3d& vol_trafo = m_model_object->volumes.front()->get_transformation().get_matrix(); - const Transform3f& tr = (trafo() * vol_trafo).cast(); - for (sla::SupportPoint& suppt : spts) { - suppt.pos = tr * suppt.pos; - } - - return spts; + assert(model_object()); + + return sla::transformed_support_points(*model_object(), trafo()); } sla::DrainHoles SLAPrintObject::transformed_drainhole_points() const { - return sla::transformed_drainhole_points(*this->model_object(), trafo()); + assert(model_object()); + + return sla::transformed_drainhole_points(*model_object(), trafo()); } DynamicConfig SLAPrintStatistics::config() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 5bd2d98fd..31252d319 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -123,7 +123,7 @@ void GLGizmoHollow::render_points(const Selection& selection) return; double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); - Transform3d trafo(inst->get_transformation().get_matrix() * inst->get_object()->volumes.front()->get_matrix()); + Transform3d trafo(inst->get_transformation().get_matrix()); trafo.translation()(2) += shift_z; const Geometry::Transformation transformation{trafo}; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index b117a2beb..301bbcdbe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -153,7 +153,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) return; double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); - Transform3d trafo(inst->get_transformation().get_matrix() * inst->get_object()->volumes.front()->get_matrix()); + Transform3d trafo = inst->get_transformation().get_matrix(); trafo.translation()(2) += shift_z; const Geometry::Transformation transformation{trafo}; @@ -1084,7 +1084,7 @@ void GLGizmoSlaSupports::get_data_from_backend() if (po->model_object()->id() == mo->id()) { m_normal_cache.clear(); const std::vector& points = po->get_support_points(); - auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast(); + auto mat = po->trafo().inverse().cast(); for (unsigned int i=0; i Date: Tue, 17 Jan 2023 13:13:09 +0100 Subject: [PATCH 071/206] Fixing sla support point and drillhole transformations May brake compatibility with 3mf files storing these objects from from previous Slic3r versions --- src/libslic3r/SLAPrintSteps.cpp | 2 -- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 13 +++++++++++-- src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp | 13 ++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 15 ++++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index f2496091d..8a71f2da7 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -292,8 +292,6 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st m = generate_preview_vdb(po, step); } - assert(!po.m_preview_meshes[step]); - if (!m.empty()) po.m_preview_meshes[step] = std::make_shared(std::move(m)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 31252d319..fc54622e8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -463,7 +463,16 @@ void GLGizmoHollow::update_hole_raycasters_for_picking_transform() assert(!m_hole_raycasters.empty()); const GLVolume* vol = m_parent.get_selection().get_first_volume(); - const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse(); + Geometry::Transformation transformation(vol->get_instance_transformation()); + + auto *inst = m_c->selection_info()->model_instance(); + if (inst && m_c->selection_info() && m_c->selection_info()->print_object()) { + double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); + auto trafo = inst->get_transformation().get_matrix(); + trafo.translation()(2) += shift_z; + transformation.set_matrix(trafo); + } + const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); for (size_t i = 0; i < drain_holes.size(); ++i) { const sla::DrainHole& drain_hole = drain_holes[i]; @@ -471,7 +480,7 @@ void GLGizmoHollow::update_hole_raycasters_for_picking_transform() Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); const Eigen::AngleAxisd aa(q); - const Transform3d matrix = vol->world_matrix() * hole_matrix * Transform3d(aa.toRotationMatrix()) * + const Transform3d matrix = transformation.get_matrix() * hole_matrix * Transform3d(aa.toRotationMatrix()) * Geometry::translation_transform(-drain_hole.height * Vec3d::UnitZ()) * Geometry::scale_transform(Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); m_hole_raycasters[i]->set_transform(matrix); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp index a9876bed1..444e3bf46 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp @@ -148,12 +148,23 @@ bool GLGizmoSlaBase::unproject_on_mesh(const Vec2d& mouse_pos, std::pairselection_info()->model_instance(); + if (!inst) + return false; + + Transform3d trafo = m_volumes.volumes.front()->world_matrix(); + if (m_c->selection_info() && m_c->selection_info()->print_object()) { + double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); + trafo = inst->get_transformation().get_matrix(); + trafo.translation()(2) += shift_z; + } + // The raycaster query Vec3f hit; Vec3f normal; if (m_c->raycaster()->raycaster()->unproject_on_mesh( mouse_pos, - m_volumes.volumes.front()->world_matrix(), + trafo/*m_volumes.volumes.front()->world_matrix()*/, wxGetApp().plater()->get_camera(), hit, normal, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 301bbcdbe..e7ea945e3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1184,7 +1184,16 @@ void GLGizmoSlaSupports::update_point_raycasters_for_picking_transform() assert(!m_point_raycasters.empty()); const GLVolume* vol = m_parent.get_selection().get_first_volume(); - const Geometry::Transformation transformation(vol->world_matrix()); + Geometry::Transformation transformation(vol->world_matrix()); + + auto *inst = m_c->selection_info()->model_instance(); + if (inst && m_c->selection_info() && m_c->selection_info()->print_object()) { + double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); + auto trafo = inst->get_transformation().get_matrix(); + trafo.translation()(2) += shift_z; + transformation.set_matrix(trafo); + } + const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); for (size_t i = 0; i < m_editing_cache.size(); ++i) { const Transform3d support_matrix = Geometry::translation_transform(m_editing_cache[i].support_point.pos.cast()) * instance_scaling_matrix_inverse; @@ -1195,13 +1204,13 @@ void GLGizmoSlaSupports::update_point_raycasters_for_picking_transform() Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); const Eigen::AngleAxisd aa(q); - const Transform3d cone_matrix = vol->world_matrix() * support_matrix * Transform3d(aa.toRotationMatrix()) * + const Transform3d cone_matrix = transformation.get_matrix() * support_matrix * Transform3d(aa.toRotationMatrix()) * Geometry::assemble_transform((CONE_HEIGHT + m_editing_cache[i].support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(), Vec3d(PI, 0.0, 0.0), Vec3d(CONE_RADIUS, CONE_RADIUS, CONE_HEIGHT)); m_point_raycasters[i].second->set_transform(cone_matrix); const double radius = (double)m_editing_cache[i].support_point.head_front_radius * RenderPointScale; - const Transform3d sphere_matrix = vol->world_matrix() * support_matrix * Geometry::scale_transform(radius); + const Transform3d sphere_matrix = transformation.get_matrix() * support_matrix * Geometry::scale_transform(radius); m_point_raycasters[i].first->set_transform(sphere_matrix); } } From 53287002b30faeee43509e7a3ba486c31d269d65 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 17 Jan 2023 17:18:29 +0100 Subject: [PATCH 072/206] Fix crash when changing hollowing thickness --- src/libslic3r/SLAPrintSteps.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 8a71f2da7..269195bc0 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -349,6 +349,7 @@ void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po) void SLAPrint::Steps::hollow_model(SLAPrintObject &po) { po.m_hollowing_data.reset(); + po.m_supportdata.reset(); clear_csg(po.m_mesh_to_slice, slaposDrillHoles); clear_csg(po.m_mesh_to_slice, slaposHollowing); @@ -400,6 +401,7 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) // Drill holes into the hollowed/original mesh. void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { + po.m_supportdata.reset(); clear_csg(po.m_mesh_to_slice, slaposDrillHoles); csg::model_to_csgmesh(*po.model_object(), po.trafo(), From bef64ecec083a2c2e59bd18e373652c597bd6c35 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 15:20:45 +0200 Subject: [PATCH 073/206] Don't call search_ground_route for every ground point candidate search_ground_route already tries to find a suitable ground point globally from the source. Different destination arguments will not help much but will cause a lot of redundant cpu burning SPE-1303 --- src/libslic3r/BranchingTree/BranchingTree.cpp | 2 +- src/libslic3r/BranchingTree/BranchingTree.hpp | 1 - src/libslic3r/SLA/BranchingTreeSLA.cpp | 14 +++++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 6463a30de..3e5e0a686 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -5,7 +5,7 @@ #include #include -#include "libslic3r/SLA/SupportTreeUtils.hpp" +#include "libslic3r/TriangleMesh.hpp" namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index c88410b3a..b54d47ca2 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -5,7 +5,6 @@ #include #include "libslic3r/ExPolygon.hpp" -#include "libslic3r/BoundingBox.hpp" namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 72314c9d8..59ec05113 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -18,6 +18,8 @@ class BranchingTreeBuilder: public branchingtree::Builder { const SupportableMesh &m_sm; const branchingtree::PointCloud &m_cloud; + std::set m_ground_mem; + // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour static constexpr double WIDENING_SCALE = 0.02; @@ -156,11 +158,21 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node, bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, const branchingtree::Node &to) { - bool ret = search_ground_route(ex_tbb, m_builder, m_sm, + bool ret = false; + + auto it = m_ground_mem.find(from.id); + if (it == m_ground_mem.end()) { + ret = search_ground_route(ex_tbb, m_builder, m_sm, sla::Junction{from.pos.cast(), get_radius(from)}, get_radius(to)).first; + // Remember that this node was tested if can go to ground, don't + // test it with any other destination ground point because + // it is unlikely that search_ground_route would find a better solution + m_ground_mem.insert(from.id); + } + if (ret) { build_subtree(from.id); } From 31fb0ae04902acd0083e09baca0d3b632fa60b6e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 16:58:06 +0200 Subject: [PATCH 074/206] Do not add junctions for elongated bed connections It prevents search_ground_route from finding good solutions This should at least improve on SPE-1311 --- src/libslic3r/BranchingTree/BranchingTree.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 3e5e0a686..72ecf8c86 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,18 +76,7 @@ void build_tree(PointCloud &nodes, Builder &builder) switch (type) { case BED: { closest_node.weight = w; - if (closest_it->dst_branching > nodes.properties().max_branch_length()) { - auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; - Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; - new_node.id = int(nodes.next_junction_id()); - new_node.weight = nodes.get(node_id).weight + hl_br_len; - new_node.left = node.id; - if ((routed = builder.add_bridge(node, new_node))) { - size_t new_idx = nodes.insert_junction(new_node); - ptsqueue.push(new_idx); - } - } - else if ((routed = builder.add_ground_bridge(node, closest_node))) { + if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; nodes.mark_unreachable(closest_node_id); From f5c16236421d7ae29674fc7cafea9bb5299ec24c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 17:33:58 +0200 Subject: [PATCH 075/206] Try routing unsuccessful branches to ground recursively This helps to avoid huge branches being discarded where there is an obvious route to ground for a sub-branch. Should improve SPE-1311 --- src/libslic3r/BranchingTree/PointCloud.hpp | 6 +++--- src/libslic3r/SLA/BranchingTreeSLA.cpp | 23 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index f99b17990..143ab72ce 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -259,9 +259,9 @@ template void traverse(PC &&pc, size_t root, Fn &&fn) { if (auto nodeptr = pc.find(root); nodeptr != nullptr) { auto &nroot = *nodeptr; - fn(nroot); - if (nroot.left >= 0) traverse(pc, nroot.left, fn); - if (nroot.right >= 0) traverse(pc, nroot.right, fn); + bool r = fn(nroot); + if (r && nroot.left >= 0) traverse(pc, nroot.left, fn); + if (r && nroot.right >= 0) traverse(pc, nroot.right, fn); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 59ec05113..2660b8dea 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -61,22 +61,41 @@ class BranchingTreeBuilder: public branchingtree::Builder { toR); m_builder.add_junction(tod, toR); } + + return true; }); } void discard_subtree(size_t root) { // Discard all the support points connecting to this branch. + // As a last resort, try to route child nodes to ground and stop + // traversing if any child branch succeeds. traverse(m_cloud, root, [this](const branchingtree::Node &node) { + bool ret = true; + int suppid_parent = m_cloud.get_leaf_id(node.id); - int suppid_left = m_cloud.get_leaf_id(node.left); - int suppid_right = m_cloud.get_leaf_id(node.right); + int suppid_left = branchingtree::Node::ID_NONE; + int suppid_right = branchingtree::Node::ID_NONE; + + if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), node)) + ret = false; + else + suppid_left = m_cloud.get_leaf_id(node.left); + + if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), node)) + ret = false; + else + suppid_right = m_cloud.get_leaf_id(node.right); + if (suppid_parent >= 0) m_unroutable_pinheads.emplace_back(suppid_parent); if (suppid_left >= 0) m_unroutable_pinheads.emplace_back(suppid_left); if (suppid_right >= 0) m_unroutable_pinheads.emplace_back(suppid_right); + + return ret; }); } From 1b54235d67ae8c1d49b14a821ff10a1c7e009c2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:34:56 +0200 Subject: [PATCH 076/206] connect_to_ground() now handles widening correctly --- src/libslic3r/SLA/SupportTreeUtils.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 38515f879..983380d12 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -558,8 +558,7 @@ bool optimize_pinhead_placement(Ex policy, m.cfg.head_back_radius_mm; // check available distance - Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, - sd); + Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd); if (t.distance() < w) { // Let's try to optimize this angle, there might be a @@ -663,11 +662,13 @@ std::pair connect_to_ground(Ex policy, return {false, SupportTreeNode::ID_UNSET}; Vec3d endp = hjp + d * dir; - auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r); + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); + auto ret = create_ground_pillar(policy, builder, sm, endp, dir, pill_r, end_r); if (ret.second >= 0) { - builder.add_bridge(hjp, endp, r); - builder.add_junction(endp, r); + builder.add_diffbridge(hjp, endp, r, pill_r); + builder.add_junction(endp, pill_r); } return ret; @@ -687,9 +688,9 @@ std::pair search_ground_route(Ex policy, if (res.first) return res; - // Optimize bridge direction: - // Straight path failed so we will try to search for a suitable - // direction out of the cavity. + // Optimize bridge direction: + // Straight path failed so we will try to search for a suitable + // direction out of the cavity. auto [polar, azimuth] = dir_to_spheric(init_dir); Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); From ba6b202aa41f30ccc924921b3ff06db7bb2f487a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:05 +0200 Subject: [PATCH 077/206] Fix weight calculation for fallback ground routing in branching tree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 2660b8dea..633b18324 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -78,12 +78,16 @@ class BranchingTreeBuilder: public branchingtree::Builder { int suppid_left = branchingtree::Node::ID_NONE; int suppid_right = branchingtree::Node::ID_NONE; - if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), node)) + branchingtree::Node dst = node; + dst.weight += node.pos.z(); + dst.Rmin = std::max(node.Rmin, dst.Rmin); + + if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) ret = false; else suppid_left = m_cloud.get_leaf_id(node.left); - if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), node)) + if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst)) ret = false; else suppid_right = m_cloud.get_leaf_id(node.right); @@ -143,7 +147,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, double fromR = get_radius(from), toR = get_radius(to); Beam beam{Ball{fromd, fromR}, Ball{tod, toR}}; auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam, - m_sm.cfg.safety_distance_mm); + 0.9 * m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); From ccab4b3dc9c32479ab0298ee1df4e243a7b4304f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:19 +0200 Subject: [PATCH 078/206] Improve optimization accuracy --- src/libslic3r/SLA/SupportTree.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 0ac29ff7a..66262fb34 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -94,8 +94,8 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; - static const double constexpr optimizer_rel_score_diff = 1e-6; - static const unsigned constexpr optimizer_max_iterations = 1000; + static const double constexpr optimizer_rel_score_diff = 1e-10; + static const unsigned constexpr optimizer_max_iterations = 2000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; From 6448f36c2bbf1304b4ed9896fc5082cba08428da Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:32 +0200 Subject: [PATCH 079/206] Remove redundant headers --- src/libslic3r/BranchingTree/PointCloud.cpp | 1 - src/libslic3r/BranchingTree/PointCloud.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index f1d7ae521..5f07d91c7 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -1,6 +1,5 @@ #include "PointCloud.hpp" -#include "libslic3r/Geometry.hpp" #include "libslic3r/Tesselate.hpp" #include diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index 143ab72ce..08a1eb8e0 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -5,7 +5,7 @@ #include "BranchingTree.hpp" -#include "libslic3r/Execution/Execution.hpp" +//#include "libslic3r/Execution/Execution.hpp" #include "libslic3r/MutablePriorityQueue.hpp" #include "libslic3r/BoostAdapter.hpp" From 15a1d9a50a0185e4dbaacf89228d6c8da0012425 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Oct 2022 16:10:06 +0200 Subject: [PATCH 080/206] Keep track of avoidance paths and merge them if possible --- src/libslic3r/BranchingTree/BranchingTree.hpp | 5 + src/libslic3r/BranchingTree/PointCloud.hpp | 41 ++++-- src/libslic3r/SLA/BranchingTreeSLA.cpp | 86 ++++++++++-- src/libslic3r/SLA/SupportTreeBuilder.hpp | 4 + src/libslic3r/SLA/SupportTreeUtils.hpp | 129 +++++++++++++----- 5 files changed, 208 insertions(+), 57 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index b54d47ca2..fd69fe4cd 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -76,6 +76,11 @@ struct Node {} }; +inline bool is_occupied(const Node &n) +{ + return n.left != Node::ID_NONE && n.right != Node::ID_NONE; +} + // An output interface for the branching tree generator function. Consider each // method as a callback and implement the actions that need to be done. class Builder diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index 08a1eb8e0..cbd01a465 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -78,14 +78,6 @@ private: rtree /* ? */> m_ktree; - bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const - { - Vec3d D = (pt - supp).cast(); - double dot_sq = -D.z() * std::abs(-D.z()); - - return dot_sq < D.squaredNorm() * cos2bridge_slope; - } - template static auto *get_node(PC &&pc, size_t id) { @@ -104,6 +96,14 @@ private: public: + bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const + { + Vec3d D = (pt - supp).cast(); + double dot_sq = -D.z() * std::abs(-D.z()); + + return dot_sq < D.squaredNorm() * cos2bridge_slope; + } + static constexpr auto Unqueued = size_t(-1); struct ZCompareFn @@ -255,13 +255,32 @@ public: } }; +template constexpr bool IsTraverseFn = std::is_invocable_v; + +struct TraverseReturnT +{ + bool to_left : 1; // if true, continue traversing to the left + bool to_right : 1; // if true, continue traversing to the right +}; + template void traverse(PC &&pc, size_t root, Fn &&fn) { if (auto nodeptr = pc.find(root); nodeptr != nullptr) { auto &nroot = *nodeptr; - bool r = fn(nroot); - if (r && nroot.left >= 0) traverse(pc, nroot.left, fn); - if (r && nroot.right >= 0) traverse(pc, nroot.right, fn); + TraverseReturnT ret{true, true}; + + if constexpr (std::is_same_v, void>) { + // Our fn has no return value + fn(nroot); + } else { + // Fn returns instructions about how to continue traversing + ret = fn(nroot); + } + + if (ret.to_left && nroot.left >= 0) + traverse(pc, nroot.left, fn); + if (ret.to_right && nroot.right >= 0) + traverse(pc, nroot.right, fn); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 633b18324..b8c34399f 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,6 +20,14 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::set m_ground_mem; + // Establish an index of + using PointIndexEl = std::pair; + boost::geometry::index:: + rtree /* ? */> + m_pillar_index; + + std::vector m_pillars; + // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour static constexpr double WIDENING_SCALE = 0.02; @@ -61,8 +69,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { toR); m_builder.add_junction(tod, toR); } - - return true; }); } @@ -72,7 +78,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // As a last resort, try to route child nodes to ground and stop // traversing if any child branch succeeds. traverse(m_cloud, root, [this](const branchingtree::Node &node) { - bool ret = true; + branchingtree::TraverseReturnT ret{true, true}; int suppid_parent = m_cloud.get_leaf_id(node.id); int suppid_left = branchingtree::Node::ID_NONE; @@ -83,12 +89,12 @@ class BranchingTreeBuilder: public branchingtree::Builder { dst.Rmin = std::max(node.Rmin, dst.Rmin); if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) - ret = false; + ret.to_left = false; else suppid_left = m_cloud.get_leaf_id(node.left); if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst)) - ret = false; + ret.to_right = false; else suppid_right = m_cloud.get_leaf_id(node.right); @@ -141,7 +147,7 @@ public: }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, - const branchingtree::Node &to) + const branchingtree::Node &to) { Vec3d fromd = from.pos.cast(), tod = to.pos.cast(); double fromR = get_radius(from), toR = get_radius(to); @@ -183,12 +189,72 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, { bool ret = false; + namespace bgi = boost::geometry::index; + + struct Output { + std::optional &res; + + Output& operator *() { return *this; } + void operator=(const PointIndexEl &el) { res = el; } + Output& operator++() { return *this; } + }; + auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - ret = search_ground_route(ex_tbb, m_builder, m_sm, - sla::Junction{from.pos.cast(), - get_radius(from)}, - get_radius(to)).first; + std::optional result; + auto filter = bgi::satisfies( + [this, &from](const PointIndexEl &e) { + auto len = (from.pos - e.first).norm(); + return !branchingtree::is_occupied(m_pillars[e.second]) + && len < m_sm.cfg.max_bridge_length_mm + && !m_cloud.is_outside_support_cone(from.pos, e.first) + && beam_mesh_hit(ex_tbb, + m_sm.emesh, + Beam{Ball{from.pos.cast(), + get_radius(from)}, + Ball{e.first.cast(), + get_radius( + m_pillars[e.second])}}, + 0.9 * m_sm.cfg.safety_distance_mm) + .distance() + > len; + }); + m_pillar_index.query(filter && bgi::nearest(from.pos, 1), Output{result}); + + sla::Junction j{from.pos.cast(), get_radius(from)}; + if (!result) { + auto [found_conn, cjunc] = optimize_ground_connection( + ex_tbb, + m_builder, + m_sm, + j, + get_radius(to)); + + if (found_conn) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; + auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); + + if (plr.second >= 0) { + m_builder.add_junction(endp, R); + if (cjunc) { + m_builder.add_diffbridge(j.pos, endp, j.r, R); + branchingtree::Node n{cjunc->pos.cast(), float(R)}; + n.left = from.id; + m_pillars.emplace_back(n); + m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + } + + ret = true; + } + } + } else { + const auto &resnode = m_pillars[result->second]; + m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); + m_pillars[result->second].right = from.id; + ret = true; + } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 29d34ab8e..87e250f65 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -189,6 +189,10 @@ struct DiffBridge: public Bridge { DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e) : Bridge{p_s, p_e, r_s}, end_r{r_e} {} + + DiffBridge(const Junction &j_s, const Junction &j_e) + : Bridge{j_s.pos, j_e.pos, j_s.r}, end_r{j_e.r} + {} }; // This class will hold the support tree parts (not meshes, but logical parts) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 983380d12..eed5e7938 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -283,17 +283,17 @@ Hit pinhead_mesh_hit(Ex ex, template Hit pinhead_mesh_hit(Ex ex, - const AABBMesh &mesh, - const Head &head, - double safety_d) + const AABBMesh &mesh, + const Head &head, + double safety_d) { return pinhead_mesh_hit(ex, mesh, head.pos, head.dir, head.r_pin_mm, - head.r_back_mm, head.width_mm, safety_d); + head.r_back_mm, head.width_mm, safety_d); } template -std::optional search_widening_path(Ex policy, - const SupportableMesh &sm, +std::optional search_widening_path(Ex policy, + const SupportableMesh &sm, const Vec3d &jp, const Vec3d &dir, double radius, @@ -635,56 +635,61 @@ std::optional calculate_pinhead_placement(Ex policy, } template -std::pair connect_to_ground(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) +std::pair> find_ground_connection( + Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + const Vec3d &dir, + double end_r) { auto hjp = j.pos; double r = j.r; auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); - double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); - double d = 0, tdown = 0; - t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); + double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd) + .distance(); + double d = 0, tdown = 0; + t = std::min(t, + sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); - while (d < t && - !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, - Beam{hjp + d * dir, DOWN, r, r2}, sd) - .distance())) { + while ( + d < t && + !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, + Beam{hjp + d * dir, DOWN, r, r2}, sd) + .distance())) { d += r; } - if(!std::isinf(tdown)) - return {false, SupportTreeNode::ID_UNSET}; + std::pair> ret; - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); - double pill_r = r + bridge_ratio * (end_r - r); - auto ret = create_ground_pillar(policy, builder, sm, endp, dir, pill_r, end_r); + if (std::isinf(tdown)) { + ret.first = true; + if (d > 0) { + Vec3d endp = hjp + d * dir; + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); - if (ret.second >= 0) { - builder.add_diffbridge(hjp, endp, r, pill_r); - builder.add_junction(endp, pill_r); + ret.second = Junction{endp, pill_r}; + } } return ret; } template -std::pair search_ground_route(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - double end_radius, - const Vec3d &init_dir = DOWN) +std::pair> optimize_ground_connection( + Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) { double downdst = j.pos.z() - ground_level(sm); - auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius); + auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); if (res.first) return res; @@ -710,7 +715,59 @@ std::pair search_ground_route(Ex policy, Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); - return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius); + return find_ground_connection(policy, builder, sm, j, bridgedir, end_radius); +} + +template +std::pair connect_to_ground(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + const Vec3d &dir, + double end_r) +{ + std::pair ret = {false, SupportTreeNode::ID_UNSET}; + + auto [found_c, cjunc] = find_ground_connection(policy, builder, sm, j, dir, end_r); + + if (found_c) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + + if (ret.second >= 0) { + builder.add_diffbridge(j.pos, endp, j.r, R); + builder.add_junction(endp, R); + } + } + + return ret; +} + +template +std::pair search_ground_route(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + double end_r, + const Vec3d &init_dir = DOWN) +{ + std::pair ret = {false, SupportTreeNode::ID_UNSET}; + + auto [found_c, cjunc] = optimize_ground_connection(policy, builder, sm, j, end_r, init_dir); + if (found_c) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; + ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + + if (ret.second >= 0) { + builder.add_diffbridge(j.pos, endp, j.r, R); + builder.add_junction(endp, R); + } + } + + return ret; } template From 84784259ba6deeb742209034c7bcd2c7629704b9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Oct 2022 17:17:59 +0200 Subject: [PATCH 081/206] Add max_weight_on_model parameter Limiting the weight of subtrees going to the model body --- src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 11 +++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 3 +++ src/libslic3r/SLA/SupportTree.hpp | 4 +++- src/libslic3r/SLAPrint.cpp | 2 ++ src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 206844b7b..ce3c27bf6 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -501,6 +501,7 @@ static std::vector s_Preset_sla_print_options { "support_pillar_diameter", "support_small_pillar_diameter_percent", "support_max_bridges_on_pillar", + "support_max_weight_on_model", "support_pillar_connection_mode", "support_buildplate_only", "support_pillar_widening_factor", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bc6d81b2d..482e36e43 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3676,6 +3676,17 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("support_max_weight_on_model", coFloat); + def->label = L("Max weight on model"); + def->category = L("Supports"); + def->tooltip = L( + "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " + "branches emanating from the endpoint."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10.)); + def = this->add("support_pillar_connection_mode", coEnum); def->label = L("Pillar connection mode"); def->tooltip = L("Controls the bridge type between two neighboring pillars." diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a36125a07..47c43148a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -861,6 +861,8 @@ PRINT_CONFIG_CLASS_DEFINE( // Generate only ground facing supports ((ConfigOptionBool, support_buildplate_only)) + ((ConfigOptionFloat, support_max_weight_on_model)) + // TODO: unimplemented at the moment. This coefficient will have an impact // when bridges and pillars are merged. The resulting pillar should be a bit // thicker than the ones merging into it. How much thicker? I don't know diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index b8c34399f..62431a347 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -272,6 +272,9 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, const branchingtree::Node &to) { + if (from.weight > m_sm.cfg.max_weight_on_model_support) + return false; + sla::Junction fromj = {from.pos.cast(), get_radius(from)}; auto anchor = m_sm.cfg.ground_facing_only ? diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 66262fb34..051d56926 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -76,7 +76,9 @@ struct SupportTreeConfig double pillar_base_safety_distance_mm = 0.5; unsigned max_bridges_on_pillar = 3; - + + double max_weight_on_model_support = 10.f; + double head_fullwidth() const { return 2 * head_front_radius_mm + head_width_mm + 2 * head_back_radius_mm - head_penetration_mm; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d9b8e33df..21895f943 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -66,6 +66,7 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); + scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat(); return scfg; } @@ -843,6 +844,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vectorappend_single_option_line("support_pillar_connection_mode"); optgroup->append_single_option_line("support_buildplate_only"); optgroup->append_single_option_line("support_pillar_widening_factor"); + optgroup->append_single_option_line("support_max_weight_on_model"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); optgroup->append_single_option_line("support_base_safety_distance"); From a20cf5521df9b118351988edb2967c992f30bcd4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 2 Nov 2022 18:11:01 +0100 Subject: [PATCH 082/206] ground route merges wip on separating ground route search and actual creation wip Some fixes but still problems with pedestals --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 104 ++++++---- src/libslic3r/SLA/DefaultSupportTree.cpp | 11 +- src/libslic3r/SLA/DefaultSupportTree.hpp | 3 +- src/libslic3r/SLA/SupportTreeBuilder.hpp | 29 +-- src/libslic3r/SLA/SupportTreeUtils.hpp | 234 +++++++++++++++++++---- 5 files changed, 291 insertions(+), 90 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 62431a347..96675288d 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -32,7 +32,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // widening behaviour static constexpr double WIDENING_SCALE = 0.02; - double get_radius(const branchingtree::Node &j) + double get_radius(const branchingtree::Node &j) const { double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight; @@ -109,6 +109,44 @@ class BranchingTreeBuilder: public branchingtree::Builder { }); } + std::optional + search_for_existing_pillar(const branchingtree::Node &from) const + { + namespace bgi = boost::geometry::index; + + struct Output + { + std::optional &res; + + Output &operator*() { return *this; } + void operator=(const PointIndexEl &el) { res = el; } + Output &operator++() { return *this; } + }; + + std::optional result; + + auto filter = bgi::satisfies([this, &from](const PointIndexEl &e) { + assert(e.second < m_pillars.size()); + + auto len = (from.pos - e.first).norm(); + const branchingtree::Node &to = m_pillars[e.second]; + double sd = m_sm.cfg.safety_distance_mm; + + Beam beam{Ball{from.pos.cast(), get_radius(from)}, + Ball{e.first.cast(), get_radius(to)}}; + + return !branchingtree::is_occupied(to) && + len < m_sm.cfg.max_bridge_length_mm && + !m_cloud.is_outside_support_cone(from.pos, e.first) && + beam_mesh_hit(ex_tbb, m_sm.emesh, beam, sd).distance() > len; + }); + + m_pillar_index.query(filter && bgi::nearest(from.pos, 1), + Output{result}); + + return result; + } + public: BranchingTreeBuilder(SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -153,7 +191,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, double fromR = get_radius(from), toR = get_radius(to); Beam beam{Ball{fromd, fromR}, Ball{tod, toR}}; auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam, - 0.9 * m_sm.cfg.safety_distance_mm); + m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); @@ -201,53 +239,43 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - std::optional result; - auto filter = bgi::satisfies( - [this, &from](const PointIndexEl &e) { - auto len = (from.pos - e.first).norm(); - return !branchingtree::is_occupied(m_pillars[e.second]) - && len < m_sm.cfg.max_bridge_length_mm - && !m_cloud.is_outside_support_cone(from.pos, e.first) - && beam_mesh_hit(ex_tbb, - m_sm.emesh, - Beam{Ball{from.pos.cast(), - get_radius(from)}, - Ball{e.first.cast(), - get_radius( - m_pillars[e.second])}}, - 0.9 * m_sm.cfg.safety_distance_mm) - .distance() - > len; - }); - m_pillar_index.query(filter && bgi::nearest(from.pos, 1), Output{result}); + std::optional result = search_for_existing_pillar(from); sla::Junction j{from.pos.cast(), get_radius(from)}; if (!result) { - auto [found_conn, cjunc] = optimize_ground_connection( + auto conn = optimize_ground_connection( ex_tbb, m_builder, m_sm, j, get_radius(to)); - if (found_conn) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; - auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); + if (conn) { + build_ground_connection(m_builder, m_sm, conn); + Junction connlast = conn.path.back(); + branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; + n.left = from.id; + m_pillars.emplace_back(n); + m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + ret = true; - if (plr.second >= 0) { - m_builder.add_junction(endp, R); - if (cjunc) { - m_builder.add_diffbridge(j.pos, endp, j.r, R); - branchingtree::Node n{cjunc->pos.cast(), float(R)}; - n.left = from.id; - m_pillars.emplace_back(n); - m_pillar_index.insert({n.pos, m_pillars.size() - 1}); - } +// Vec3d endp = cjunc? cjunc->pos : j.pos; +// double R = cjunc? cjunc->r : j.r; +// Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; +// auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); - ret = true; - } +// if (plr.second >= 0) { +// m_builder.add_junction(endp, R); +// if (cjunc) { +// m_builder.add_diffbridge(j.pos, endp, j.r, R); +// branchingtree::Node n{cjunc->pos.cast(), float(R)}; +// n.left = from.id; +// m_pillars.emplace_back(n); +// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); +// } + +// ret = true; +// } } } else { const auto &resnode = m_pillars[result->second]; diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 9e21fca45..63280b9df 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -340,15 +340,13 @@ bool DefaultSupportTree::connect_to_nearpillar(const Head &head, return true; } -bool DefaultSupportTree::create_ground_pillar(const Vec3d &hjp, +bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, - double radius, long head_id) { auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, m_builder, m_sm, hjp, - sourcedir, radius, radius, - head_id); + sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, @@ -587,7 +585,7 @@ void DefaultSupportTree::routing_to_ground() Head &h = m_builder.head(hid); - if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { + if (!create_ground_pillar(h.junction(), h.dir, h.id)) { BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; m_iheads_onmodel.emplace_back(h.id); @@ -615,10 +613,9 @@ void DefaultSupportTree::routing_to_ground() if (!connect_to_nearpillar(sidehead, centerpillarID) && !search_pillar_and_connect(sidehead)) { - Vec3d pstart = sidehead.junction_point(); // Vec3d pend = Vec3d{pstart.x(), pstart.y(), gndlvl}; // Could not find a pillar, create one - create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); + create_ground_pillar(sidehead.junction(), sidehead.dir, sidehead.id); } } } diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index ff0269978..cea4b65e1 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -176,9 +176,8 @@ class DefaultSupportTree { // jp is the starting junction point which needs to be routed down. // sourcedir is the allowed direction of an optional bridge between the // jp junction and the final pillar. - bool create_ground_pillar(const Vec3d &jp, + bool create_ground_pillar(const Junction &jp, const Vec3d &sourcedir, - double radius, long head_id = SupportTreeNode::ID_UNSET); void add_pillar_base(long pid) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 87e250f65..e4567e405 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -67,6 +67,14 @@ struct SupportTreeNode long id = ID_UNSET; // For identification withing a tree. }; +// A junction connecting bridges and pillars +struct Junction: public SupportTreeNode { + double r = 1; + Vec3d pos; + + Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} +}; + // A pinhead originating from a support point struct Head: public SupportTreeNode { Vec3d dir = DOWN; @@ -77,7 +85,6 @@ struct Head: public SupportTreeNode { double width_mm = 2; double penetration_mm = 0.5; - // If there is a pillar connecting to this head, then the id will be set. long pillar_id = ID_UNSET; @@ -103,21 +110,21 @@ struct Head: public SupportTreeNode { { return real_width() - penetration_mm; } - + + inline Junction junction() const + { + Junction j{pos + (fullwidth() - r_back_mm) * dir, r_back_mm}; + j.id = -this->id; // Remember that this junction is from a head + + return j; + } + inline Vec3d junction_point() const { - return pos + (fullwidth() - r_back_mm) * dir; + return junction().pos; } }; -// A junction connecting bridges and pillars -struct Junction: public SupportTreeNode { - double r = 1; - Vec3d pos; - - Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} -}; - // A straight pillar. Only has an endpoint and a height. No explicit starting // point is given, as it would allow the pillar to be angled. // Some connection info with other primitives can also be tracked. diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index eed5e7938..e7bf9f018 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include namespace Slic3r { namespace sla { @@ -358,19 +360,19 @@ std::pair create_ground_pillar( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, - const Vec3d &pinhead_junctionpt, + const Junction &pinhead_junctionpt, const Vec3d &sourcedir, - double radius, double end_radius, long head_id = SupportTreeNode::ID_UNSET) { - Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir; + Vec3d jp = pinhead_junctionpt.pos, endp = jp, dir = sourcedir; long pillar_id = SupportTreeNode::ID_UNSET; bool can_add_base = false, non_head = false; double gndlvl = 0.; // The Z level where pedestals should be double jp_gnd = 0.; // The lowest Z where a junction center can be double gap_dist = 0.; // The gap distance between the model and the pad + double radius = pinhead_junctionpt.r; double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); @@ -634,8 +636,185 @@ std::optional calculate_pinhead_placement(Ex policy, return {}; } +struct GroundConnection { + // Currently, a ground connection will contain at most 2 additional junctions + // which will not require any allocations. If I come up with an algo that + // can produce a route to ground with more junctions, this design will be + // able to handle that. + static constexpr size_t MaxExpectedJunctions = 3; + + boost::container::small_vector path; + double end_radius; + std::optional pillar_base; + + operator bool() const { return !path.empty(); } +}; + template -std::pair> find_ground_connection( +GroundConnection find_pillar_route(Ex policy, +// SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &sourcedir, + double end_radius) +{ + GroundConnection ret; + + Vec3d jp = source.pos, endp = jp, dir = sourcedir; +// long pillar_id = SupportTreeNode::ID_UNSET; + bool can_add_base = false/*, non_head = false*/; + + double gndlvl = 0.; // The Z level where pedestals should be + double jp_gnd = 0.; // The lowest Z where a junction center can be + double gap_dist = 0.; // The gap distance between the model and the pad + double radius = source.r; + + double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); + + auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + + auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] + (bool base_en = true) + { + can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; + double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; + gndlvl = ground_level(sm); + if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; + jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); + gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; + }; + + eval_limits(); + + // We are dealing with a mini pillar that's potentially too long + if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) + { + std::optional diffbr = + search_widening_path(policy, sm, jp, dir, radius, + sm.cfg.head_back_radius_mm); + + if (diffbr && diffbr->endp.z() > jp_gnd) { +// auto &br = builder.add_diffbridge(*diffbr); +// if (head_id >= 0) +// builder.head(head_id).bridge_id = br.id; + ret.path.emplace_back(source); + endp = diffbr->endp; + radius = diffbr->end_r; +// builder.add_junction(endp, radius); + ret.path.emplace_back(endp, radius); +// non_head = true; + dir = diffbr->get_dir(); + eval_limits(); + } else return ret;//return {false, pillar_id}; + } + + if (sm.cfg.object_elevation_mm < EPSILON) + { + // get a suitable direction for the corrector bridge. It is the + // original sourcedir's azimuth but the polar angle is saturated to the + // configured bridge slope. + auto [polar, azimuth] = dir_to_spheric(dir); + polar = PI - sm.cfg.bridge_slope; + Vec3d d = spheric_to_dir(polar, azimuth).normalized(); + auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); + double tmax = std::min(sm.cfg.max_bridge_length_mm, t); + t = 0.; + + double zd = endp.z() - jp_gnd; + double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + Vec3d nexp = endp; + double dlast = 0.; + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && + t < tmax) + { + t += radius; + nexp = endp + t * d; + } + + if (dlast < gap_dist && can_add_base) { + nexp = endp; + t = 0.; + can_add_base = false; + eval_limits(can_add_base); + + zd = endp.z() - jp_gnd; + tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { + t += radius; + nexp = endp + t * d; + } + } + + // Could not find a path to avoid the pad gap + if (dlast < gap_dist) { + ret.path.clear(); + return ret; + //return {false, pillar_id}; + } + + if (t > 0.) { // Need to make additional bridge +// const Bridge& br = builder.add_bridge(endp, nexp, radius); +// if (head_id >= 0) +// builder.head(head_id).bridge_id = br.id; + +// builder.add_junction(nexp, radius); + ret.path.emplace_back(nexp, radius); + endp = nexp; +// non_head = true; + } + } + + Vec3d gp = to_floor(endp); + double h = endp.z() - gp.z(); + +// pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : +// builder.add_pillar(gp, h, radius, end_radius); + ret.end_radius = end_radius; + + if (can_add_base) { + ret.pillar_base = Pedestal{gp, h, sm.cfg.base_height_mm, sm.cfg.base_radius_mm}; +// builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, +// sm.cfg.base_radius_mm); + } + + return ret; //{true, pillar_id}; +} + +inline long build_ground_connection(SupportTreeBuilder &builder, + const SupportableMesh &sm, + const GroundConnection &conn) +{ + long ret = SupportTreeNode::ID_UNSET; + + if (!conn) + return ret; + + auto it = conn.path.begin(); + auto itnx = std::next(it); + + while (itnx != conn.path.end()) { + builder.add_diffbridge(*it, *itnx); + } + + auto gp = conn.path.back().pos; + gp.z() = ground_level(sm); + double h = conn.path.back().pos.z() - gp.z(); + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + if (conn.pillar_base) + builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + + return ret; +} + +template +GroundConnection find_ground_connection( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -662,16 +841,23 @@ std::pair> find_ground_connection( d += r; } - std::pair> ret; + GroundConnection ret; + ret.end_radius = end_r; if (std::isinf(tdown)) { - ret.first = true; + ret.path.emplace_back(j); if (d > 0) { Vec3d endp = hjp + d * dir; double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); double pill_r = r + bridge_ratio * (end_r - r); - ret.second = Junction{endp, pill_r}; +// ret.path.emplace_back(endp, pill_r); + auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); + for (auto &j : route.path) + ret.path.emplace_back(j); + + ret.pillar_base = route.pillar_base; + ret.end_radius = end_r; } } @@ -679,7 +865,7 @@ std::pair> find_ground_connection( } template -std::pair> optimize_ground_connection( +GroundConnection optimize_ground_connection( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -690,7 +876,7 @@ std::pair> optimize_ground_connection( double downdst = j.pos.z() - ground_level(sm); auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); - if (res.first) + if (!res) return res; // Optimize bridge direction: @@ -728,18 +914,9 @@ std::pair connect_to_ground(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto [found_c, cjunc] = find_ground_connection(policy, builder, sm, j, dir, end_r); - - if (found_c) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); - - if (ret.second >= 0) { - builder.add_diffbridge(j.pos, endp, j.r, R); - builder.add_junction(endp, R); - } - } + auto conn = find_ground_connection(policy, builder, sm, j, dir, end_r); + ret.first = bool(conn); + ret.second = build_ground_connection(builder, sm, conn); return ret; } @@ -754,18 +931,11 @@ std::pair search_ground_route(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto [found_c, cjunc] = optimize_ground_connection(policy, builder, sm, j, end_r, init_dir); - if (found_c) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; - ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + auto conn = optimize_ground_connection(policy, builder, sm, j, + end_r, init_dir); - if (ret.second >= 0) { - builder.add_diffbridge(j.pos, endp, j.r, R); - builder.add_junction(endp, R); - } - } + ret.first = bool(conn); + ret.second = build_ground_connection(builder, sm, conn); return ret; } From f028bfe680c905b52f8050fd9310269ff64f3eee Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 3 Nov 2022 18:16:15 +0100 Subject: [PATCH 083/206] Pillar creation restored but only in branchingtree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 36 ++++++++-------- src/libslic3r/SLA/DefaultSupportTree.cpp | 14 +++++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 52 +++++++++--------------- 3 files changed, 46 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 96675288d..fcd546ae0 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -26,7 +26,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { rtree /* ? */> m_pillar_index; - std::vector m_pillars; + std::vector m_pillars; // to put an index over them + + // cache succesfull ground connections + std::map m_gnd_connections; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -182,6 +185,13 @@ public: } bool is_valid() const override { return !m_builder.ctl().stopcondition(); } + + + void group_pillars() + { + + } + }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, @@ -251,31 +261,15 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, get_radius(to)); if (conn) { - build_ground_connection(m_builder, m_sm, conn); +// build_ground_connection(m_builder, m_sm, conn); Junction connlast = conn.path.back(); branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; n.left = from.id; m_pillars.emplace_back(n); m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + m_gnd_connections[m_pillars.size() - 1] = conn; + ret = true; - -// Vec3d endp = cjunc? cjunc->pos : j.pos; -// double R = cjunc? cjunc->r : j.r; -// Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; -// auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); - -// if (plr.second >= 0) { -// m_builder.add_junction(endp, R); -// if (cjunc) { -// m_builder.add_diffbridge(j.pos, endp, j.r, R); -// branchingtree::Node n{cjunc->pos.cast(), float(R)}; -// n.left = from.id; -// m_pillars.emplace_back(n); -// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); -// } - -// ret = true; -// } } } else { const auto &resnode = m_pillars[result->second]; @@ -381,6 +375,8 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); + vbuilder.group_pillars(); + for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 63280b9df..06477c40c 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -344,15 +344,21 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, long head_id) { - auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, - m_builder, m_sm, hjp, - sourcedir, hjp.r, head_id); + long pillar_id = SupportTreeNode::ID_UNSET; + + auto conn = sla::find_pillar_route(suptree_ex_policy, m_sm, hjp, sourcedir, hjp.r); + if (conn) + pillar_id = build_ground_connection(m_builder, m_sm, conn); + +// auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, +// m_builder, m_sm, hjp, +// sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, unsigned(pillar_id)); - return ret; + return bool(conn); } void DefaultSupportTree::add_pinheads() diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index e7bf9f018..ecc9fdbf7 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -652,7 +652,6 @@ struct GroundConnection { template GroundConnection find_pillar_route(Ex policy, -// SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &source, const Vec3d &sourcedir, @@ -661,7 +660,6 @@ GroundConnection find_pillar_route(Ex policy, GroundConnection ret; Vec3d jp = source.pos, endp = jp, dir = sourcedir; -// long pillar_id = SupportTreeNode::ID_UNSET; bool can_add_base = false/*, non_head = false*/; double gndlvl = 0.; // The Z level where pedestals should be @@ -694,18 +692,13 @@ GroundConnection find_pillar_route(Ex policy, sm.cfg.head_back_radius_mm); if (diffbr && diffbr->endp.z() > jp_gnd) { -// auto &br = builder.add_diffbridge(*diffbr); -// if (head_id >= 0) -// builder.head(head_id).bridge_id = br.id; ret.path.emplace_back(source); endp = diffbr->endp; radius = diffbr->end_r; -// builder.add_junction(endp, radius); ret.path.emplace_back(endp, radius); -// non_head = true; dir = diffbr->get_dir(); eval_limits(); - } else return ret;//return {false, pillar_id}; + } else return ret; } if (sm.cfg.object_elevation_mm < EPSILON) @@ -760,28 +753,18 @@ GroundConnection find_pillar_route(Ex policy, } if (t > 0.) { // Need to make additional bridge -// const Bridge& br = builder.add_bridge(endp, nexp, radius); -// if (head_id >= 0) -// builder.head(head_id).bridge_id = br.id; - -// builder.add_junction(nexp, radius); ret.path.emplace_back(nexp, radius); endp = nexp; -// non_head = true; } } Vec3d gp = to_floor(endp); - double h = endp.z() - gp.z(); -// pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : -// builder.add_pillar(gp, h, radius, end_radius); ret.end_radius = end_radius; if (can_add_base) { - ret.pillar_base = Pedestal{gp, h, sm.cfg.base_height_mm, sm.cfg.base_radius_mm}; -// builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, -// sm.cfg.base_radius_mm); + ret.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, sm.cfg.base_radius_mm, end_radius}; } return ret; //{true, pillar_id}; @@ -806,7 +789,15 @@ inline long build_ground_connection(SupportTreeBuilder &builder, auto gp = conn.path.back().pos; gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); - ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + +// TODO: does not work yet +// if (conn.path.back().id < 0) { +// // this is a head +// long head_id = std::abs(conn.path.back().id); +// ret = builder.add_pillar(head_id, h); +// } else + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + if (conn.pillar_base) builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); @@ -846,19 +837,16 @@ GroundConnection find_ground_connection( if (std::isinf(tdown)) { ret.path.emplace_back(j); - if (d > 0) { - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); - double pill_r = r + bridge_ratio * (end_r - r); + Vec3d endp = hjp + d * dir; + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); -// ret.path.emplace_back(endp, pill_r); - auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - for (auto &j : route.path) - ret.path.emplace_back(j); + auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); + for (auto &j : route.path) + ret.path.emplace_back(j); - ret.pillar_base = route.pillar_base; - ret.end_radius = end_r; - } + ret.pillar_base = route.pillar_base; + ret.end_radius = end_r; } return ret; From 834f428ba01a6f8721ed7803430a866b3ec77e84 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Nov 2022 16:22:23 +0100 Subject: [PATCH 084/206] WIP on grouping ground pillars for branching tree supports --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 28 +++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index fcd546ae0..ce440b227 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -186,12 +186,18 @@ public: bool is_valid() const override { return !m_builder.ctl().stopcondition(); } + const std::vector & pillars() const { return m_pillars; } - void group_pillars() + const GroundConnection *ground_conn(size_t pillar) const { + const GroundConnection *ret = nullptr; + auto it = m_gnd_connections.find(pillar); + if (it != m_gnd_connections.end()) + ret = &it->second; + + return ret; } - }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, @@ -375,7 +381,23 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - vbuilder.group_pillars(); + std::vector bedleafs; + for (auto n : vbuilder.pillars()) { + n.left = branchingtree::Node::ID_NONE; + n.right = branchingtree::Node::ID_NONE; + bedleafs.emplace_back(n); + } + + props.max_branch_length(20.f); + branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; + BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; + branchingtree::build_tree(gndnodes, gndbuilder); + + for (size_t pill_id = 0; pill_id < gndbuilder.pillars().size(); ++pill_id) { + auto * conn = gndbuilder.ground_conn(pill_id); + if (conn) + build_ground_connection(builder, sm, *conn); + } for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); From e62873ff1341544083d5727ae0235f2ddb8085d2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 08:29:42 +0100 Subject: [PATCH 085/206] Prevent uninitialized value in nlopt optimizer --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index f5d314046..cc7edb97a 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -68,7 +68,7 @@ template class NLoptOpt {}; template class NLoptOpt> { protected: StopCriteria m_stopcr; - OptDir m_dir; + OptDir m_dir = OptDir::MIN; template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; From b4b5e8eb8e6f0ef939b4e7b600ec123f74bdbce9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 08:30:02 +0100 Subject: [PATCH 086/206] WIP on pillar grouping for sla branching supports --- src/libslic3r/BranchingTree/PointCloud.hpp | 5 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 54 ++++++++++++---------- src/libslic3r/SLA/SupportTree.hpp | 4 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index cbd01a465..03b935f76 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -286,6 +286,11 @@ template void traverse(PC &&pc, size_t root, Fn &&fn) void build_tree(PointCloud &pcloud, Builder &builder); +inline void build_tree(PointCloud &&pc, Builder &builder) +{ + build_tree(pc, builder); +} + }} // namespace Slic3r::branchingtree #endif // POINTCLOUD_HPP diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index ce440b227..a4605bbfd 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -122,7 +122,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::optional &res; Output &operator*() { return *this; } - void operator=(const PointIndexEl &el) { res = el; } + Output &operator=(const PointIndexEl &el) { res = el; return *this; } Output &operator++() { return *this; } }; @@ -245,20 +245,12 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; - struct Output { - std::optional &res; - - Output& operator *() { return *this; } - void operator=(const PointIndexEl &el) { res = el; } - Output& operator++() { return *this; } - }; - auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - std::optional result = search_for_existing_pillar(from); +// std::optional result = search_for_existing_pillar(from); sla::Junction j{from.pos.cast(), get_radius(from)}; - if (!result) { +// if (!result) { auto conn = optimize_ground_connection( ex_tbb, m_builder, @@ -268,21 +260,21 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, if (conn) { // build_ground_connection(m_builder, m_sm, conn); - Junction connlast = conn.path.back(); - branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; - n.left = from.id; - m_pillars.emplace_back(n); - m_pillar_index.insert({n.pos, m_pillars.size() - 1}); +// Junction connlast = conn.path.back(); +// branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; +// n.left = from.id; + m_pillars.emplace_back(from); +// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); m_gnd_connections[m_pillars.size() - 1] = conn; ret = true; } - } else { - const auto &resnode = m_pillars[result->second]; - m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); - m_pillars[result->second].right = from.id; - ret = true; - } +// } else { +// const auto &resnode = m_pillars[result->second]; +// m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); +// m_pillars[result->second].right = from.id; +// ret = true; +// } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because @@ -353,7 +345,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s for (auto &h : heads) if (h && h->is_valid()) { leafs.emplace_back(h->junction_point().cast(), h->r_back_mm); - h->id = leafs.size() - 1; + h->id = long(leafs.size() - 1); builder.add_head(h->id, *h); } @@ -372,7 +364,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s props.sampling_radius()); auto bedpts = branchingtree::sample_bed(props.bed_shape(), - props.ground_level(), + float(props.ground_level()), props.sampling_radius()); branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts), @@ -388,11 +380,23 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s bedleafs.emplace_back(n); } - props.max_branch_length(20.f); + props.max_branch_length(50.f); + auto gndsm = sm; branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; branchingtree::build_tree(gndnodes, gndbuilder); + // All leafs of gndbuilder are nodes that already proved to be routable + // to the ground. gndbuilder should not encounter any unroutable nodes +// assert(gndbuilder.unroutable_pinheads().empty()); + + +// for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) { +// auto * conn = vbuilder.ground_conn(pill_id); +// if (conn) +// build_ground_connection(builder, sm, *conn); +// } + for (size_t pill_id = 0; pill_id < gndbuilder.pillars().size(); ++pill_id) { auto * conn = gndbuilder.ground_conn(pill_id); if (conn) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 051d56926..b70f77319 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -96,8 +96,8 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; - static const double constexpr optimizer_rel_score_diff = 1e-10; - static const unsigned constexpr optimizer_max_iterations = 2000; + static const double constexpr optimizer_rel_score_diff = 1e-16; + static const unsigned constexpr optimizer_max_iterations = 20000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index ecc9fdbf7..834ade358 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -875,7 +875,7 @@ GroundConnection optimize_ground_connection( Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/; auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; From 823d28ec4b415c447367fdf3011f5d4f08972bbd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 10:03:55 +0100 Subject: [PATCH 087/206] Fix infinite loop in build_ground_connection --- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 834ade358..ef132c969 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -784,6 +784,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, while (itnx != conn.path.end()) { builder.add_diffbridge(*it, *itnx); + builder.add_junction(*itnx); + ++it; ++itnx; } auto gp = conn.path.back().pos; From 4dc0741766d1adf526b87fb8e8d87d928cc81c67 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 10:07:33 +0100 Subject: [PATCH 088/206] set strict safety distance Don't change with pillar radius --- src/libslic3r/SLA/SupportTreeUtils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index ef132c969..1cf9dfdc5 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -709,7 +709,7 @@ GroundConnection find_pillar_route(Ex policy, auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - sm.cfg.bridge_slope; Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance_mm; //radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); double tmax = std::min(sm.cfg.max_bridge_length_mm, t); t = 0.; @@ -817,7 +817,7 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance_mm; //r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd) From 1e9bd28714d936277cb925fddcaa3d835bb2ab85 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 15:12:53 +0100 Subject: [PATCH 089/206] Upgrade support tree route search functions, add tests --- src/libslic3r/BranchingTree/BranchingTree.hpp | 3 + src/libslic3r/CMakeLists.txt | 2 + .../Optimize/BruteforceOptimizer.hpp | 4 +- src/libslic3r/Optimize/NLoptOptimizer.hpp | 4 +- src/libslic3r/OrganicTree/OrganicTree.hpp | 95 ++++++++++++++ src/libslic3r/OrganicTree/OrganicTreeImpl.hpp | 11 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 51 ++++---- src/libslic3r/SLA/SupportTree.hpp | 4 +- src/libslic3r/SLA/SupportTreeMesher.cpp | 3 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 84 ++++++------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 + tests/data/U_overhang.obj | 76 +++++++++++ tests/sla_print/CMakeLists.txt | 1 + tests/sla_print/sla_supptreeutils_tests.cpp | 119 ++++++++++++++++++ 14 files changed, 385 insertions(+), 76 deletions(-) create mode 100644 src/libslic3r/OrganicTree/OrganicTree.hpp create mode 100644 src/libslic3r/OrganicTree/OrganicTreeImpl.hpp create mode 100644 tests/data/U_overhang.obj create mode 100644 tests/sla_print/sla_supptreeutils_tests.cpp diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index fd69fe4cd..3f7fd7b80 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -20,6 +20,9 @@ class Properties ExPolygons m_bed_shape; public: + + constexpr bool group_pillars() const noexcept { return false; } + // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6217a11df..3740d4e01 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -356,6 +356,8 @@ set(SLIC3R_SOURCES BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp BranchingTree/PointCloud.hpp + OrganicTree/OrganicTree.hpp + OrganicTree/OrganicTreeImpl.hpp Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp diff --git a/src/libslic3r/Optimize/BruteforceOptimizer.hpp b/src/libslic3r/Optimize/BruteforceOptimizer.hpp index 2daef538e..2f6b42224 100644 --- a/src/libslic3r/Optimize/BruteforceOptimizer.hpp +++ b/src/libslic3r/Optimize/BruteforceOptimizer.hpp @@ -13,7 +13,9 @@ template long num_iter(const std::array &idx, size_t gridsz) { long ret = 0; - for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i); + for (size_t i = 0; i < N; ++i) + ret += idx[i] * std::pow(gridsz, i); + return ret; } diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index cc7edb97a..3859217da 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -158,7 +158,7 @@ public: return optimize(nl, std::forward(func), initvals); } - explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} + explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } const StopCriteria &get_criteria() const noexcept { return m_stopcr; } @@ -226,7 +226,7 @@ using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlg; +using AlgNLoptMLSL = detail::NLoptAlgComb; }} // namespace Slic3r::opt diff --git a/src/libslic3r/OrganicTree/OrganicTree.hpp b/src/libslic3r/OrganicTree/OrganicTree.hpp new file mode 100644 index 000000000..cf34c9eb3 --- /dev/null +++ b/src/libslic3r/OrganicTree/OrganicTree.hpp @@ -0,0 +1,95 @@ +#ifndef ORGANICTREE_HPP +#define ORGANICTREE_HPP + +#include +#include +#include + +namespace Slic3r { namespace organictree { + +enum class NodeType { Bed, Mesh, Junction }; + +template struct DomainTraits_ { + using Node = typename T::Node; + + static void push(const T &dom, const Node &n) + { + dom.push_junction(n); + } + + static Node pop(T &dom) { return dom.pop(); } + + static bool empty(const T &dom) { return dom.empty(); } + + static std::optional> + closest(const T &dom, const Node &n) + { + return dom.closest(n); + } + + static Node merge_node(const T &dom, const Node &a, const Node &b) + { + return dom.merge_node(a, b); + } + + static void bridge(T &dom, const Node &from, const Node &to) + { + dom.bridge(from, to); + } + + static void anchor(T &dom, const Node &from, const Node &to) + { + dom.anchor(from, to); + } + + static void pillar(T &dom, const Node &from, const Node &to) + { + dom.pillar(from, to); + } + + static void merge (T &dom, const Node &n1, const Node &n2, const Node &mrg) + { + dom.merge(n1, n2, mrg); + } + + static void report_fail(T &dom, const Node &n) { dom.report_fail(n); } +}; + +template +void build_tree(Domain &&D) +{ + using Dom = DomainTraits_>>; + using Node = typename Dom::Node; + + while (! Dom::empty(D)) { + Node n = Dom::pop(D); + + std::optional> C = Dom::closest(D, n); + + if (!C) { + Dom::report_fail(D, n); + } else switch (C->second) { + case NodeType::Bed: + Dom::pillar(D, n, C->first); + break; + case NodeType::Mesh: + Dom::anchor(D, n, C->first); + break; + case NodeType::Junction: { + Node M = Dom::merge_node(D, n, C->first); + + if (M == C->first) { + Dom::bridge(D, n, C->first); + } else { + Dom::push(D, M); + Dom::merge(D, n, M, C->first); + } + break; + } + } + } +} + +}} // namespace Slic3r::organictree + +#endif // ORGANICTREE_HPP diff --git a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp new file mode 100644 index 000000000..6e18550da --- /dev/null +++ b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp @@ -0,0 +1,11 @@ +#ifndef ORGANICTREEIMPL_HPP +#define ORGANICTREEIMPL_HPP + +namespace Slic3r { namespace organictree { + + + + +}} + +#endif // ORGANICTREEIMPL_HPP diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index a4605bbfd..fc5dc2982 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -253,13 +253,11 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, // if (!result) { auto conn = optimize_ground_connection( ex_tbb, - m_builder, m_sm, j, get_radius(to)); if (conn) { -// build_ground_connection(m_builder, m_sm, conn); // Junction connlast = conn.path.back(); // branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; // n.left = from.id; @@ -321,6 +319,17 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, return bool(anchor); } +inline void build_pillars(SupportTreeBuilder &builder, + BranchingTreeBuilder &vbuilder, + const SupportableMesh &sm) +{ + for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) { + auto * conn = vbuilder.ground_conn(pill_id); + if (conn) + build_ground_connection(builder, sm, *conn); + } +} + void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &sm) { auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); }; @@ -373,34 +382,22 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - std::vector bedleafs; - for (auto n : vbuilder.pillars()) { - n.left = branchingtree::Node::ID_NONE; - n.right = branchingtree::Node::ID_NONE; - bedleafs.emplace_back(n); - } + if constexpr (props.group_pillars()) { - props.max_branch_length(50.f); - auto gndsm = sm; - branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; - BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; - branchingtree::build_tree(gndnodes, gndbuilder); + std::vector bedleafs; + for (auto n : vbuilder.pillars()) { + n.left = branchingtree::Node::ID_NONE; + n.right = branchingtree::Node::ID_NONE; + bedleafs.emplace_back(n); + } - // All leafs of gndbuilder are nodes that already proved to be routable - // to the ground. gndbuilder should not encounter any unroutable nodes -// assert(gndbuilder.unroutable_pinheads().empty()); + branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; + BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; + branchingtree::build_tree(gndnodes, gndbuilder); - -// for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) { -// auto * conn = vbuilder.ground_conn(pill_id); -// if (conn) -// build_ground_connection(builder, sm, *conn); -// } - - for (size_t pill_id = 0; pill_id < gndbuilder.pillars().size(); ++pill_id) { - auto * conn = gndbuilder.ground_conn(pill_id); - if (conn) - build_ground_connection(builder, sm, *conn); + build_pillars(builder, gndbuilder, sm); + } else { + build_pillars(builder, vbuilder, sm); } for (size_t id : vbuilder.unroutable_pinheads()) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index b70f77319..051d56926 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -96,8 +96,8 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; - static const double constexpr optimizer_rel_score_diff = 1e-16; - static const unsigned constexpr optimizer_max_iterations = 20000; + static const double constexpr optimizer_rel_score_diff = 1e-10; + static const unsigned constexpr optimizer_max_iterations = 2000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp index 3f0b6c841..6d91de7e6 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.cpp +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -165,7 +165,8 @@ indexed_triangle_set halfcone(double baseheight, { assert(steps > 0); - if (baseheight <= 0 || steps <= 0) return {}; + if (baseheight <= 0 || steps <= 0 || (r_bottom <= 0. && r_top <= 0.)) + return {}; indexed_triangle_set base; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 1cf9dfdc5..070ffbf0d 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -178,7 +179,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) [&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) { Hit &hit = hits[i]; - // Point on the circle on the pin sphere + // Point on the circle on the pin sphere Vec3d p_src = ring.get(i, src, r_src + sd); Vec3d p_dst = ring.get(i, dst, r_dst + sd); Vec3d raydir = (p_dst - p_src).normalized(); @@ -644,10 +645,9 @@ struct GroundConnection { static constexpr size_t MaxExpectedJunctions = 3; boost::container::small_vector path; - double end_radius; std::optional pillar_base; - operator bool() const { return !path.empty(); } + operator bool() const { return pillar_base.has_value() && !path.empty(); } }; template @@ -659,6 +659,8 @@ GroundConnection find_pillar_route(Ex policy, { GroundConnection ret; + ret.path.emplace_back(source); + Vec3d jp = source.pos, endp = jp, dir = sourcedir; bool can_add_base = false/*, non_head = false*/; @@ -666,6 +668,7 @@ GroundConnection find_pillar_route(Ex policy, double jp_gnd = 0.; // The lowest Z where a junction center can be double gap_dist = 0.; // The gap distance between the model and the pad double radius = source.r; + double sd = sm.cfg.safety_distance_mm; double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); @@ -692,7 +695,6 @@ GroundConnection find_pillar_route(Ex policy, sm.cfg.head_back_radius_mm); if (diffbr && diffbr->endp.z() > jp_gnd) { - ret.path.emplace_back(source); endp = diffbr->endp; radius = diffbr->end_r; ret.path.emplace_back(endp, radius); @@ -709,7 +711,6 @@ GroundConnection find_pillar_route(Ex policy, auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - sm.cfg.bridge_slope; Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - auto sd = sm.cfg.safety_distance_mm; //radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); double tmax = std::min(sm.cfg.max_bridge_length_mm, t); t = 0.; @@ -759,15 +760,19 @@ GroundConnection find_pillar_route(Ex policy, } Vec3d gp = to_floor(endp); + auto hit = beam_mesh_hit(policy, sm.emesh, + Beam{{endp, radius}, {gp, end_radius}}, sd); - ret.end_radius = end_radius; + if (std::isinf(hit.distance())) { + double base_radius = can_add_base ? + std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; - if (can_add_base) { + Vec3d gp = to_floor(endp); ret.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, sm.cfg.base_radius_mm, end_radius}; + Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; } - return ret; //{true, pillar_id}; + return ret; } inline long build_ground_connection(SupportTreeBuilder &builder, @@ -798,10 +803,9 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // long head_id = std::abs(conn.path.back().id); // ret = builder.add_pillar(head_id, h); // } else - ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); - if (conn.pillar_base) - builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); return ret; } @@ -809,7 +813,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder, template GroundConnection find_ground_connection( Ex policy, - SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &j, const Vec3d &dir, @@ -817,39 +820,36 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = sm.cfg.safety_distance_mm; //r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance_mm; double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); - double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd) - .distance(); - double d = 0, tdown = 0; - t = std::min(t, - sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); + double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); + t = std::min(t, sm.cfg.max_bridge_length_mm); + double d = 0.; + + GroundConnection gnd_route; + + while (!gnd_route && d < t) { + Vec3d endp = hjp + d * dir; + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); + gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - while ( - d < t && - !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, - Beam{hjp + d * dir, DOWN, r, r2}, sd) - .distance())) { d += r; } GroundConnection ret; - ret.end_radius = end_r; - if (std::isinf(tdown)) { + if (d > 0.) ret.path.emplace_back(j); - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); - double pill_r = r + bridge_ratio * (end_r - r); - auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - for (auto &j : route.path) - ret.path.emplace_back(j); + for (auto &p : gnd_route.path) + ret.path.emplace_back(p); - ret.pillar_base = route.pillar_base; - ret.end_radius = end_r; - } + // This will ultimately determine if the route is valid or not + // but the path junctions will be provided anyways, so invalid paths + // can be debugged + ret.pillar_base = gnd_route.pillar_base; return ret; } @@ -857,7 +857,6 @@ GroundConnection find_ground_connection( template GroundConnection optimize_ground_connection( Ex policy, - SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &j, double end_radius, @@ -865,8 +864,8 @@ GroundConnection optimize_ground_connection( { double downdst = j.pos.z() - ground_level(sm); - auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); - if (!res) + auto res = find_ground_connection(policy, sm, j, init_dir, end_radius); + if (res) return res; // Optimize bridge direction: @@ -874,7 +873,7 @@ GroundConnection optimize_ground_connection( // direction out of the cavity. auto [polar, azimuth] = dir_to_spheric(init_dir); - Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); + Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/; @@ -891,7 +890,7 @@ GroundConnection optimize_ground_connection( Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); - return find_ground_connection(policy, builder, sm, j, bridgedir, end_radius); + return find_ground_connection(policy, sm, j, bridgedir, end_radius); } template @@ -904,7 +903,7 @@ std::pair connect_to_ground(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto conn = find_ground_connection(policy, builder, sm, j, dir, end_r); + auto conn = find_ground_connection(policy, sm, j, dir, end_r); ret.first = bool(conn); ret.second = build_ground_connection(builder, sm, conn); @@ -921,8 +920,7 @@ std::pair search_ground_route(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto conn = optimize_ground_connection(policy, builder, sm, j, - end_r, init_dir); + auto conn = optimize_ground_connection(policy, sm, j, end_r, init_dir); ret.first = bool(conn); ret.second = build_ground_connection(builder, sm, conn); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index c22cdf606..e0f3acb70 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" @@ -1019,6 +1021,8 @@ void GLGizmoSlaSupports::select_point(int i) m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; } else { + if (!m_editing_cache[i].selected) + BOOST_LOG_TRIVIAL(debug) << "Support point selected [" << i << "]: " << m_editing_cache[i].support_point.pos.transpose() << " \tnormal: " << m_editing_cache[i].normal.transpose(); m_editing_cache[i].selected = true; m_selection_empty = false; m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; diff --git a/tests/data/U_overhang.obj b/tests/data/U_overhang.obj new file mode 100644 index 000000000..70453e58f --- /dev/null +++ b/tests/data/U_overhang.obj @@ -0,0 +1,76 @@ +#### +# +# OBJ File Generated by Meshlab +# +#### +# Object U_overhang.obj +# +# Vertices: 16 +# Faces: 28 +# +#### +vn 1.570797 1.570796 1.570796 +v 10.000000 10.000000 11.000000 +vn 4.712389 1.570796 -1.570796 +v 10.000000 1.000000 10.000000 +vn 1.570796 1.570796 -1.570796 +v 10.000000 10.000000 10.000000 +vn 1.570796 -1.570796 1.570796 +v 10.000000 0.000000 11.000000 +vn 4.712389 1.570796 1.570796 +v 10.000000 1.000000 1.000000 +vn 1.570797 1.570796 -1.570796 +v 10.000000 10.000000 0.000000 +vn 1.570796 1.570796 1.570796 +v 10.000000 10.000000 1.000000 +vn 1.570796 -1.570796 -1.570796 +v 10.000000 0.000000 0.000000 +vn -1.570796 1.570796 1.570796 +v 0.000000 10.000000 1.000000 +vn -4.712389 1.570796 1.570796 +v 0.000000 1.000000 1.000000 +vn -1.570796 -1.570796 -1.570796 +v 0.000000 0.000000 0.000000 +vn -1.570797 1.570796 -1.570796 +v 0.000000 10.000000 0.000000 +vn -4.712389 1.570796 -1.570796 +v 0.000000 1.000000 10.000000 +vn -1.570797 1.570796 1.570796 +v 0.000000 10.000000 11.000000 +vn -1.570796 1.570796 -1.570796 +v 0.000000 10.000000 10.000000 +vn -1.570796 -1.570796 1.570796 +v 0.000000 0.000000 11.000000 +# 16 vertices, 0 vertices normals + +f 1//1 2//2 3//3 +f 2//2 4//4 5//5 +f 4//4 2//2 1//1 +f 5//5 6//6 7//7 +f 5//5 8//8 6//6 +f 8//8 5//5 4//4 +f 9//9 5//5 7//7 +f 5//5 9//9 10//10 +f 11//11 6//6 8//8 +f 6//6 11//11 12//12 +f 12//12 10//10 9//9 +f 10//10 11//11 13//13 +f 11//11 10//10 12//12 +f 13//13 14//14 15//15 +f 13//13 16//16 14//14 +f 16//16 13//13 11//11 +f 6//6 9//9 7//7 +f 9//9 6//6 12//12 +f 11//11 4//4 16//16 +f 4//4 11//11 8//8 +f 13//13 3//3 2//2 +f 3//3 13//13 15//15 +f 5//5 13//13 2//2 +f 13//13 5//5 10//10 +f 14//14 4//4 1//1 +f 4//4 14//14 16//16 +f 3//3 14//14 1//1 +f 14//14 3//3 15//15 +# 28 faces, 0 coords texture + +# End of File diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 24e9552c5..2a800cc50 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_test_utils.hpp sla_test_utils.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp + sla_supptreeutils_tests.cpp sla_archive_readwrite_tests.cpp) # mold linker for successful linking needs also to link TBB library and link it before libslic3r. diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp new file mode 100644 index 000000000..b433e80ee --- /dev/null +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "libslic3r/Execution/ExecutionSeq.hpp" +#include "libslic3r/SLA/SupportTreeUtils.hpp" + +TEST_CASE("Avoid disk below junction", "[suptreeutils]") +{ + // In this test there will be a disk mesh with some radius, centered at + // (0, 0, 0) and above the disk, a junction from which the support pillar + // should be routed. The algorithm needs to find an avoidance route. + + using namespace Slic3r; + + constexpr double FromRadius = .5; + constexpr double EndRadius = 1.; + constexpr double CylRadius = 4.; + constexpr double CylHeight = 1.; + + sla::SupportTreeConfig cfg; + + indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); + + // 2.5 * CyRadius height should be enough to be able to insert a bridge + // with 45 degree tilt above the disk. + sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius}; + + sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + its_merge(disk, builder.merged_mesh()); + + its_write_stl_ascii("output_disk.stl", "disk", disk); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // The end radius end the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); + + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); +} + +TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]") +{ + // In this test there will be a disk mesh with some radius, centered at + // (0, 0, 0) and above the disk, a junction from which the support pillar + // should be routed. The algorithm needs to find an avoidance route. + + using namespace Slic3r; + + constexpr double FromRadius = .5; + constexpr double EndRadius = 1.; + constexpr double CylRadius = 4.; + constexpr double CylHeight = 1.; + constexpr double JElevX = 2.5; + + sla::SupportTreeConfig cfg; + + indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); + indexed_triangle_set wall = its_make_cube(1., 2 * CylRadius, JElevX * CylRadius); + its_translate(wall, Vec3f{float(FromRadius), -float(CylRadius), 0.f}); + its_merge(disk, wall); + + // 2.5 * CyRadius height should be enough to be able to insert a bridge + // with 45 degree tilt above the disk. + sla::Junction j{Vec3d{0., 0., JElevX * CylRadius}, FromRadius}; + + sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + its_merge(disk, builder.merged_mesh()); + + its_write_stl_ascii("output_disk_wall.stl", "disk_wall", disk); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // The end radius end the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); + + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); +} From d3a2f11e2929c7726fb8cf0745da6febfd528060 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 16:53:56 +0100 Subject: [PATCH 090/206] Use old pillar creation functions for default support tree --- src/libslic3r/SLA/DefaultSupportTree.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 06477c40c..63280b9df 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -344,21 +344,15 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, long head_id) { - long pillar_id = SupportTreeNode::ID_UNSET; - - auto conn = sla::find_pillar_route(suptree_ex_policy, m_sm, hjp, sourcedir, hjp.r); - if (conn) - pillar_id = build_ground_connection(m_builder, m_sm, conn); - -// auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, -// m_builder, m_sm, hjp, -// sourcedir, hjp.r, head_id); + auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, + m_builder, m_sm, hjp, + sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, unsigned(pillar_id)); - return bool(conn); + return ret; } void DefaultSupportTree::add_pinheads() From 5f63b4496d7c3da51005fbb13e647c5d0e5e4c7f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 16:54:53 +0100 Subject: [PATCH 091/206] WIP on pillar grouping --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/BranchingTree/PointCloud.cpp | 5 ++++- src/libslic3r/SLA/BranchingTreeSLA.cpp | 10 +++++----- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 3f7fd7b80..d30499c11 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,7 +21,7 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return false; } + constexpr bool group_pillars() const noexcept { return true; } // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 5f07d91c7..319f334ff 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -197,7 +197,10 @@ PointCloud::PointCloud(std::vector meshpts, for (size_t i = 0; i < m_leafs.size(); ++i) { Node &n = m_leafs[i]; - n.id = int(LEAFS_BEGIN + i); + n.id = int(LEAFS_BEGIN + i); + n.left = Node::ID_NONE; + n.right = Node::ID_NONE; + m_ktree.insert({n.pos, n.id}); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index fc5dc2982..2edc0fe7b 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -382,20 +382,20 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); + std::cout << "Original pillar count: " << vbuilder.pillars().size() << std::endl; + if constexpr (props.group_pillars()) { std::vector bedleafs; - for (auto n : vbuilder.pillars()) { - n.left = branchingtree::Node::ID_NONE; - n.right = branchingtree::Node::ID_NONE; - bedleafs.emplace_back(n); - } + std::copy(vbuilder.pillars().begin(), vbuilder.pillars().end(), std::back_inserter(bedleafs)); branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; branchingtree::build_tree(gndnodes, gndbuilder); + std::cout << "Grouped pillar count: " << gndbuilder.pillars().size() << std::endl; build_pillars(builder, gndbuilder, sm); + } else { build_pillars(builder, vbuilder, sm); } diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 070ffbf0d..243152189 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -629,7 +629,7 @@ std::optional calculate_pinhead_placement(Ex policy, }; if (optimize_pinhead_placement(policy, sm, head)) { - head.id = suppt_idx; + head.id = long(suppt_idx); return head; } From 963e8e6585ae79c4a927ef8f41d5dca5affa6cad Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 14 Nov 2022 12:37:34 +0100 Subject: [PATCH 092/206] Revert util functions of DefaultSupportTree to original To not break DefautlSupportTree --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/SLA/DefaultSupportTree.cpp | 9 +- src/libslic3r/SLA/DefaultSupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 190 +--------------- src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp | 220 +++++++++++++++++++ tests/sla_print/sla_supptreeutils_tests.cpp | 10 +- 7 files changed, 251 insertions(+), 183 deletions(-) create mode 100644 src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3740d4e01..232285cee 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -323,6 +323,7 @@ set(SLIC3R_SOURCES SLA/SupportTreeMesher.hpp SLA/SupportTreeMesher.cpp SLA/SupportTreeUtils.hpp + SLA/SupportTreeUtilsLegacy.hpp SLA/SupportTreeBuilder.cpp SLA/SupportTree.hpp SLA/SupportTree.cpp diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 63280b9df..53475542a 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -345,8 +345,13 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, long head_id) { auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, - m_builder, m_sm, hjp, - sourcedir, hjp.r, head_id); + m_builder, + m_sm, + hjp.pos, + sourcedir, + hjp.r, + hjp.r, + head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index cea4b65e1..ee58e9ded 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -1,7 +1,7 @@ #ifndef LEGACYSUPPORTTREE_HPP #define LEGACYSUPPORTTREE_HPP -#include "SupportTreeUtils.hpp" +#include "SupportTreeUtilsLegacy.hpp" #include #include diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 051d56926..b7aaf8aee 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -50,7 +50,7 @@ struct SupportTreeConfig // when bridges and pillars are merged. The resulting pillar should be a bit // thicker than the ones merging into it. How much thicker? I don't know // but it will be derived from this value. - double pillar_widening_factor = .05; + double pillar_widening_factor = .5; // Radius in mm of the pillar base. double base_radius_mm = 2.0; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 243152189..3ee72436a 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -351,136 +351,6 @@ std::optional search_widening_path(Ex policy, return {}; } -// This is a proxy function for pillar creation which will mind the gap -// between the pad and the model bottom in zero elevation mode. -// 'pinhead_junctionpt' is the starting junction point which needs to be -// routed down. sourcedir is the allowed direction of an optional bridge -// between the jp junction and the final pillar. -template -std::pair create_ground_pillar( - Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &pinhead_junctionpt, - const Vec3d &sourcedir, - double end_radius, - long head_id = SupportTreeNode::ID_UNSET) -{ - Vec3d jp = pinhead_junctionpt.pos, endp = jp, dir = sourcedir; - long pillar_id = SupportTreeNode::ID_UNSET; - bool can_add_base = false, non_head = false; - - double gndlvl = 0.; // The Z level where pedestals should be - double jp_gnd = 0.; // The lowest Z where a junction center can be - double gap_dist = 0.; // The gap distance between the model and the pad - double radius = pinhead_junctionpt.r; - - double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); - - auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; - - auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] - (bool base_en = true) - { - can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; - double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; - gndlvl = ground_level(sm); - if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; - jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); - gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; - }; - - eval_limits(); - - // We are dealing with a mini pillar that's potentially too long - if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) - { - std::optional diffbr = - search_widening_path(policy, sm, jp, dir, radius, - sm.cfg.head_back_radius_mm); - - if (diffbr && diffbr->endp.z() > jp_gnd) { - auto &br = builder.add_diffbridge(*diffbr); - if (head_id >= 0) builder.head(head_id).bridge_id = br.id; - endp = diffbr->endp; - radius = diffbr->end_r; - builder.add_junction(endp, radius); - non_head = true; - dir = diffbr->get_dir(); - eval_limits(); - } else return {false, pillar_id}; - } - - if (sm.cfg.object_elevation_mm < EPSILON) - { - // get a suitable direction for the corrector bridge. It is the - // original sourcedir's azimuth but the polar angle is saturated to the - // configured bridge slope. - auto [polar, azimuth] = dir_to_spheric(dir); - polar = PI - sm.cfg.bridge_slope; - Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; - double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); - double tmax = std::min(sm.cfg.max_bridge_length_mm, t); - t = 0.; - - double zd = endp.z() - jp_gnd; - double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - Vec3d nexp = endp; - double dlast = 0.; - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && - t < tmax) - { - t += radius; - nexp = endp + t * d; - } - - if (dlast < gap_dist && can_add_base) { - nexp = endp; - t = 0.; - can_add_base = false; - eval_limits(can_add_base); - - zd = endp.z() - jp_gnd; - tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { - t += radius; - nexp = endp + t * d; - } - } - - // Could not find a path to avoid the pad gap - if (dlast < gap_dist) return {false, pillar_id}; - - if (t > 0.) { // Need to make additional bridge - const Bridge& br = builder.add_bridge(endp, nexp, radius); - if (head_id >= 0) builder.head(head_id).bridge_id = br.id; - - builder.add_junction(nexp, radius); - endp = nexp; - non_head = true; - } - } - - Vec3d gp = to_floor(endp); - double h = endp.z() - gp.z(); - - pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : - builder.add_pillar(gp, h, radius, end_radius); - - if (can_add_base) - builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, - sm.cfg.base_radius_mm); - - return {true, pillar_id}; -} - inline double distance(const SupportPoint &a, const SupportPoint &b) { return (a.pos - b.pos).norm(); @@ -533,13 +403,13 @@ bool optimize_pinhead_placement(Ex policy, double back_r = head.r_back_mm; - // skip if the tilt is not sane + // skip if the tilt is not sane if (polar < PI - m.cfg.normal_cutoff_angle) return false; - // We saturate the polar angle to 3pi/4 + // We saturate the polar angle to 3pi/4 polar = std::max(polar, PI - m.cfg.bridge_slope); - // save the head (pinpoint) position + // save the head (pinpoint) position Vec3d hp = head.pos; double lmin = m.cfg.head_width_mm, lmax = lmin; @@ -548,19 +418,18 @@ bool optimize_pinhead_placement(Ex policy, lmin = 0., lmax = m.cfg.head_penetration_mm; } - // The distance needed for a pinhead to not collide with model. + // The distance needed for a pinhead to not collide with model. double w = lmin + 2 * back_r + 2 * m.cfg.head_front_radius_mm - m.cfg.head_penetration_mm; double pin_r = head.r_pin_mm; - // Reassemble the now corrected normal + // Reassemble the now corrected normal auto nn = spheric_to_dir(polar, azimuth).normalized(); - double sd = back_r * m.cfg.safety_distance_mm / - m.cfg.head_back_radius_mm; + double sd = m.cfg.safety_distance_mm; - // check available distance + // check available distance Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd); if (t.distance() < w) { @@ -568,7 +437,7 @@ bool optimize_pinhead_placement(Ex policy, // viable normal that doesn't collide with the model // geometry and its very close to the default. - Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); + Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( @@ -602,10 +471,10 @@ bool optimize_pinhead_placement(Ex policy, head.r_back_mm = back_r; ret = true; - } else if (back_r > m.cfg.head_fallback_radius_mm) { + } /*else if (back_r > m.cfg.head_fallback_radius_mm) { head.r_back_mm = m.cfg.head_fallback_radius_mm; ret = optimize_pinhead_placement(policy, m, head); - } + }*/ return ret; } @@ -848,7 +717,7 @@ GroundConnection find_ground_connection( // This will ultimately determine if the route is valid or not // but the path junctions will be provided anyways, so invalid paths - // can be debugged + // can be inspected ret.pillar_base = gnd_route.pillar_base; return ret; @@ -876,7 +745,7 @@ GroundConnection optimize_ground_connection( Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/; + auto sd = sm.cfg.safety_distance_mm; auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; @@ -893,41 +762,6 @@ GroundConnection optimize_ground_connection( return find_ground_connection(policy, sm, j, bridgedir, end_radius); } -template -std::pair connect_to_ground(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) -{ - std::pair ret = {false, SupportTreeNode::ID_UNSET}; - - auto conn = find_ground_connection(policy, sm, j, dir, end_r); - ret.first = bool(conn); - ret.second = build_ground_connection(builder, sm, conn); - - return ret; -} - -template -std::pair search_ground_route(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - double end_r, - const Vec3d &init_dir = DOWN) -{ - std::pair ret = {false, SupportTreeNode::ID_UNSET}; - - auto conn = optimize_ground_connection(policy, sm, j, end_r, init_dir); - - ret.first = bool(conn); - ret.second = build_ground_connection(builder, sm, conn); - - return ret; -} - template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, diff --git a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp new file mode 100644 index 000000000..43a8ddb59 --- /dev/null +++ b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp @@ -0,0 +1,220 @@ +#ifndef SUPPORTTREEUTILSLEGACY_HPP +#define SUPPORTTREEUTILSLEGACY_HPP + +#include "SupportTreeUtils.hpp" + +// Old functions are gathered here that are used in DefaultSupportTree +// to maintain functionality that was well tested. + +namespace Slic3r { namespace sla { + +// This is a proxy function for pillar creation which will mind the gap +// between the pad and the model bottom in zero elevation mode. +// 'pinhead_junctionpt' is the starting junction point which needs to be +// routed down. sourcedir is the allowed direction of an optional bridge +// between the jp junction and the final pillar. +template +std::pair create_ground_pillar( + Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Vec3d &pinhead_junctionpt, + const Vec3d &sourcedir, + double radius, + double end_radius, + long head_id = SupportTreeNode::ID_UNSET) +{ + Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir; + long pillar_id = SupportTreeNode::ID_UNSET; + bool can_add_base = false, non_head = false; + + double gndlvl = 0.; // The Z level where pedestals should be + double jp_gnd = 0.; // The lowest Z where a junction center can be + double gap_dist = 0.; // The gap distance between the model and the pad + + double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); + + auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + + auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] + (bool base_en = true) + { + can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; + double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; + gndlvl = ground_level(sm); + if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; + jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); + gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; + }; + + eval_limits(); + + // We are dealing with a mini pillar that's potentially too long + if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) + { + std::optional diffbr = + search_widening_path(policy, sm, jp, dir, radius, + sm.cfg.head_back_radius_mm); + + if (diffbr && diffbr->endp.z() > jp_gnd) { + auto &br = builder.add_diffbridge(*diffbr); + if (head_id >= 0) builder.head(head_id).bridge_id = br.id; + endp = diffbr->endp; + radius = diffbr->end_r; + builder.add_junction(endp, radius); + non_head = true; + dir = diffbr->get_dir(); + eval_limits(); + } else return {false, pillar_id}; + } + + if (sm.cfg.object_elevation_mm < EPSILON) + { + // get a suitable direction for the corrector bridge. It is the + // original sourcedir's azimuth but the polar angle is saturated to the + // configured bridge slope. + auto [polar, azimuth] = dir_to_spheric(dir); + polar = PI - sm.cfg.bridge_slope; + Vec3d d = spheric_to_dir(polar, azimuth).normalized(); + auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); + double tmax = std::min(sm.cfg.max_bridge_length_mm, t); + t = 0.; + + double zd = endp.z() - jp_gnd; + double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + Vec3d nexp = endp; + double dlast = 0.; + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && + t < tmax) + { + t += radius; + nexp = endp + t * d; + } + + if (dlast < gap_dist && can_add_base) { + nexp = endp; + t = 0.; + can_add_base = false; + eval_limits(can_add_base); + + zd = endp.z() - jp_gnd; + tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { + t += radius; + nexp = endp + t * d; + } + } + + // Could not find a path to avoid the pad gap + if (dlast < gap_dist) return {false, pillar_id}; + + if (t > 0.) { // Need to make additional bridge + const Bridge& br = builder.add_bridge(endp, nexp, radius); + if (head_id >= 0) builder.head(head_id).bridge_id = br.id; + + builder.add_junction(nexp, radius); + endp = nexp; + non_head = true; + } + } + + Vec3d gp = to_floor(endp); + double h = endp.z() - gp.z(); + + pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : + builder.add_pillar(gp, h, radius, end_radius); + + if (can_add_base) + builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, + sm.cfg.base_radius_mm); + + return {true, pillar_id}; +} + +template +std::pair connect_to_ground(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + const Vec3d &dir, + double end_r) +{ + auto hjp = j.pos; + double r = j.r; + auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); + + double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); + double d = 0, tdown = 0; + t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); + + while (d < t && + !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, + Beam{hjp + d * dir, DOWN, r, r2}, sd) + .distance())) { + d += r; + } + + if(!std::isinf(tdown)) + return {false, SupportTreeNode::ID_UNSET}; + + Vec3d endp = hjp + d * dir; + auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r); + + if (ret.second >= 0) { + builder.add_bridge(hjp, endp, r); + builder.add_junction(endp, r); + } + + return ret; +} + +template +std::pair search_ground_route(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + double downdst = j.pos.z() - ground_level(sm); + + auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius); + if (res.first) + return res; + + // Optimize bridge direction: + // Straight path failed so we will try to search for a suitable + // direction out of the cavity. + auto [polar, azimuth] = dir_to_spheric(init_dir); + + Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); + solver.seed(0); // we want deterministic behavior + + auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto oresult = solver.to_max().optimize( + [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { + auto &[plr, azm] = input; + Vec3d n = spheric_to_dir(plr, azm).normalized(); + Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}}; + return beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); + }, + initvals({polar, azimuth}), // let's start with what we have + bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} }) + ); + + Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); + + return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius); +} + +}} // namespace Slic3r::sla + +#endif // SUPPORTTREEUTILSLEGACY_HPP diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index b433e80ee..aca193a1b 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -49,7 +49,11 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") // The route should include the source and one avoidance junction. REQUIRE(conn.path.size() == 2); - // The end radius end the pillar base's upper radius should match + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + + // The end radius and the pillar base's upper radius should match REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); // Check if the avoidance junction is indeed outside of the disk barrier's @@ -108,6 +112,10 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" // The route should include the source and one avoidance junction. REQUIRE(conn.path.size() == 2); + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + // The end radius end the pillar base's upper radius should match REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); From a20659fc2d3f8b497cd4b0659fe378184452801d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Nov 2022 15:08:28 +0100 Subject: [PATCH 093/206] New ground route search implemented Working gap avoidance for zero elevation --- src/libslic3r/AABBMesh.hpp | 6 +- src/libslic3r/SLA/BranchingTreeSLA.cpp | 34 +- src/libslic3r/SLA/SupportTree.hpp | 20 +- src/libslic3r/SLA/SupportTreeBuilder.hpp | 8 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 376 ++++++++++++++------ tests/sla_print/sla_supptreeutils_tests.cpp | 64 +++- tests/sla_print/sla_test_utils.cpp | 11 +- 7 files changed, 360 insertions(+), 159 deletions(-) diff --git a/src/libslic3r/AABBMesh.hpp b/src/libslic3r/AABBMesh.hpp index 6a08c4303..312d8926d 100644 --- a/src/libslic3r/AABBMesh.hpp +++ b/src/libslic3r/AABBMesh.hpp @@ -72,9 +72,9 @@ public: double m_t = infty(); int m_face_id = -1; const AABBMesh *m_mesh = nullptr; - Vec3d m_dir; - Vec3d m_source; - Vec3d m_normal; + Vec3d m_dir = Vec3d::Zero(); + Vec3d m_source = Vec3d::Zero(); + Vec3d m_normal = Vec3d::Zero(); friend class AABBMesh; // A valid object of this class can only be obtained from diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 2edc0fe7b..16236f7cd 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -39,7 +39,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { { double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight; - return std::min(m_sm.cfg.base_radius_mm, double(j.Rmin) + w); + return double(j.Rmin) + w; } std::vector m_unroutable_pinheads; @@ -247,32 +247,18 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { -// std::optional result = search_for_existing_pillar(from); - sla::Junction j{from.pos.cast(), get_radius(from)}; -// if (!result) { - auto conn = optimize_ground_connection( - ex_tbb, - m_sm, - j, - get_radius(to)); + Vec3d init_dir = (to.pos - from.pos).cast().normalized(); - if (conn) { -// Junction connlast = conn.path.back(); -// branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; -// n.left = from.id; - m_pillars.emplace_back(from); -// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); - m_gnd_connections[m_pillars.size() - 1] = conn; + auto conn = deepsearch_ground_connection(ex_tbb, m_sm, j, + get_radius(to), init_dir); - ret = true; - } -// } else { -// const auto &resnode = m_pillars[result->second]; -// m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); -// m_pillars[result->second].right = from.id; -// ret = true; -// } + if (conn) { + m_pillars.emplace_back(from); + m_gnd_connections[m_pillars.size() - 1] = conn; + + ret = true; + } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index b7aaf8aee..e0d7db97d 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -84,6 +84,12 @@ struct SupportTreeConfig 2 * head_back_radius_mm - head_penetration_mm; } + double safety_distance() const { return safety_distance_mm; } + double safety_distance(double r) const + { + return std::min(safety_distance_mm, r * safety_distance_mm / head_back_radius_mm); + } + // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) // ///////////////////////////////////////////////////////////////////////// @@ -91,7 +97,9 @@ struct SupportTreeConfig // The max Z angle for a normal at which it will get completely ignored. static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0; - // The shortest distance of any support structure from the model surface + // The safety gap between a support structure and model body. For support + // struts smaller than head_back_radius, the safety distance is scaled + // down accordingly. see method safety_distance() static const double constexpr safety_distance_mm = 0.5; static const double constexpr max_solo_pillar_height_mm = 15.0; @@ -117,11 +125,11 @@ struct SupportableMesh : emesh{trmsh}, pts{sp}, cfg{c} {} - explicit SupportableMesh(const AABBMesh &em, - const SupportPoints &sp, - const SupportTreeConfig &c) - : emesh{em}, pts{sp}, cfg{c} - {} +// explicit SupportableMesh(const AABBMesh &em, +// const SupportPoints &sp, +// const SupportTreeConfig &c) +// : emesh{em}, pts{sp}, cfg{c} +// {} }; inline double ground_level(const SupportableMesh &sm) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index e4567e405..93fbead99 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -79,7 +79,7 @@ struct Junction: public SupportTreeNode { struct Head: public SupportTreeNode { Vec3d dir = DOWN; Vec3d pos = {0, 0, 0}; - + double r_back_mm = 1; double r_pin_mm = 0.5; double width_mm = 2; @@ -87,12 +87,12 @@ struct Head: public SupportTreeNode { // If there is a pillar connecting to this head, then the id will be set. long pillar_id = ID_UNSET; - + long bridge_id = ID_UNSET; - + inline void invalidate() { id = ID_UNSET; } inline bool is_valid() const { return id >= 0; } - + Head(double r_big_mm, double r_small_mm, double length_mm, diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 3ee72436a..08e410431 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -316,8 +316,7 @@ std::optional search_widening_path(Ex policy, auto d = spheric_to_dir(plr, azm).normalized(); - auto sd = new_radius * sm.cfg.safety_distance_mm / - sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance(new_radius); double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius, new_radius, t, sd) @@ -427,7 +426,7 @@ bool optimize_pinhead_placement(Ex policy, // Reassemble the now corrected normal auto nn = spheric_to_dir(polar, azimuth).normalized(); - double sd = m.cfg.safety_distance_mm; + double sd = m.cfg.safety_distance(back_r); // check available distance Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd); @@ -471,10 +470,10 @@ bool optimize_pinhead_placement(Ex policy, head.r_back_mm = back_r; ret = true; - } /*else if (back_r > m.cfg.head_fallback_radius_mm) { + } else if (back_r > m.cfg.head_fallback_radius_mm) { head.r_back_mm = m.cfg.head_fallback_radius_mm; ret = optimize_pinhead_placement(policy, m, head); - }*/ + } return ret; } @@ -527,116 +526,19 @@ GroundConnection find_pillar_route(Ex policy, double end_radius) { GroundConnection ret; - ret.path.emplace_back(source); - Vec3d jp = source.pos, endp = jp, dir = sourcedir; - bool can_add_base = false/*, non_head = false*/; + double sd = sm.cfg.safety_distance(source.r); + auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)}; - double gndlvl = 0.; // The Z level where pedestals should be - double jp_gnd = 0.; // The lowest Z where a junction center can be - double gap_dist = 0.; // The gap distance between the model and the pad - double radius = source.r; - double sd = sm.cfg.safety_distance_mm; - - double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); - - auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; - - auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] - (bool base_en = true) - { - can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; - double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; - gndlvl = ground_level(sm); - if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; - jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); - gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; - }; - - eval_limits(); - - // We are dealing with a mini pillar that's potentially too long - if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) - { - std::optional diffbr = - search_widening_path(policy, sm, jp, dir, radius, - sm.cfg.head_back_radius_mm); - - if (diffbr && diffbr->endp.z() > jp_gnd) { - endp = diffbr->endp; - radius = diffbr->end_r; - ret.path.emplace_back(endp, radius); - dir = diffbr->get_dir(); - eval_limits(); - } else return ret; - } - - if (sm.cfg.object_elevation_mm < EPSILON) - { - // get a suitable direction for the corrector bridge. It is the - // original sourcedir's azimuth but the polar angle is saturated to the - // configured bridge slope. - auto [polar, azimuth] = dir_to_spheric(dir); - polar = PI - sm.cfg.bridge_slope; - Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); - double tmax = std::min(sm.cfg.max_bridge_length_mm, t); - t = 0.; - - double zd = endp.z() - jp_gnd; - double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - Vec3d nexp = endp; - double dlast = 0.; - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && - t < tmax) - { - t += radius; - nexp = endp + t * d; - } - - if (dlast < gap_dist && can_add_base) { - nexp = endp; - t = 0.; - can_add_base = false; - eval_limits(can_add_base); - - zd = endp.z() - jp_gnd; - tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { - t += radius; - nexp = endp + t * d; - } - } - - // Could not find a path to avoid the pad gap - if (dlast < gap_dist) { - ret.path.clear(); - return ret; - //return {false, pillar_id}; - } - - if (t > 0.) { // Need to make additional bridge - ret.path.emplace_back(nexp, radius); - endp = nexp; - } - } - - Vec3d gp = to_floor(endp); - auto hit = beam_mesh_hit(policy, sm.emesh, - Beam{{endp, radius}, {gp, end_radius}}, sd); + auto hit = beam_mesh_hit(policy, + sm.emesh, + Beam{{source.pos, source.r}, {gp, end_radius}}, + sd); if (std::isinf(hit.distance())) { - double base_radius = can_add_base ? - std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; + double base_radius = std::max(sm.cfg.base_radius_mm, end_radius); - Vec3d gp = to_floor(endp); ret.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; } @@ -644,6 +546,139 @@ GroundConnection find_pillar_route(Ex policy, return ret; } +//template +//GroundConnection find_pillar_route(Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// const Vec3d &sourcedir, +// double end_radius) +//{ +// GroundConnection ret; + +// ret.path.emplace_back(source); + +// Vec3d jp = source.pos, endp = jp, dir = sourcedir; +// bool can_add_base = false/*, non_head = false*/; + +// double gndlvl = 0.; // The Z level where pedestals should be +// double jp_gnd = 0.; // The lowest Z where a junction center can be +// double gap_dist = 0.; // The gap distance between the model and the pad +// double radius = source.r; +// double sd = sm.cfg.safety_distance(radius); + +// double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); + +// auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + +// auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd, end_radius] +// (bool base_en = true) +// { +// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; +// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; +// gndlvl = ground_level(sm); +// if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; +// jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); +// gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; +// }; + +// eval_limits(); + +// // We are dealing with a mini pillar that's potentially too long +// if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) +// { +// std::optional diffbr = +// search_widening_path(policy, sm, jp, dir, radius, +// sm.cfg.head_back_radius_mm); + +// if (diffbr && diffbr->endp.z() > jp_gnd) { +// endp = diffbr->endp; +// radius = diffbr->end_r; +// ret.path.emplace_back(endp, radius); +// dir = diffbr->get_dir(); +// eval_limits(); +// } else return ret; +// } + +// if (sm.cfg.object_elevation_mm < EPSILON) +// { +// // get a suitable direction for the corrector bridge. It is the +// // original sourcedir's azimuth but the polar angle is saturated to the +// // configured bridge slope. +// auto [polar, azimuth] = dir_to_spheric(dir); +// polar = PI - sm.cfg.bridge_slope; +// Vec3d d = spheric_to_dir(polar, azimuth).normalized(); +// double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); +// double tmax = std::min(sm.cfg.max_bridge_length_mm, t); +// t = 0.; + +// double zd = endp.z() - jp_gnd; +// double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); +// tmax = std::min(tmax, tmax2); + +// Vec3d nexp = endp; +// double dlast = 0.; +// double rnext = radius; +// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || +// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && +// t < tmax) +// { +// t += radius; +// nexp = endp + t * d; +// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); +// rnext = rnext + bridge_ratio * (end_radius - rnext); +// } + +// // If could not find avoidance bridge for the pad gap, try again +// // without the pillar base +// if (dlast < gap_dist && can_add_base) { +// nexp = endp; +// t = 0.; +// rnext = radius; +// can_add_base = false; +// eval_limits(can_add_base); + +// zd = endp.z() - jp_gnd; +// tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); +// tmax = std::min(tmax, tmax2); + +// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || +// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && t < tmax) { +// t += radius; +// nexp = endp + t * d; + +// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); +// rnext = rnext + bridge_ratio * (end_radius - rnext); +// } +// } + +// // Could not find a path to avoid the pad gap +// if (dlast < gap_dist) { +// ret.path.clear(); +// return ret; +// } + +// if (t > 0.) { // Need to make additional bridge +// ret.path.emplace_back(nexp, rnext); +// endp = nexp; +// } +// } + +// Vec3d gp = to_floor(endp); +// auto hit = beam_mesh_hit(policy, sm.emesh, +// Beam{{endp, radius}, {gp, end_radius}}, sd); + +// if (std::isinf(hit.distance())) { +// double base_radius = can_add_base ? +// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; + +// Vec3d gp = to_floor(endp); +// ret.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; +// } + +// return ret; +//} + inline long build_ground_connection(SupportTreeBuilder &builder, const SupportableMesh &sm, const GroundConnection &conn) @@ -689,7 +724,7 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = sm.cfg.safety_distance_mm; + auto sd = sm.cfg.safety_distance(r); double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); @@ -700,7 +735,7 @@ GroundConnection find_ground_connection( while (!gnd_route && d < t) { Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double bridge_ratio = d / (d + (endp.z() - ground_level(sm))); double pill_r = r + bridge_ratio * (end_r - r); gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); @@ -745,7 +780,7 @@ GroundConnection optimize_ground_connection( Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - auto sd = sm.cfg.safety_distance_mm; + auto sd = sm.cfg.safety_distance(j.r); auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; @@ -762,6 +797,120 @@ GroundConnection optimize_ground_connection( return find_ground_connection(policy, sm, j, bridgedir, end_radius); } +template +GroundConnection deepsearch_ground_connection( + Ex policy, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + // Score is the total lenght of the route. Feasible routes will have + // infinite length (rays not colliding with model), thus the stop score + // should be a reasonably big number. + constexpr double StopScore = 1e6; + + const auto sd = sm.cfg.safety_distance(j.r); + const auto gndlvl = ground_level(sm); + const double widening = end_radius - j.r; + const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + + auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + + Optimizer solver(criteria); + solver.seed(0); // enforce deterministic behavior + + auto optfn = [&](const opt::Input<3> &input) { + double ret = NaNd; + + // solver suggests polar, azimuth and bridge length values: + auto &[plr, azm, bridge_len] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = j.pos + bridge_len * n; + + double full_len = bridge_len + bridge_end.z() - gndlvl; + double bridge_r = j.r + widening * bridge_len / full_len; + double brhit_dist = 0.; + + if (bridge_len > EPSILON) { + // beam_mesh_hit with a zero lenght bridge is invalid + + Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; + auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); + brhit_dist = brhit.distance(); + } + + if (brhit_dist < bridge_len) { + ret = brhit_dist; + } else { + // check if pillar can be placed below + auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + + Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; + auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + + if (std::isinf(gndhit.distance())) { + // Ground route is free with this bridge + + if (sm.cfg.object_elevation_mm < EPSILON) { + // Dealing with zero elevation mode, to not route pillars + // into the gap between the optional pad and the model + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + if (gap < zelev_gap) + ret = full_len - zelev_gap + gap; + else // success + ret = StopScore; + } else { + // No zero elevation, return success + ret = StopScore; + } + } else { + // Ground route is not free + ret = bridge_len + gndhit.distance(); + } + } + + return ret; + }; + + auto [plr_init, azm_init] = dir_to_spheric(init_dir); + + // Saturate the polar angle to max tilt defined in config + plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + + auto oresult = solver.to_max().optimize( + optfn, + initvals({plr_init, azm_init, 0.}), // start with a zero bridge + bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length + ); + + GroundConnection conn; + + if (oresult.score >= StopScore) { + // search was successful, extract and apply the result + auto &[plr, azm, bridge_len] = oresult.optimum; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = j.pos + bridge_len * n; + + double full_len = bridge_len + bridge_end.z() - gndlvl; + double bridge_r = j.r + widening * bridge_len / full_len; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + + conn.path.emplace_back(j); + conn.path.emplace_back(Junction{bridge_end, bridge_r}); + + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + } + + return conn; +} + template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, @@ -779,8 +928,7 @@ bool optimize_anchor_placement(Ex policy, double lmax = std::min(sm.cfg.head_width_mm, distance(from.pos, anchor.pos) - 2 * from.r); - double sd = anchor.r_back_mm * sm.cfg.safety_distance_mm / - sm.cfg.head_back_radius_mm; + double sd = sm.cfg.safety_distance(anchor.r_back_mm); Optimizer solver(get_criteria(sm.cfg) .stop_score(anchor.fullwidth()) diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index aca193a1b..95ac76633 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -28,7 +28,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; sla::GroundConnection conn = - sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); #ifndef NDEBUG @@ -63,6 +63,66 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") REQUIRE(pR + FromRadius > CylRadius); } +TEST_CASE("Avoid disk below junction - Zero elevation", "[suptreeutils]") +{ + // In this test there will be a disk mesh with some radius, centered at + // (0, 0, 0) and above the disk, a junction from which the support pillar + // should be routed. The algorithm needs to find an avoidance route. + + using namespace Slic3r; + + constexpr double FromRadius = .5; + constexpr double EndRadius = 1.; + constexpr double CylRadius = 4.; + constexpr double CylHeight = 1.; + + sla::SupportTreeConfig cfg; + cfg.object_elevation_mm = 0.; + + indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); + + // 2.5 * CyRadius height should be enough to be able to insert a bridge + // with 45 degree tilt above the disk. + sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius}; + + sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + its_merge(disk, builder.merged_mesh()); + + its_write_stl_ascii("output_disk_ze.stl", "disk", disk); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + + // The end radius and the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); + + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); +} + TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]") { // In this test there will be a disk mesh with some radius, centered at @@ -91,7 +151,7 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; sla::GroundConnection conn = - sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); #ifndef NDEBUG diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 69feea31f..e097a3bb7 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -118,7 +118,7 @@ void test_supports(const std::string &obj_filename, // Create the special index-triangle mesh with spatial indexing which // is the input of the support point and support mesh generators - AABBMesh emesh{mesh}; + sla::SupportableMesh sm{mesh.its, {}, supportcfg}; #ifdef SLIC3R_HOLE_RAYCASTER if (hollowingcfg.enabled) @@ -130,23 +130,23 @@ void test_supports(const std::string &obj_filename, // Create the support point generator sla::SupportPointGenerator::Config autogencfg; autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); - sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; + sla::SupportPointGenerator point_gen{sm.emesh, autogencfg, [] {}, [](int) {}}; point_gen.seed(0); // Make the test repeatable point_gen.execute(out.model_slices, out.slicegrid); // Get the calculated support points. - std::vector support_points = point_gen.output(); + sm.pts = point_gen.output(); int validityflags = ASSUME_NO_REPAIR; // If there is no elevation, support points shall be removed from the // bottom of the object. if (std::abs(supportcfg.object_elevation_mm) < EPSILON) { - sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm); + sla::remove_bottom_points(sm.pts, zmin + supportcfg.base_height_mm); } else { // Should be support points at least on the bottom of the model - REQUIRE_FALSE(support_points.empty()); + REQUIRE_FALSE(sm.pts.empty()); // Also the support mesh should not be empty. validityflags |= ASSUME_NO_EMPTY; @@ -154,7 +154,6 @@ void test_supports(const std::string &obj_filename, // Generate the actual support tree sla::SupportTreeBuilder treebuilder; - sla::SupportableMesh sm{emesh, support_points, supportcfg}; switch (sm.cfg.tree_type) { case sla::SupportTreeType::Default: { From 0bbd50eaa01c83a2f5dbb8a5b9f9cc53a31769c0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Nov 2022 17:00:16 +0100 Subject: [PATCH 094/206] Bugfixes and new tests for pillar search --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 331 +----------------- src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp | 80 +++++ tests/sla_print/sla_print_tests.cpp | 8 - tests/sla_print/sla_supptreeutils_tests.cpp | 307 ++++++++++------ tests/sla_print/sla_test_utils.hpp | 54 --- 6 files changed, 277 insertions(+), 505 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index d30499c11..3f7fd7b80 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,7 +21,7 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return true; } + constexpr bool group_pillars() const noexcept { return false; } // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 08e410431..1a771e028 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -26,30 +26,6 @@ using Slic3r::opt::AlgNLoptGenetic; using Slic3r::Geometry::dir_to_spheric; using Slic3r::Geometry::spheric_to_dir; -// Helper function for pillar interconnection where pairs of already connected -// pillars should be checked for not to be processed again. This can be done -// in constant time with a set of hash values uniquely representing a pair of -// integers. The order of numbers within the pair should not matter, it has -// the same unique hash. The hash value has to have twice as many bits as the -// arguments need. If the same integral type is used for args and return val, -// make sure the arguments use only the half of the type's bit depth. -template> -IntegerOnly pairhash(I a, I b) -{ - using std::ceil; using std::log2; using std::max; using std::min; - static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT); - static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT); - static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits; - - I g = min(a, b), l = max(a, b); - - // Assume the hash will fit into the output variable - assert((g ? (ceil(log2(g))) : 0) <= shift); - assert((l ? (ceil(log2(l))) : 0) <= shift); - - return (DoubleI(g) << shift) + l; -} - // Give points on a 3D ring with given center, radius and orientation // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -294,62 +270,6 @@ Hit pinhead_mesh_hit(Ex ex, head.r_back_mm, head.width_mm, safety_d); } -template -std::optional search_widening_path(Ex policy, - const SupportableMesh &sm, - const Vec3d &jp, - const Vec3d &dir, - double radius, - double new_radius) -{ - double w = radius + 2 * sm.cfg.head_back_radius_mm; - double stopval = w + jp.z() - ground_level(sm); - Optimizer solver(get_criteria(sm.cfg).stop_score(stopval)); - - auto [polar, azimuth] = dir_to_spheric(dir); - - double fallback_ratio = radius / sm.cfg.head_back_radius_mm; - - auto oresult = solver.to_max().optimize( - [&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) { - auto &[plr, azm, t] = input; - - auto d = spheric_to_dir(plr, azm).normalized(); - - auto sd = sm.cfg.safety_distance(new_radius); - - double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius, - new_radius, t, sd) - .distance(); - - Beam beam{jp + t * d, d, new_radius}; - double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); - - if (ret > t && std::isinf(down)) - ret += jp.z() - ground_level(sm); - - return ret; - }, - initvals({polar, azimuth, w}), // start with what we have - bounds({ - {PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit - {-PI, PI}, // azimuth can be a full search - {radius + sm.cfg.head_back_radius_mm, - fallback_ratio * sm.cfg.max_bridge_length_mm} - })); - - if (oresult.score >= stopval) { - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - double t = std::get<2>(oresult.optimum); - Vec3d endp = jp + t * spheric_to_dir(polar, azimuth); - - return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm); - } - - return {}; -} - inline double distance(const SupportPoint &a, const SupportPoint &b) { return (a.pos - b.pos).norm(); @@ -518,167 +438,6 @@ struct GroundConnection { operator bool() const { return pillar_base.has_value() && !path.empty(); } }; -template -GroundConnection find_pillar_route(Ex policy, - const SupportableMesh &sm, - const Junction &source, - const Vec3d &sourcedir, - double end_radius) -{ - GroundConnection ret; - ret.path.emplace_back(source); - - double sd = sm.cfg.safety_distance(source.r); - auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)}; - - auto hit = beam_mesh_hit(policy, - sm.emesh, - Beam{{source.pos, source.r}, {gp, end_radius}}, - sd); - - if (std::isinf(hit.distance())) { - double base_radius = std::max(sm.cfg.base_radius_mm, end_radius); - - ret.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; - } - - return ret; -} - -//template -//GroundConnection find_pillar_route(Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// const Vec3d &sourcedir, -// double end_radius) -//{ -// GroundConnection ret; - -// ret.path.emplace_back(source); - -// Vec3d jp = source.pos, endp = jp, dir = sourcedir; -// bool can_add_base = false/*, non_head = false*/; - -// double gndlvl = 0.; // The Z level where pedestals should be -// double jp_gnd = 0.; // The lowest Z where a junction center can be -// double gap_dist = 0.; // The gap distance between the model and the pad -// double radius = source.r; -// double sd = sm.cfg.safety_distance(radius); - -// double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); - -// auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; - -// auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd, end_radius] -// (bool base_en = true) -// { -// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; -// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; -// gndlvl = ground_level(sm); -// if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; -// jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); -// gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; -// }; - -// eval_limits(); - -// // We are dealing with a mini pillar that's potentially too long -// if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) -// { -// std::optional diffbr = -// search_widening_path(policy, sm, jp, dir, radius, -// sm.cfg.head_back_radius_mm); - -// if (diffbr && diffbr->endp.z() > jp_gnd) { -// endp = diffbr->endp; -// radius = diffbr->end_r; -// ret.path.emplace_back(endp, radius); -// dir = diffbr->get_dir(); -// eval_limits(); -// } else return ret; -// } - -// if (sm.cfg.object_elevation_mm < EPSILON) -// { -// // get a suitable direction for the corrector bridge. It is the -// // original sourcedir's azimuth but the polar angle is saturated to the -// // configured bridge slope. -// auto [polar, azimuth] = dir_to_spheric(dir); -// polar = PI - sm.cfg.bridge_slope; -// Vec3d d = spheric_to_dir(polar, azimuth).normalized(); -// double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); -// double tmax = std::min(sm.cfg.max_bridge_length_mm, t); -// t = 0.; - -// double zd = endp.z() - jp_gnd; -// double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); -// tmax = std::min(tmax, tmax2); - -// Vec3d nexp = endp; -// double dlast = 0.; -// double rnext = radius; -// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || -// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && -// t < tmax) -// { -// t += radius; -// nexp = endp + t * d; -// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); -// rnext = rnext + bridge_ratio * (end_radius - rnext); -// } - -// // If could not find avoidance bridge for the pad gap, try again -// // without the pillar base -// if (dlast < gap_dist && can_add_base) { -// nexp = endp; -// t = 0.; -// rnext = radius; -// can_add_base = false; -// eval_limits(can_add_base); - -// zd = endp.z() - jp_gnd; -// tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); -// tmax = std::min(tmax, tmax2); - -// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || -// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && t < tmax) { -// t += radius; -// nexp = endp + t * d; - -// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); -// rnext = rnext + bridge_ratio * (end_radius - rnext); -// } -// } - -// // Could not find a path to avoid the pad gap -// if (dlast < gap_dist) { -// ret.path.clear(); -// return ret; -// } - -// if (t > 0.) { // Need to make additional bridge -// ret.path.emplace_back(nexp, rnext); -// endp = nexp; -// } -// } - -// Vec3d gp = to_floor(endp); -// auto hit = beam_mesh_hit(policy, sm.emesh, -// Beam{{endp, radius}, {gp, end_radius}}, sd); - -// if (std::isinf(hit.distance())) { -// double base_radius = can_add_base ? -// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; - -// Vec3d gp = to_floor(endp); -// ret.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; -// } - -// return ret; -//} - inline long build_ground_connection(SupportTreeBuilder &builder, const SupportableMesh &sm, const GroundConnection &conn) @@ -714,89 +473,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder, return ret; } -template -GroundConnection find_ground_connection( - Ex policy, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) -{ - auto hjp = j.pos; - double r = j.r; - auto sd = sm.cfg.safety_distance(r); - double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); - - double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); - t = std::min(t, sm.cfg.max_bridge_length_mm); - double d = 0.; - - GroundConnection gnd_route; - - while (!gnd_route && d < t) { - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - ground_level(sm))); - double pill_r = r + bridge_ratio * (end_r - r); - gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - - d += r; - } - - GroundConnection ret; - - if (d > 0.) - ret.path.emplace_back(j); - - for (auto &p : gnd_route.path) - ret.path.emplace_back(p); - - // This will ultimately determine if the route is valid or not - // but the path junctions will be provided anyways, so invalid paths - // can be inspected - ret.pillar_base = gnd_route.pillar_base; - - return ret; -} - -template -GroundConnection optimize_ground_connection( - Ex policy, - const SupportableMesh &sm, - const Junction &j, - double end_radius, - const Vec3d &init_dir = DOWN) -{ - double downdst = j.pos.z() - ground_level(sm); - - auto res = find_ground_connection(policy, sm, j, init_dir, end_radius); - if (res) - return res; - - // Optimize bridge direction: - // Straight path failed so we will try to search for a suitable - // direction out of the cavity. - auto [polar, azimuth] = dir_to_spheric(init_dir); - - Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); - solver.seed(0); // we want deterministic behavior - - auto sd = sm.cfg.safety_distance(j.r); - auto oresult = solver.to_max().optimize( - [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { - auto &[plr, azm] = input; - Vec3d n = spheric_to_dir(plr, azm).normalized(); - Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}}; - return beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); - }, - initvals({polar, azimuth}), // let's start with what we have - bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} }) - ); - - Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); - - return find_ground_connection(policy, sm, j, bridgedir, end_radius); -} - template GroundConnection deepsearch_ground_connection( Ex policy, @@ -861,10 +537,10 @@ GroundConnection deepsearch_ground_connection( if (gap < zelev_gap) ret = full_len - zelev_gap + gap; else // success - ret = StopScore; + ret = StopScore + EPSILON; } else { // No zero elevation, return success - ret = StopScore; + ret = StopScore + EPSILON; } } else { // Ground route is not free @@ -902,7 +578,8 @@ GroundConnection deepsearch_ground_connection( Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; conn.path.emplace_back(j); - conn.path.emplace_back(Junction{bridge_end, bridge_r}); + if (bridge_len > EPSILON) + conn.path.emplace_back(Junction{bridge_end, bridge_r}); conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; diff --git a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp index 43a8ddb59..b504d82fb 100644 --- a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp +++ b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp @@ -8,6 +8,86 @@ namespace Slic3r { namespace sla { +// Helper function for pillar interconnection where pairs of already connected +// pillars should be checked for not to be processed again. This can be done +// in constant time with a set of hash values uniquely representing a pair of +// integers. The order of numbers within the pair should not matter, it has +// the same unique hash. The hash value has to have twice as many bits as the +// arguments need. If the same integral type is used for args and return val, +// make sure the arguments use only the half of the type's bit depth. +template> +IntegerOnly pairhash(I a, I b) +{ + using std::ceil; using std::log2; using std::max; using std::min; + static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT); + static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT); + static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits; + + I g = min(a, b), l = max(a, b); + + // Assume the hash will fit into the output variable + assert((g ? (ceil(log2(g))) : 0) <= shift); + assert((l ? (ceil(log2(l))) : 0) <= shift); + + return (DoubleI(g) << shift) + l; +} + +template +std::optional search_widening_path(Ex policy, + const SupportableMesh &sm, + const Vec3d &jp, + const Vec3d &dir, + double radius, + double new_radius) +{ + double w = radius + 2 * sm.cfg.head_back_radius_mm; + double stopval = w + jp.z() - ground_level(sm); + Optimizer solver(get_criteria(sm.cfg).stop_score(stopval)); + + auto [polar, azimuth] = dir_to_spheric(dir); + + double fallback_ratio = radius / sm.cfg.head_back_radius_mm; + + auto oresult = solver.to_max().optimize( + [&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) { + auto &[plr, azm, t] = input; + + auto d = spheric_to_dir(plr, azm).normalized(); + + auto sd = sm.cfg.safety_distance(new_radius); + + double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius, + new_radius, t, sd) + .distance(); + + Beam beam{jp + t * d, d, new_radius}; + double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); + + if (ret > t && std::isinf(down)) + ret += jp.z() - ground_level(sm); + + return ret; + }, + initvals({polar, azimuth, w}), // start with what we have + bounds({ + {PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit + {-PI, PI}, // azimuth can be a full search + {radius + sm.cfg.head_back_radius_mm, + fallback_ratio * sm.cfg.max_bridge_length_mm} + })); + + if (oresult.score >= stopval) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + double t = std::get<2>(oresult.optimum); + Vec3d endp = jp + t * spheric_to_dir(polar, azimuth); + + return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm); + } + + return {}; +} + // This is a proxy function for pillar creation which will mind the gap // between the pad and the model bottom in zero elevation mode. // 'pinhead_junctionpt' is the starting junction point which needs to be diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 8ea91d57a..d581f8340 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = { } // namespace -TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") { - test_pairhash(); - test_pairhash(); - test_pairhash(); - test_pairhash(); -} - TEST_CASE("Support point generator should be deterministic if seeded", "[SLASupportGeneration], [SLAPointGen]") { TriangleMesh mesh = load_model("A_upsidedown.obj"); diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 95ac76633..69117358d 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -1,8 +1,158 @@ #include #include +#include + #include "libslic3r/Execution/ExecutionSeq.hpp" #include "libslic3r/SLA/SupportTreeUtils.hpp" +#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp" + +// Test pair hash for 'nums' random number pairs. +template void test_pairhash() +{ + const constexpr size_t nums = 1000; + I A[nums] = {0}, B[nums] = {0}; + std::unordered_set CH; + std::unordered_map> ints; + + std::random_device rd; + std::mt19937 gen(rd()); + + const I Ibits = int(sizeof(I) * CHAR_BIT); + const II IIbits = int(sizeof(II) * CHAR_BIT); + + int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; + if (std::is_signed::value) bits -= 1; + const I Imin = 0; + const I Imax = I(std::pow(2., bits) - 1); + + std::uniform_int_distribution dis(Imin, Imax); + + for (size_t i = 0; i < nums;) { + I a = dis(gen); + if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } + } + + for (size_t i = 0; i < nums;) { + I b = dis(gen); + if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } + } + + for (size_t i = 0; i < nums; ++i) { + I a = A[i], b = B[i]; + + REQUIRE(a != b); + + II hash_ab = Slic3r::sla::pairhash(a, b); + II hash_ba = Slic3r::sla::pairhash(b, a); + REQUIRE(hash_ab == hash_ba); + + auto it = ints.find(hash_ab); + + if (it != ints.end()) { + REQUIRE(( + (it->second.first == a && it->second.second == b) || + (it->second.first == b && it->second.second == a) + )); + } else + ints[hash_ab] = std::make_pair(a, b); + } +} + +TEST_CASE("Pillar pairhash should be unique", "[suptreeutils]") { + test_pairhash(); + test_pairhash(); + test_pairhash(); + test_pairhash(); +} + +static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, + const Slic3r::sla::SupportableMesh &sm, + const Slic3r::sla::Junction &j, + double end_r, + const std::string &stl_fname = "output.stl") +{ + using namespace Slic3r; + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + indexed_triangle_set mesh = *sm.emesh.get_triangle_mesh(); + its_merge(mesh, builder.merged_mesh()); + + its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + + // The end radius and the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(end_r)); +} + +TEST_CASE("Pillar search dumb case", "[suptreeutils]") { + using namespace Slic3r; + + constexpr double FromR = 0.5; + auto j = sla::Junction{Vec3d::Zero(), FromR}; + + SECTION("with empty mesh") { + sla::SupportableMesh sm{indexed_triangle_set{}, + sla::SupportPoints{}, + sla::SupportTreeConfig{}}; + + constexpr double EndR = 1.; + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); + + REQUIRE(conn); + REQUIRE(conn.path.size() == 1); + REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); + } + + SECTION("with zero R source and destination") { + sla::SupportableMesh sm{indexed_triangle_set{}, + sla::SupportPoints{}, + sla::SupportTreeConfig{}}; + + j.r = 0.; + constexpr double EndR = 0.; + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); + + REQUIRE(conn); + REQUIRE(conn.path.size() == 1); + REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); + REQUIRE(conn.pillar_base->r_top == Approx(0.)); + } + + SECTION("with zero init direction") { + sla::SupportableMesh sm{indexed_triangle_set{}, + sla::SupportPoints{}, + sla::SupportTreeConfig{}}; + + constexpr double EndR = 1.; + Vec3d init_dir = Vec3d::Zero(); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir); + + REQUIRE(conn); + REQUIRE(conn.path.size() == 1); + REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); + } +} TEST_CASE("Avoid disk below junction", "[suptreeutils]") { @@ -27,100 +177,34 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + SECTION("without elevation") { -#ifndef NDEBUG + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - sla::SupportTreeBuilder builder; + eval_ground_conn(conn, sm, j, EndRadius, "disk.stl"); - if (!conn) - builder.add_junction(j); + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } - sla::build_ground_connection(builder, sm, conn); + SECTION("with elevation") { + sm.cfg.object_elevation_mm = 0.; - its_merge(disk, builder.merged_mesh()); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - its_write_stl_ascii("output_disk.stl", "disk", disk); -#endif + eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl"); - REQUIRE(bool(conn)); - - // The route should include the source and one avoidance junction. - REQUIRE(conn.path.size() == 2); - - // Check if the radius increases with each node - REQUIRE(conn.path.front().r < conn.path.back().r); - REQUIRE(conn.path.back().r < conn.pillar_base->r_top); - - // The end radius and the pillar base's upper radius should match - REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); - - // Check if the avoidance junction is indeed outside of the disk barrier's - // edge. - auto p = conn.path.back().pos; - double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); - REQUIRE(pR + FromRadius > CylRadius); -} - -TEST_CASE("Avoid disk below junction - Zero elevation", "[suptreeutils]") -{ - // In this test there will be a disk mesh with some radius, centered at - // (0, 0, 0) and above the disk, a junction from which the support pillar - // should be routed. The algorithm needs to find an avoidance route. - - using namespace Slic3r; - - constexpr double FromRadius = .5; - constexpr double EndRadius = 1.; - constexpr double CylRadius = 4.; - constexpr double CylHeight = 1.; - - sla::SupportTreeConfig cfg; - cfg.object_elevation_mm = 0.; - - indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); - - // 2.5 * CyRadius height should be enough to be able to insert a bridge - // with 45 degree tilt above the disk. - sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius}; - - sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - -#ifndef NDEBUG - - sla::SupportTreeBuilder builder; - - if (!conn) - builder.add_junction(j); - - sla::build_ground_connection(builder, sm, conn); - - its_merge(disk, builder.merged_mesh()); - - its_write_stl_ascii("output_disk_ze.stl", "disk", disk); -#endif - - REQUIRE(bool(conn)); - - // The route should include the source and one avoidance junction. - REQUIRE(conn.path.size() == 2); - - // Check if the radius increases with each node - REQUIRE(conn.path.front().r < conn.path.back().r); - REQUIRE(conn.path.back().r < conn.pillar_base->r_top); - - // The end radius and the pillar base's upper radius should match - REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); - - // Check if the avoidance junction is indeed outside of the disk barrier's - // edge. - auto p = conn.path.back().pos; - double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); - REQUIRE(pR + FromRadius > CylRadius); + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } } TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]") @@ -150,38 +234,31 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + SECTION("without elevation") { + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); -#ifndef NDEBUG + eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier.stl"); - sla::SupportTreeBuilder builder; + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } - if (!conn) - builder.add_junction(j); + SECTION("without elevation") { + sm.cfg.object_elevation_mm = 0.; - sla::build_ground_connection(builder, sm, conn); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - its_merge(disk, builder.merged_mesh()); + eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier_ze.stl"); - its_write_stl_ascii("output_disk_wall.stl", "disk_wall", disk); -#endif - - REQUIRE(bool(conn)); - - // The route should include the source and one avoidance junction. - REQUIRE(conn.path.size() == 2); - - // Check if the radius increases with each node - REQUIRE(conn.path.front().r < conn.path.back().r); - REQUIRE(conn.path.back().r < conn.pillar_base->r_top); - - // The end radius end the pillar base's upper radius should match - REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); - - // Check if the avoidance junction is indeed outside of the disk barrier's - // edge. - auto p = conn.path.back().pos; - double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); - REQUIRE(pR + FromRadius > CylRadius); + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } } diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 187a72d54..103b2f66a 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -14,14 +14,12 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/SLA/Pad.hpp" #include "libslic3r/SLA/SupportTreeBuilder.hpp" -#include "libslic3r/SLA/SupportTreeUtils.hpp" #include "libslic3r/SLA/SupportPointGenerator.hpp" #include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/SLA/ConcaveHull.hpp" #include "libslic3r/MTUtils.hpp" #include "libslic3r/SVG.hpp" -#include "libslic3r/Format/OBJ.hpp" using namespace Slic3r; @@ -111,58 +109,6 @@ inline void test_support_model_collision( test_support_model_collision(obj_filename, input_supportcfg, hcfg, {}); } -// Test pair hash for 'nums' random number pairs. -template void test_pairhash() -{ - const constexpr size_t nums = 1000; - I A[nums] = {0}, B[nums] = {0}; - std::unordered_set CH; - std::unordered_map> ints; - - std::random_device rd; - std::mt19937 gen(rd()); - - const I Ibits = int(sizeof(I) * CHAR_BIT); - const II IIbits = int(sizeof(II) * CHAR_BIT); - - int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; - if (std::is_signed::value) bits -= 1; - const I Imin = 0; - const I Imax = I(std::pow(2., bits) - 1); - - std::uniform_int_distribution dis(Imin, Imax); - - for (size_t i = 0; i < nums;) { - I a = dis(gen); - if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } - } - - for (size_t i = 0; i < nums;) { - I b = dis(gen); - if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } - } - - for (size_t i = 0; i < nums; ++i) { - I a = A[i], b = B[i]; - - REQUIRE(a != b); - - II hash_ab = sla::pairhash(a, b); - II hash_ba = sla::pairhash(b, a); - REQUIRE(hash_ab == hash_ba); - - auto it = ints.find(hash_ab); - - if (it != ints.end()) { - REQUIRE(( - (it->second.first == a && it->second.second == b) || - (it->second.first == b && it->second.second == a) - )); - } else - ints[hash_ab] = std::make_pair(a, b); - } -} - // SLA Raster test utils: using TPixel = uint8_t; From 003647e898cfe5ba49b712a6fd17e7fd858337ac Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 10:48:18 +0100 Subject: [PATCH 095/206] Prepare UI for organic supports option --- src/libslic3r/PrintConfig.cpp | 4 +++- src/libslic3r/SLA/SupportTree.cpp | 3 +++ src/libslic3r/SLA/SupportTreeStrategies.hpp | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 482e36e43..ebdfd2ff6 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -181,7 +181,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed); static inline const t_config_enum_values s_keys_map_SLASupportTreeType = { {"default", int(sla::SupportTreeType::Default)}, - {"branching", int(sla::SupportTreeType::Branching)} + {"branching", int(sla::SupportTreeType::Branching)}, + //TODO: {"organic", int(sla::SupportTreeType::Organic)} }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLASupportTreeType); @@ -3614,6 +3615,7 @@ void PrintConfigDef::init_sla_params() def->enum_labels = ConfigOptionEnum::get_enum_names(); def->enum_labels[0] = L("Default"); def->enum_labels[1] = L("Branching"); + // TODO: def->enum_labels[2] = L("Organic"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index b221c4330..37c2e85e9 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -39,6 +39,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, create_branching_tree(*builder, sm); break; } + case SupportTreeType::Organic: { + // TODO + } default:; } diff --git a/src/libslic3r/SLA/SupportTreeStrategies.hpp b/src/libslic3r/SLA/SupportTreeStrategies.hpp index 487f43575..2cef1a8a9 100644 --- a/src/libslic3r/SLA/SupportTreeStrategies.hpp +++ b/src/libslic3r/SLA/SupportTreeStrategies.hpp @@ -5,7 +5,7 @@ namespace Slic3r { namespace sla { -enum class SupportTreeType { Default, Branching }; +enum class SupportTreeType { Default, Branching, Organic }; enum class PillarConnectionMode { zigzag, cross, dynamic }; }} // namespace Slic3r::sla From 2565d45543432b1c39a00aaa94d9d4eb201a0202 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 21 Nov 2022 12:35:11 +0100 Subject: [PATCH 096/206] Extend Optimizer interface to accept constraint functions --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 94 +++++++++++++++++++---- src/libslic3r/Optimize/Optimizer.hpp | 24 +++++- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 3859217da..87aec4b36 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -13,7 +13,7 @@ #include -#include +#include "Optimizer.hpp" namespace Slic3r { namespace opt { @@ -64,12 +64,36 @@ struct NLopt { // Helper RAII class for nlopt_opt template class NLoptOpt {}; +// Map a generic function to each argument following the mapping function +template +Fn for_each_argument(Fn &&fn, Args&&...args) +{ + // see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/ + (fn(std::forward(args)),...); + + return fn; +} + +template +Fn for_each_in_tuple(Fn fn, const std::tuple &tup) +{ + auto arg = std::tuple_cat(std::make_tuple(fn), tup); + auto mpfn = [](auto fn, auto...pack) { + return for_each_argument(fn, pack...); + }; + std::apply(mpfn, arg); + + return fn; +} + // Optimizers based on NLopt. template class NLoptOpt> { protected: StopCriteria m_stopcr; OptDir m_dir = OptDir::MIN; + static constexpr double ConstraintEps = 1e-6; + template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; @@ -78,7 +102,7 @@ protected: double *gradient, void *data) { - assert(n >= N); + assert(n == N); auto tdata = static_cast*>(data); @@ -101,6 +125,21 @@ protected: return scoreval; } + template + static double constrain_func(unsigned n, const double *params, + double *gradient, + void *data) + { + assert(n == N); + + auto tdata = static_cast*>(data); + + auto &fnptr = std::get<0>(*tdata); + auto funval = to_arr(params); + + return (*fnptr)(funval); + } + template void set_up(NLopt &nl, const Bounds& bounds) { @@ -125,13 +164,30 @@ protected: nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); } - template - Result optimize(NLopt &nl, Fn &&fn, const Input &initvals) + template + Result optimize(NLopt &nl, Fn &&fn, const Input &initvals, + const std::tuple &equalities, + const std::tuple &inequalities) { Result r; TOptData data = std::make_tuple(&fn, this, nl.ptr); + auto do_for_each_eq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_equality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + auto do_for_each_ineq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_inequality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + for_each_in_tuple(do_for_each_eq, equalities); + for_each_in_tuple(do_for_each_ineq, inequalities); + switch(m_dir) { case OptDir::MIN: nlopt_set_min_objective(nl.ptr, optfunc, &data); break; @@ -147,15 +203,18 @@ protected: public: - template + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl{alg, N}; set_up(nl, bounds); - return optimize(nl, std::forward(func), initvals); + return optimize(nl, std::forward(func), initvals, + equalities, inequalities); } explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} @@ -173,10 +232,12 @@ class NLoptOpt>: public NLoptOpt> using Base = NLoptOpt>; public: - template + template Result optimize(Fn&& f, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl_glob{glob, N}, nl_loc{loc, N}; @@ -184,7 +245,8 @@ public: Base::set_up(nl_loc, bounds); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); - return Base::optimize(nl_glob, std::forward(f), initvals); + return Base::optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); } explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} @@ -201,12 +263,16 @@ public: Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } - template + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {}) { - return m_opt.optimize(std::forward(func), initvals, bounds); + return m_opt.optimize(std::forward(func), initvals, bounds, + eq_constraints, + ineq_constraint); } explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} @@ -225,7 +291,9 @@ public: using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptCobyla = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptMLSL = detail::NLoptAlgComb; }} // namespace Slic3r::opt diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index bf95d9ee0..6212a5f59 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -12,6 +12,15 @@ namespace Slic3r { namespace opt { +template +using FloatingOnly = std::enable_if_t::value, O>; + +template> +constexpr T NaN = std::numeric_limits::quiet_NaN(); + +constexpr float NaNf = NaN; +constexpr double NaNd = NaN; + // A type to hold the complete result of the optimization. template struct Result { int resultcode; // Method dependent @@ -79,7 +88,7 @@ public: double stop_score() const { return m_stop_score; } - StopCriteria & max_iterations(double val) + StopCriteria & max_iterations(unsigned val) { m_max_iterations = val; return *this; } @@ -137,16 +146,25 @@ public: // For each dimension an interval (Bound) has to be given marking the bounds // for that dimension. // + // Optionally, some constraints can be given in the form of double(Input) + // functors. The parameters eq_constraints and ineq_constraints can be used + // to add equality and inequality (<= 0) constraints to the optimization. + // Note that it is up the the particular method if it accepts these + // constraints. + // // initvals have to be within the specified bounds, otherwise its undefined // behavior. // // Func can return a score of type double or optionally a ScoreGradient // class to indicate the function gradient for a optimization methods that // make use of the gradient. - template + template Result optimize(Func&& /*func*/, const Input &/*initvals*/, - const Bounds& /*bounds*/) { return {}; } + const Bounds& /*bounds*/, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {} + ) { return {}; } // optional for randomized methods: void seed(long /*s*/) {} From 2cd6a2025419db5ff57d37d6da9782ea76f633cf Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 10:59:52 +0100 Subject: [PATCH 097/206] Move merge point search out of pointcloud to support tree utils --- src/libslic3r/BranchingTree/PointCloud.cpp | 72 +------------- tests/sla_print/sla_print_tests.cpp | 100 -------------------- tests/sla_print/sla_supptreeutils_tests.cpp | 95 +++++++++++++++++++ 3 files changed, 98 insertions(+), 169 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 319f334ff..4075a20c2 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -1,81 +1,15 @@ #include "PointCloud.hpp" #include "libslic3r/Tesselate.hpp" +#include "libslic3r/SLA/SupportTreeUtils.hpp" #include namespace Slic3r { namespace branchingtree { -std::optional find_merge_pt(const Vec3f &A, - const Vec3f &B, - float critical_angle) +std::optional find_merge_pt(const Vec3f &A, const Vec3f &B, float max_slope) { - // The idea is that A and B both have their support cones. But searching - // for the intersection of these support cones is difficult and its enough - // to reduce this problem to 2D and search for the intersection of two - // rays that merge somewhere between A and B. The 2D plane is a vertical - // slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and - // the 2D X axis is determined by the XY direction of the AB vector. - // - // Z^ - // | A * - // | . . B * - // | . . . . - // | . . . . - // | . x . - // -------------------> XY - - // Determine the transformation matrix for the 2D projection: - Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f}; - Vec3f dir = diff.normalized(); // TODO: avoid normalization - - Eigen::Matrix tr2D; - tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()}; - tr2D.row(1) = Vec3f{0.f, 0.f, 1.f}; - - // Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can - // omit 'a', pretend that its the origin and use BA as the vector b. - Vec2f b = tr2D * (B - A); - - // Get the square sine of the ray emanating from 'a' towards 'b'. This ray might - // exceed the allowed angle but that is corrected subsequently. - // The sign of the original sine is also needed, hence b.y is multiplied by - // abs(b.y) - float b_sqn = b.squaredNorm(); - float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f; - - // sine2 from 'b' to 'a' is the opposite of sine2 from a to b - float sin2sig_b = -sin2sig_a; - - // Derive the allowed angles from the given critical angle. - // critical_angle is measured from the horizontal X axis. - // The rays need to go downwards which corresponds to negative angles - - float sincrit = std::sin(critical_angle); // sine of the critical angle - float sin2crit = -sincrit * sincrit; // signed sine squared - sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays - sin2sig_b = std::min(sin2sig_b, sin2crit); // - float sin2_a = std::abs(sin2sig_a); // Get cosine squared values - float sin2_b = std::abs(sin2sig_b); - float cos2_a = 1.f - sin2_a; - float cos2_b = 1.f - sin2_b; - - // Derive the new direction vectors. This is by square rooting the sin2 - // and cos2 values and restoring the original signs - Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)}; - Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)}; - - // Determine where two rays ([0, 0], Da), (b, Db) intersect. - // Based on - // https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect - // One ray is emanating from (0, 0) so the formula is simplified - double t1 = (Db.y() * b.x() - b.y() * Db.x()) / - (Da.x() * Db.y() - Da.y() * Db.x()); - - Vec2f mp = t1 * Da; - Vec3f Mp = A + tr2D.transpose() * mp; - - return t1 >= 0.f ? Mp : Vec3f{}; + return sla::find_merge_pt(A, B, max_slope); } void to_eigen_mesh(const indexed_triangle_set &its, diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index d581f8340..a733e77cc 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -165,106 +165,6 @@ TEST_CASE("DefaultSupports::FloorSupportsDoNotPierceModel", "[SLASupportGenerati // for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); //} -bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt, float angle) -{ - Vec3d D = (pt - supp).cast(); - double dot_sq = -D.z() * std::abs(-D.z()); - - return dot_sq < - D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle)); -} - -TEST_CASE("BranchingSupports::MergePointFinder", "[SLASupportGeneration][Branching]") { - SECTION("Identical points have the same merge point") { - Vec3f a{0.f, 0.f, 0.f}, b = a; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - REQUIRE((*mergept - b).norm() < EPSILON); - REQUIRE((*mergept - a).norm() < EPSILON); - } - - // ^ Z - // | a * - // | - // | b * <= mergept - SECTION("Points at different heights have the lower point as mergepoint") { - Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON); - } - - // -|---------> X - // a b - // * * - // * <= mergept - SECTION("Points at different X have mergept in the middle at lower Z") { - Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - - // Distance of mergept should be equal from both input points - float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); - - REQUIRE(D < EPSILON); - REQUIRE(!is_outside_support_cone(a, *mergept, slope)); - REQUIRE(!is_outside_support_cone(b, *mergept, slope)); - } - - // -|---------> Y - // a b - // * * - // * <= mergept - SECTION("Points at different Y have mergept in the middle at lower Z") { - Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - - // Distance of mergept should be equal from both input points - float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); - - REQUIRE(D < EPSILON); - REQUIRE(!is_outside_support_cone(a, *mergept, slope)); - REQUIRE(!is_outside_support_cone(b, *mergept, slope)); - } - - SECTION("Points separated by less than critical angle have the lower point as mergept") { - Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - REQUIRE((*mergept - b).norm() < 2 * EPSILON); - } - - // -|----------------------------> Y - // a b - // * * <= mergept * - // - SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") { - Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f}; - auto slope = EPSILON; - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - Vec3f middle = (b + a) / 2.; - REQUIRE((*mergept - middle).norm() < 4 * EPSILON); - } -} TEST_CASE("BranchingSupports::ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration][Branching]") { diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 69117358d..d529eefb5 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -262,3 +262,98 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" REQUIRE(pR + FromRadius > CylRadius); } } + +TEST_CASE("BranchingSupports::MergePointFinder", "[suptreeutils]") { + using namespace Slic3r; + + SECTION("Identical points have the same merge point") { + Vec3f a{0.f, 0.f, 0.f}, b = a; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + REQUIRE((*mergept - b).norm() < EPSILON); + REQUIRE((*mergept - a).norm() < EPSILON); + } + + // ^ Z + // | a * + // | + // | b * <= mergept + SECTION("Points at different heights have the lower point as mergepoint") { + Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON); + } + + // -|---------> X + // a b + // * * + // * <= mergept + SECTION("Points at different X have mergept in the middle at lower Z") { + Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + + // Distance of mergept should be equal from both input points + float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); + + REQUIRE(D < EPSILON); + REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope)); + REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope)); + } + + // -|---------> Y + // a b + // * * + // * <= mergept + SECTION("Points at different Y have mergept in the middle at lower Z") { + Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + + // Distance of mergept should be equal from both input points + float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); + + REQUIRE(D < EPSILON); + REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope)); + REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope)); + } + + SECTION("Points separated by less than critical angle have the lower point as mergept") { + Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + REQUIRE((*mergept - b).norm() < 2 * EPSILON); + } + + // -|----------------------------> Y + // a b + // * * <= mergept * + // + SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") { + Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f}; + auto slope = EPSILON; + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + Vec3f middle = (b + a) / 2.; + REQUIRE((*mergept - middle).norm() < 4 * EPSILON); + } +} + From 9cdd6738aee3ce2b5f9d70ba683b339d169d73f1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:01:49 +0100 Subject: [PATCH 098/206] Widening improvements in SupportTreeUtils Fix failing tests after introducing wideningfn into ground route search --- src/libslic3r/SLA/SupportTreeUtils.hpp | 257 +++++++++++++++++++++++-- 1 file changed, 241 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 1a771e028..6da28f1fd 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -473,12 +473,20 @@ inline long build_ground_connection(SupportTreeBuilder &builder, return ret; } -template +template +constexpr bool IsWideningFn = std::is_invocable_r_v; + +template> > GroundConnection deepsearch_ground_connection( Ex policy, const SupportableMesh &sm, const Junction &j, - double end_radius, + WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { // Score is the total lenght of the route. Feasible routes will have @@ -488,9 +496,6 @@ GroundConnection deepsearch_ground_connection( const auto sd = sm.cfg.safety_distance(j.r); const auto gndlvl = ground_level(sm); - const double widening = end_radius - j.r; - const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; auto criteria = get_criteria(sm.cfg).stop_score(StopScore); @@ -507,7 +512,7 @@ GroundConnection deepsearch_ground_connection( Vec3d bridge_end = j.pos + bridge_len * n; double full_len = bridge_len + bridge_end.z() - gndlvl; - double bridge_r = j.r + widening * bridge_len / full_len; + double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); double brhit_dist = 0.; if (bridge_len > EPSILON) { @@ -522,7 +527,8 @@ GroundConnection deepsearch_ground_connection( ret = brhit_dist; } else { // check if pillar can be placed below - auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); @@ -533,9 +539,11 @@ GroundConnection deepsearch_ground_connection( if (sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model - double gap = std::sqrt(sm.emesh.squared_distance(gp)); - if (gap < zelev_gap) - ret = full_len - zelev_gap + gap; + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + if (gap < max_gap) + ret = full_len - max_gap + gap; else // success ret = StopScore + EPSILON; } else { @@ -560,8 +568,8 @@ GroundConnection deepsearch_ground_connection( optfn, initvals({plr_init, azm_init, 0.}), // start with a zero bridge bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length ); GroundConnection conn; @@ -572,22 +580,153 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = j.pos + bridge_len * n; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double full_len = bridge_len + bridge_end.z() - gndlvl; - double bridge_r = j.r + widening * bridge_len / full_len; - Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); + double down_l = bridge_end.z() - gndlvl; + double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); conn.path.emplace_back(j); if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; } return conn; } +template +GroundConnection deepsearch_ground_connection(Ex policy, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + double gndlvl = ground_level(sm); + auto wfn = [end_radius, gndlvl](Ball src, Vec3d dir, double len) { + Vec3d dst = src.p + len * dir; + double widening = end_radius - src.R; + double zlen = dst.z() - gndlvl; + double full_len = len + zlen; + double r = src.R + widening * len / full_len; + + return r; + }; + + static_assert(IsWideningFn, "Not a widening function"); + + return deepsearch_ground_connection(policy, sm, j, wfn, init_dir); + +// // Score is the total lenght of the route. Feasible routes will have +// // infinite length (rays not colliding with model), thus the stop score +// // should be a reasonably big number. +// constexpr double StopScore = 1e6; + +// const auto sd = sm.cfg.safety_distance(j.r); +// const auto gndlvl = ground_level(sm); +// const double widening = end_radius - j.r; +// const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + +// auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + +// Optimizer solver(criteria); +// solver.seed(0); // enforce deterministic behavior + +// auto optfn = [&](const opt::Input<3> &input) { +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = j.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = j.r + widening * bridge_len / full_len; +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + +// if (std::isinf(gndhit.distance())) { +// // Ground route is free with this bridge + +// if (sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// if (gap < zelev_gap) +// ret = full_len - zelev_gap + gap; +// else // success +// ret = StopScore + EPSILON; +// } else { +// // No zero elevation, return success +// ret = StopScore + EPSILON; +// } +// } else { +// // Ground route is not free +// ret = bridge_len + gndhit.distance(); +// } +// } + +// return ret; +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + +// auto oresult = solver.to_max().optimize( +// optfn, +// initvals({plr_init, azm_init, 0.}), // start with a zero bridge +// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle +// {-PI, PI}, // bounds for azimuth +// {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length +// ); + +// GroundConnection conn; + +// if (oresult.score >= StopScore) { +// // search was successful, extract and apply the result +// auto &[plr, azm, bridge_len] = oresult.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = j.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = j.r + widening * bridge_len / full_len; +// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + +// conn.path.emplace_back(j); +// if (bridge_len > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; +// } + +// return conn; +} + template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, @@ -671,6 +810,92 @@ std::optional calculate_anchor_placement(Ex policy, return anchor; } +inline bool is_outside_support_cone(const Vec3f &supp, + const Vec3f &pt, + float angle) +{ + using namespace Slic3r; + + Vec3d D = (pt - supp).cast(); + double dot_sq = -D.z() * std::abs(-D.z()); + + return dot_sq < + D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle)); +} + +inline // TODO: should be in a cpp +std::optional find_merge_pt(const Vec3f &A, + const Vec3f &B, + float critical_angle) +{ + // The idea is that A and B both have their support cones. But searching + // for the intersection of these support cones is difficult and its enough + // to reduce this problem to 2D and search for the intersection of two + // rays that merge somewhere between A and B. The 2D plane is a vertical + // slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and + // the 2D X axis is determined by the XY direction of the AB vector. + // + // Z^ + // | A * + // | . . B * + // | . . . . + // | . . . . + // | . x . + // -------------------> XY + + // Determine the transformation matrix for the 2D projection: + Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f}; + Vec3f dir = diff.normalized(); // TODO: avoid normalization + + Eigen::Matrix tr2D; + tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()}; + tr2D.row(1) = Vec3f{0.f, 0.f, 1.f}; + + // Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can + // omit 'a', pretend that its the origin and use BA as the vector b. + Vec2f b = tr2D * (B - A); + + // Get the square sine of the ray emanating from 'a' towards 'b'. This ray might + // exceed the allowed angle but that is corrected subsequently. + // The sign of the original sine is also needed, hence b.y is multiplied by + // abs(b.y) + float b_sqn = b.squaredNorm(); + float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f; + + // sine2 from 'b' to 'a' is the opposite of sine2 from a to b + float sin2sig_b = -sin2sig_a; + + // Derive the allowed angles from the given critical angle. + // critical_angle is measured from the horizontal X axis. + // The rays need to go downwards which corresponds to negative angles + + float sincrit = std::sin(critical_angle); // sine of the critical angle + float sin2crit = -sincrit * sincrit; // signed sine squared + sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays + sin2sig_b = std::min(sin2sig_b, sin2crit); // + float sin2_a = std::abs(sin2sig_a); // Get cosine squared values + float sin2_b = std::abs(sin2sig_b); + float cos2_a = 1.f - sin2_a; + float cos2_b = 1.f - sin2_b; + + // Derive the new direction vectors. This is by square rooting the sin2 + // and cos2 values and restoring the original signs + Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)}; + Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)}; + + // Determine where two rays ([0, 0], Da), (b, Db) intersect. + // Based on + // https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect + // One ray is emanating from (0, 0) so the formula is simplified + double t1 = (Db.y() * b.x() - b.y() * Db.x()) / + (Da.x() * Db.y() - Da.y() * Db.x()); + + Vec2f mp = t1 * Da; + Vec3f Mp = A + tr2D.transpose() * mp; + + return t1 >= 0.f ? Mp : Vec3f{}; +} + }} // namespace Slic3r::sla #endif // SLASUPPORTTREEUTILS_H From 249d2550d359f133d1ffca256a2355ec19973593 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:12:52 +0100 Subject: [PATCH 099/206] Remove pillar grouping as it does not work nicely --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 - src/libslic3r/SLA/BranchingTreeSLA.cpp | 62 +------------------ 2 files changed, 1 insertion(+), 63 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 3f7fd7b80..2d372452b 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,8 +21,6 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return false; } - // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept { diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 16236f7cd..7d6f0b95d 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,12 +20,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::set m_ground_mem; - // Establish an index of - using PointIndexEl = std::pair; - boost::geometry::index:: - rtree /* ? */> - m_pillar_index; - std::vector m_pillars; // to put an index over them // cache succesfull ground connections @@ -112,44 +106,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { }); } - std::optional - search_for_existing_pillar(const branchingtree::Node &from) const - { - namespace bgi = boost::geometry::index; - - struct Output - { - std::optional &res; - - Output &operator*() { return *this; } - Output &operator=(const PointIndexEl &el) { res = el; return *this; } - Output &operator++() { return *this; } - }; - - std::optional result; - - auto filter = bgi::satisfies([this, &from](const PointIndexEl &e) { - assert(e.second < m_pillars.size()); - - auto len = (from.pos - e.first).norm(); - const branchingtree::Node &to = m_pillars[e.second]; - double sd = m_sm.cfg.safety_distance_mm; - - Beam beam{Ball{from.pos.cast(), get_radius(from)}, - Ball{e.first.cast(), get_radius(to)}}; - - return !branchingtree::is_occupied(to) && - len < m_sm.cfg.max_bridge_length_mm && - !m_cloud.is_outside_support_cone(from.pos, e.first) && - beam_mesh_hit(ex_tbb, m_sm.emesh, beam, sd).distance() > len; - }); - - m_pillar_index.query(filter && bgi::nearest(from.pos, 1), - Output{result}); - - return result; - } - public: BranchingTreeBuilder(SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -368,23 +324,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - std::cout << "Original pillar count: " << vbuilder.pillars().size() << std::endl; - - if constexpr (props.group_pillars()) { - - std::vector bedleafs; - std::copy(vbuilder.pillars().begin(), vbuilder.pillars().end(), std::back_inserter(bedleafs)); - - branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; - BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; - branchingtree::build_tree(gndnodes, gndbuilder); - - std::cout << "Grouped pillar count: " << gndbuilder.pillars().size() << std::endl; - build_pillars(builder, gndbuilder, sm); - - } else { - build_pillars(builder, vbuilder, sm); - } + build_pillars(builder, vbuilder, sm); for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); From 87336349b19ecf0083cb671ec6fc271a1d34f247 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:14:24 +0100 Subject: [PATCH 100/206] Widening improvements 2 --- src/libslic3r/SLA/SupportTreeUtils.hpp | 143 ++++++------------------- 1 file changed, 30 insertions(+), 113 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 6da28f1fd..41a1968b4 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -485,7 +485,7 @@ template EPSILON) { // beam_mesh_hit with a zero lenght bridge is invalid - Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; + Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); brhit_dist = brhit.distance(); } @@ -579,15 +579,15 @@ GroundConnection deepsearch_ground_connection( auto &[plr, azm, bridge_len] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = j.pos + bridge_len * n; + Vec3d bridge_end = source.pos + bridge_len * n; Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); + double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); double down_l = bridge_end.z() - gndlvl; double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - conn.path.emplace_back(j); + conn.path.emplace_back(source); if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); @@ -601,12 +601,12 @@ GroundConnection deepsearch_ground_connection( template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, - const Junction &j, + const Junction &source, double end_radius, const Vec3d &init_dir = DOWN) { double gndlvl = ground_level(sm); - auto wfn = [end_radius, gndlvl](Ball src, Vec3d dir, double len) { + auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) { Vec3d dst = src.p + len * dir; double widening = end_radius - src.R; double zlen = dst.z() - gndlvl; @@ -618,113 +618,30 @@ GroundConnection deepsearch_ground_connection(Ex policy, static_assert(IsWideningFn, "Not a widening function"); - return deepsearch_ground_connection(policy, sm, j, wfn, init_dir); + return deepsearch_ground_connection(policy, sm, source, wfn, init_dir); +} -// // Score is the total lenght of the route. Feasible routes will have -// // infinite length (rays not colliding with model), thus the stop score -// // should be a reasonably big number. -// constexpr double StopScore = 1e6; +struct DefaultWideningModel { + static constexpr double WIDENING_SCALE = 0.02; + const SupportableMesh &sm; -// const auto sd = sm.cfg.safety_distance(j.r); -// const auto gndlvl = ground_level(sm); -// const double widening = end_radius - j.r; -// const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + double operator()(const Ball &src, const Vec3d & /*dir*/, double len) { + static_assert(IsWideningFn, + "DefaultWideningModel is not a widening function"); -// auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len; + return src.R + w; + }; +}; -// Optimizer solver(criteria); -// solver.seed(0); // enforce deterministic behavior - -// auto optfn = [&](const opt::Input<3> &input) { -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = j.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = j.r + widening * bridge_len / full_len; -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - -// if (std::isinf(gndhit.distance())) { -// // Ground route is free with this bridge - -// if (sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// if (gap < zelev_gap) -// ret = full_len - zelev_gap + gap; -// else // success -// ret = StopScore + EPSILON; -// } else { -// // No zero elevation, return success -// ret = StopScore + EPSILON; -// } -// } else { -// // Ground route is not free -// ret = bridge_len + gndhit.distance(); -// } -// } - -// return ret; -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - -// auto oresult = solver.to_max().optimize( -// optfn, -// initvals({plr_init, azm_init, 0.}), // start with a zero bridge -// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle -// {-PI, PI}, // bounds for azimuth -// {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length -// ); - -// GroundConnection conn; - -// if (oresult.score >= StopScore) { -// // search was successful, extract and apply the result -// auto &[plr, azm, bridge_len] = oresult.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = j.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = j.r + widening * bridge_len / full_len; -// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - -// conn.path.emplace_back(j); -// if (bridge_len > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; -// } - -// return conn; +template +GroundConnection deepsearch_ground_connection(Ex policy, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &init_dir = DOWN) +{ + return deepsearch_ground_connection(policy, sm, source, + DefaultWideningModel{sm}, init_dir); } template From 3d6bb38dd4b1d50ffe54d9957dba6da9bed2e549 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 14:33:17 +0100 Subject: [PATCH 101/206] Fix failing tests --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 4 ++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 5 +++-- tests/sla_print/sla_supptreeutils_tests.cpp | 18 ++++++++++++++++++ tests/sla_print/sla_test_utils.cpp | 12 ++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 7d6f0b95d..1ad0fd93f 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -128,8 +128,8 @@ public: void report_unroutable(const branchingtree::Node &j) override { - BOOST_LOG_TRIVIAL(error) << "Cannot route junction at " << j.pos.x() - << " " << j.pos.y() << " " << j.pos.z(); + BOOST_LOG_TRIVIAL(warning) << "Cannot route junction at " << j.pos.x() + << " " << j.pos.y() << " " << j.pos.z(); // Discard all the support points connecting to this branch. discard_subtree(j.id); diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 41a1968b4..2d0fd859e 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -591,8 +591,9 @@ GroundConnection deepsearch_ground_connection( if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + if (bridge_end.z() >= gndlvl) + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; } return conn; diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index d529eefb5..8d1029152 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -263,6 +263,24 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" } } +TEST_CASE("Find ground route just above ground", "[suptreeutils]") { + using namespace Slic3r; + + sla::SupportTreeConfig cfg; + cfg.object_elevation_mm = 0.; + + sla::Junction j{Vec3d{0., 0., 2. * cfg.head_back_radius_mm}, cfg.head_back_radius_mm}; + + sla::SupportableMesh sm{{}, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, Geometry::spheric_to_dir(3 * PI/ 4, PI)); + + REQUIRE(conn); + + REQUIRE(conn.pillar_base->pos.z() >= Approx(ground_level(sm))); +} + TEST_CASE("BranchingSupports::MergePointFinder", "[suptreeutils]") { using namespace Slic3r; diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index e097a3bb7..b601cef11 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -181,6 +181,18 @@ void test_supports(const std::string &obj_filename, if (std::abs(supportcfg.object_elevation_mm) < EPSILON) allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm; +#ifndef NDEBUG + if (!(obb.min.z() >= Approx(allowed_zmin)) || !(obb.max.z() <= Approx(zmax))) + { + indexed_triangle_set its; + treebuilder.retrieve_full_mesh(its); + TriangleMesh m{its}; + m.merge(mesh); + m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" + + obj_filename).c_str()); + } +#endif + REQUIRE(obb.min.z() >= Approx(allowed_zmin)); REQUIRE(obb.max.z() <= Approx(zmax)); From cdac79016380ded66a02e8d65dfc34a90cbdc752 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 18:23:34 +0100 Subject: [PATCH 102/206] Try to fix pillar route search --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 31 +++++++---- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 60 ++++++++++++--------- tests/sla_print/sla_supptreeutils_tests.cpp | 6 +-- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 87aec4b36..46a0d0648 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -141,7 +141,9 @@ protected: } template - void set_up(NLopt &nl, const Bounds& bounds) + static void set_up(NLopt &nl, + const Bounds &bounds, + const StopCriteria &stopcr) { std::array lb, ub; @@ -153,15 +155,15 @@ protected: nlopt_set_lower_bounds(nl.ptr, lb.data()); nlopt_set_upper_bounds(nl.ptr, ub.data()); - double abs_diff = m_stopcr.abs_score_diff(); - double rel_diff = m_stopcr.rel_score_diff(); - double stopval = m_stopcr.stop_score(); + double abs_diff = stopcr.abs_score_diff(); + double rel_diff = stopcr.rel_score_diff(); + double stopval = stopcr.stop_score(); if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); - if(m_stopcr.max_iterations() > 0) - nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); + if(stopcr.max_iterations() > 0) + nlopt_set_maxeval(nl.ptr, stopcr.max_iterations()); } template @@ -211,7 +213,7 @@ public: const std::tuple &inequalities) { NLopt nl{alg, N}; - set_up(nl, bounds); + set_up(nl, bounds, m_stopcr); return optimize(nl, std::forward(func), initvals, equalities, inequalities); @@ -230,6 +232,7 @@ template class NLoptOpt>: public NLoptOpt> { using Base = NLoptOpt>; + StopCriteria m_loc_stopcr; public: template @@ -241,15 +244,20 @@ public: { NLopt nl_glob{glob, N}, nl_loc{loc, N}; - Base::set_up(nl_glob, bounds); - Base::set_up(nl_loc, bounds); + Base::set_up(nl_glob, bounds, Base::get_criteria()); + Base::set_up(nl_loc, bounds, m_loc_stopcr); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); return Base::optimize(nl_glob, std::forward(f), initvals, equalities, inequalities); } - explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} + explicit NLoptOpt(StopCriteria stopcr = {}) + : Base{stopcr}, m_loc_stopcr{stopcr} + {} + + void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; } + const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } }; } // namespace detail; @@ -285,6 +293,9 @@ public: const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } void seed(long s) { m_opt.seed(s); } + + void set_loc_criteria(const StopCriteria &cr) { m_opt.set_loc_criteria(cr); } + const StopCriteria &get_loc_criteria() const noexcept { return m_opt.get_loc_criteria(); } }; // Predefinded NLopt algorithms diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index e0d7db97d..ed5725780 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -105,7 +105,7 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; static const double constexpr optimizer_rel_score_diff = 1e-10; - static const unsigned constexpr optimizer_max_iterations = 2000; + static const unsigned constexpr optimizer_max_iterations = 30000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 2d0fd859e..0dffd0852 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -492,17 +492,24 @@ GroundConnection deepsearch_ground_connection( // Score is the total lenght of the route. Feasible routes will have // infinite length (rays not colliding with model), thus the stop score // should be a reasonably big number. - constexpr double StopScore = 1e6; + constexpr double Penality = 1e5; + constexpr double PenOffs = 1e2; const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); - auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + auto criteria = get_criteria(sm.cfg); + criteria.abs_score_diff(1.); + criteria.rel_score_diff(0.1); + criteria.max_iterations(5000); Optimizer solver(criteria); + solver.set_loc_criteria(criteria.max_iterations(100).abs_score_diff(1.)); solver.seed(0); // enforce deterministic behavior + size_t icnt = 0; auto optfn = [&](const opt::Input<3> &input) { + ++icnt; double ret = NaNd; // solver suggests polar, azimuth and bridge length values: @@ -511,7 +518,7 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_len * n; - double full_len = bridge_len + bridge_end.z() - gndlvl; + double down_l = bridge_end.z() - gndlvl; double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); double brhit_dist = 0.; @@ -524,7 +531,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist; + ret = brhit_dist + Penality; } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -532,28 +539,27 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + double gnd_hit_d = std::min(gndhit.distance(), down_l); + double penality = 0.; - if (std::isinf(gndhit.distance())) { - // Ground route is free with this bridge - - if (sm.cfg.object_elevation_mm < EPSILON) { - // Dealing with zero elevation mode, to not route pillars - // into the gap between the optional pad and the model - double gap = std::sqrt(sm.emesh.squared_distance(gp)); - double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - if (gap < max_gap) - ret = full_len - max_gap + gap; - else // success - ret = StopScore + EPSILON; - } else { - // No zero elevation, return success - ret = StopScore + EPSILON; + if (!std::isinf(gndhit.distance())) + penality = Penality; + else if (sm.cfg.object_elevation_mm < EPSILON) { + // Dealing with zero elevation mode, to not route pillars + // into the gap between the optional pad and the model + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + if (gap < min_gap) { + penality = Penality + PenOffs * (min_gap - gap); } - } else { - // Ground route is not free - ret = bridge_len + gndhit.distance(); +// gnd_hit_d += std::max(0., min_gap - gap); //penality = Penality + 100000. * (min_gap - gap); +// if (gap < min_gap) { +// penality = Penality; +// } } + + ret = bridge_len + gnd_hit_d + penality; } return ret; @@ -564,7 +570,7 @@ GroundConnection deepsearch_ground_connection( // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - auto oresult = solver.to_max().optimize( + auto oresult = solver.to_min().optimize( optfn, initvals({plr_init, azm_init, 0.}), // start with a zero bridge bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle @@ -572,10 +578,12 @@ GroundConnection deepsearch_ground_connection( {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length ); + std::cout << "iters: " << icnt << std::endl; + GroundConnection conn; - if (oresult.score >= StopScore) { - // search was successful, extract and apply the result + if (oresult.score < Penality) { + // Extract and apply the result auto &[plr, azm, bridge_len] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 8d1029152..58918db41 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -177,7 +177,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - SECTION("without elevation") { + SECTION("with elevation") { sla::GroundConnection conn = sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); @@ -191,7 +191,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") REQUIRE(pR + FromRadius > CylRadius); } - SECTION("with elevation") { + SECTION("without elevation") { sm.cfg.object_elevation_mm = 0.; sla::GroundConnection conn = @@ -234,7 +234,7 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - SECTION("without elevation") { + SECTION("with elevation") { sla::GroundConnection conn = sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); From 056e7400278cd81bf93aee67c6a751a0e9ddbbfc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 29 Nov 2022 18:25:39 +0100 Subject: [PATCH 103/206] Better handling of nonlinear constraints in nlopt wrapper --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 147 ++++++++++++++++------ 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 46a0d0648..900728804 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -74,18 +74,28 @@ Fn for_each_argument(Fn &&fn, Args&&...args) return fn; } -template -Fn for_each_in_tuple(Fn fn, const std::tuple &tup) +// Call fn on each element of the input tuple tup. +template +Fn for_each_in_tuple(Fn fn, Tup &&tup) { - auto arg = std::tuple_cat(std::make_tuple(fn), tup); - auto mpfn = [](auto fn, auto...pack) { - return for_each_argument(fn, pack...); + auto mpfn = [&fn](auto&...pack) { + for_each_argument(fn, pack...); }; - std::apply(mpfn, arg); + + std::apply(mpfn, tup); return fn; } +// Wrap each element of the tuple tup into a wrapper class W and return +// a new tuple with each element being of type W where T_i is the type of +// i-th element of tup. +template class W, class...Args> +auto wrap_tup(const std::tuple &tup) +{ + return std::tuple...>(tup); +} + // Optimizers based on NLopt. template class NLoptOpt> { protected: @@ -94,32 +104,38 @@ protected: static constexpr double ConstraintEps = 1e-6; - template using TOptData = - std::tuple*, NLoptOpt*, nlopt_opt>; + template struct OptData { + Fn fn; + NLoptOpt *self = nullptr; + nlopt_opt opt_raw = nullptr; + + OptData(const Fn &f): fn{f} {} + + OptData(const Fn &f, NLoptOpt *s, nlopt_opt nlopt_raw) + : fn{f}, self{s}, opt_raw{nlopt_raw} {} + }; template static double optfunc(unsigned n, const double *params, - double *gradient, - void *data) + double *gradient, void *data) { assert(n == N); - auto tdata = static_cast*>(data); + auto tdata = static_cast*>(data); - if (std::get<1>(*tdata)->m_stopcr.stop_condition()) - nlopt_force_stop(std::get<2>(*tdata)); + if (tdata->self->m_stopcr.stop_condition()) + nlopt_force_stop(tdata->opt_raw); - auto fnptr = std::get<0>(*tdata); auto funval = to_arr(params); double scoreval = 0.; - using RetT = decltype((*fnptr)(funval)); + using RetT = decltype(tdata->fn(funval)); if constexpr (std::is_convertible_v>) { - ScoreGradient score = (*fnptr)(funval); + ScoreGradient score = tdata->fn(funval); for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; scoreval = score.score; } else { - scoreval = (*fnptr)(funval); + scoreval = tdata->fn(funval); } return scoreval; @@ -127,17 +143,14 @@ protected: template static double constrain_func(unsigned n, const double *params, - double *gradient, - void *data) + double *gradient, void *data) { assert(n == N); - auto tdata = static_cast*>(data); - - auto &fnptr = std::get<0>(*tdata); + auto tdata = static_cast*>(data); auto funval = to_arr(params); - return (*fnptr)(funval); + return tdata->fn(funval); } template @@ -173,22 +186,27 @@ protected: { Result r; - TOptData data = std::make_tuple(&fn, this, nl.ptr); + OptData data {fn, this, nl.ptr}; - auto do_for_each_eq = [this, &nl](auto &&arg) { - auto data = std::make_tuple(&arg, this, nl.ptr); - using F = std::remove_cv_t; - nlopt_add_equality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + auto do_for_each_eq = [this, &nl](auto &arg) { + arg.self = this; + arg.opt_raw = nl.ptr; + using F = decltype(arg.fn); + nlopt_add_equality_constraint (nl.ptr, constrain_func, &arg, ConstraintEps); }; - auto do_for_each_ineq = [this, &nl](auto &&arg) { - auto data = std::make_tuple(&arg, this, nl.ptr); - using F = std::remove_cv_t; - nlopt_add_inequality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + auto do_for_each_ineq = [this, &nl](auto &arg) { + arg.self = this; + arg.opt_raw = nl.ptr; + using F = decltype(arg.fn); + nlopt_add_inequality_constraint (nl.ptr, constrain_func, &arg, ConstraintEps); }; - for_each_in_tuple(do_for_each_eq, equalities); - for_each_in_tuple(do_for_each_ineq, inequalities); + auto eq_data = wrap_tup(equalities); + for_each_in_tuple(do_for_each_eq, eq_data); + + auto ineq_data = wrap_tup(inequalities); + for_each_in_tuple(do_for_each_ineq, ineq_data); switch(m_dir) { case OptDir::MIN: @@ -260,8 +278,19 @@ public: const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } }; +template struct AlgFeatures_ { + static constexpr bool SupportsInequalities = false; + static constexpr bool SupportsEqualities = false; +}; + } // namespace detail; +template constexpr bool SupportsEqualities = + detail::AlgFeatures_>::SupportsEqualities; + +template constexpr bool SupportsInequalities = + detail::AlgFeatures_>::SupportsInequalities; + // Optimizers based on NLopt. template class Optimizer> { detail::NLoptOpt m_opt; @@ -278,6 +307,14 @@ public: const std::tuple &eq_constraints = {}, const std::tuple &ineq_constraint = {}) { + static_assert(std::tuple_size_v> == 0 + || SupportsEqualities, + "Equality constraints are not supported."); + + static_assert(std::tuple_size_v> == 0 + || SupportsInequalities, + "Inequality constraints are not supported."); + return m_opt.optimize(std::forward(func), initvals, bounds, eq_constraints, ineq_constraint); @@ -299,13 +336,41 @@ public: }; // Predefinded NLopt algorithms -using AlgNLoptGenetic = detail::NLoptAlgComb; -using AlgNLoptSubplex = detail::NLoptAlg; -using AlgNLoptSimplex = detail::NLoptAlg; -using AlgNLoptCobyla = detail::NLoptAlg; -using AlgNLoptDIRECT = detail::NLoptAlg; -using AlgNLoptISRES = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptGenetic = detail::NLoptAlgComb; +using AlgNLoptSubplex = detail::NLoptAlg; +using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptCobyla = detail::NLoptAlg; +using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptORIG_DIRECT = detail::NLoptAlg; +using AlgNLoptISRES = detail::NLoptAlg; +using AlgNLoptAGS = detail::NLoptAlg; + +using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; + +namespace detail { + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = false; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = false; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + +} // namespace detail }} // namespace Slic3r::opt From 0f34dfbeac20dbb951341979175da38858ec4e91 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 29 Nov 2022 18:26:20 +0100 Subject: [PATCH 104/206] Trying 2 phase optimization for pillar route search --- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 130 ++++++++++++-------- tests/sla_print/sla_supptreeutils_tests.cpp | 4 +- 3 files changed, 80 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index ed5725780..e0d7db97d 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -105,7 +105,7 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; static const double constexpr optimizer_rel_score_diff = 1e-10; - static const unsigned constexpr optimizer_max_iterations = 30000; + static const unsigned constexpr optimizer_max_iterations = 2000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 0dffd0852..94da64844 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -489,26 +489,30 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - // Score is the total lenght of the route. Feasible routes will have - // infinite length (rays not colliding with model), thus the stop score - // should be a reasonably big number. - constexpr double Penality = 1e5; - constexpr double PenOffs = 1e2; + const auto sd = sm.cfg.safety_distance(source.r); + const auto gndlvl = ground_level(sm); - const auto sd = sm.cfg.safety_distance(source.r); - const auto gndlvl = ground_level(sm); + auto criteria_heavy = get_criteria(sm.cfg); + criteria_heavy.max_iterations(30000); + criteria_heavy.abs_score_diff(NaNd); + criteria_heavy.rel_score_diff(NaNd); - auto criteria = get_criteria(sm.cfg); - criteria.abs_score_diff(1.); - criteria.rel_score_diff(0.1); - criteria.max_iterations(5000); + // Cobyla (local method) supports inequality constraints which will be + // needed here. + Optimizer solver_heavy(criteria_heavy); + solver_heavy.seed(0); - Optimizer solver(criteria); - solver.set_loc_criteria(criteria.max_iterations(100).abs_score_diff(1.)); - solver.seed(0); // enforce deterministic behavior + auto criteria_easy = get_criteria(sm.cfg); + criteria_easy.max_iterations(1000); + criteria_easy.abs_score_diff(NaNd); + criteria_easy.rel_score_diff(NaNd); + + Optimizer solver_easy(criteria_easy); + solver_easy.set_loc_criteria(StopCriteria{}.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); + solver_easy.seed(0); size_t icnt = 0; - auto optfn = [&](const opt::Input<3> &input) { + auto l_fn = [&](const opt::Input<3> &input) { ++icnt; double ret = NaNd; @@ -531,7 +535,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist + Penality; + ret = brhit_dist; } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -539,70 +543,90 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = std::min(gndhit.distance(), down_l); - double penality = 0.; + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (!std::isinf(gndhit.distance())) - penality = Penality; - else if (sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - if (gap < min_gap) { - penality = Penality + PenOffs * (min_gap - gap); - } -// gnd_hit_d += std::max(0., min_gap - gap); //penality = Penality + 100000. * (min_gap - gap); -// if (gap < min_gap) { -// penality = Penality; -// } + gnd_hit_d = gnd_hit_d - min_gap + gap; } - ret = bridge_len + gnd_hit_d + penality; + ret = bridge_len + gnd_hit_d; } return ret; }; + auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { + // solver suggests polar, azimuth and bridge length values: + auto &[plr, azm, bridge_l] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + + double down_l = bridge_end.z() - gndlvl; + double full_l = bridge_l + down_l; + + return full_l; + }; + + auto ineq_fn = [&](const opt::Input<3> &input) { + double h = h_fn(input); + double l = l_fn(input); + double r = h - l; + + return r; + }; + auto [plr_init, azm_init] = dir_to_spheric(init_dir); // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - - auto oresult = solver.to_min().optimize( - optfn, - initvals({plr_init, azm_init, 0.}), // start with a zero bridge + auto bound_constraints = bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + + auto oresult_init = solver_easy.to_max().optimize( + l_fn, + initvals({plr_init, azm_init, 0.}), // start with a zero bridge + bound_constraints ); - std::cout << "iters: " << icnt << std::endl; + auto oresult = solver_heavy.to_min().optimize( + h_fn, + oresult_init.optimum, + bound_constraints, + {}, + std::make_tuple(ineq_fn) + ); + + std::cout << "Iterations: " << icnt << std::endl; GroundConnection conn; - if (oresult.score < Penality) { - // Extract and apply the result - auto &[plr, azm, bridge_len] = oresult.optimum; + // Extract and apply the result + auto &[plr, azm, bridge_l] = oresult.optimum; - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_len * n; - Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); - double down_l = bridge_end.z() - gndlvl; - double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); - double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); + double down_l = bridge_end.z() - gndlvl; + double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - conn.path.emplace_back(source); - if (bridge_len > EPSILON) - conn.path.emplace_back(Junction{bridge_end, bridge_r}); + conn.path.emplace_back(source); + if (bridge_l > EPSILON) + conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (bridge_end.z() >= gndlvl) - conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - } + if (ineq_fn(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; return conn; } diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 58918db41..17bd17a03 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -180,7 +180,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") SECTION("with elevation") { sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN); eval_ground_conn(conn, sm, j, EndRadius, "disk.stl"); @@ -195,7 +195,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sm.cfg.object_elevation_mm = 0.; sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN); eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl"); From dfa6d03bedfb2df5590048793efb7e410231b31e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 18:46:52 +0100 Subject: [PATCH 105/206] Add AUGLAG support for nlopt wrapper --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 164 +++++++++++++++------- 1 file changed, 110 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 900728804..f360c6753 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -40,30 +40,70 @@ struct IsNLoptAlg> { static const constexpr bool value = true; }; +// NLopt can wrap any of its algorithms to use the augmented lagrangian method +// for deriving an object function from all equality and inequality constraints +// This way one can use algorithms that do not support these constraints natively +template struct NLoptAUGLAG {}; + +template +struct IsNLoptAlg>> { + static const constexpr bool value = true; +}; + +template struct IsNLoptAlg>> { + static const constexpr bool value = true; +}; + template using NLoptOnly = std::enable_if_t::value, T>; +template struct GetNLoptAlg_ { + static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; + static constexpr nlopt_algorithm Global = NLOPT_NUM_ALGORITHMS; + static constexpr bool IsAUGLAG = false; +}; + +template struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; + static constexpr nlopt_algorithm Global = a; + static constexpr bool IsAUGLAG = false; +}; + +template +struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = l; + static constexpr nlopt_algorithm Global = g; + static constexpr bool IsAUGLAG = false; +}; + +template constexpr nlopt_algorithm GetNLoptAlg_Global = GetNLoptAlg_>::Global; +template constexpr nlopt_algorithm GetNLoptAlg_Local = GetNLoptAlg_>::Local; +template constexpr bool IsAUGLAG = GetNLoptAlg_>::IsAUGLAG; + +template struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = GetNLoptAlg_Local; + static constexpr nlopt_algorithm Global = GetNLoptAlg_Global; + static constexpr bool IsAUGLAG = true; +}; enum class OptDir { MIN, MAX }; // Where to optimize -struct NLopt { // Helper RAII class for nlopt_opt +struct NLoptRAII { // Helper RAII class for nlopt_opt nlopt_opt ptr = nullptr; - template explicit NLopt(A&&...a) + template explicit NLoptRAII(A&&...a) { ptr = nlopt_create(std::forward(a)...); } - NLopt(const NLopt&) = delete; - NLopt(NLopt&&) = delete; - NLopt& operator=(const NLopt&) = delete; - NLopt& operator=(NLopt&&) = delete; + NLoptRAII(const NLoptRAII&) = delete; + NLoptRAII(NLoptRAII&&) = delete; + NLoptRAII& operator=(const NLoptRAII&) = delete; + NLoptRAII& operator=(NLoptRAII&&) = delete; - ~NLopt() { nlopt_destroy(ptr); } + ~NLoptRAII() { nlopt_destroy(ptr); } }; -template class NLoptOpt {}; - // Map a generic function to each argument following the mapping function template Fn for_each_argument(Fn &&fn, Args&&...args) @@ -96,10 +136,10 @@ auto wrap_tup(const std::tuple &tup) return std::tuple...>(tup); } -// Optimizers based on NLopt. -template class NLoptOpt> { -protected: +template> +class NLoptOpt { StopCriteria m_stopcr; + StopCriteria m_loc_stopcr; OptDir m_dir = OptDir::MIN; static constexpr double ConstraintEps = 1e-6; @@ -154,7 +194,7 @@ protected: } template - static void set_up(NLopt &nl, + static void set_up(NLoptRAII &nl, const Bounds &bounds, const StopCriteria &stopcr) { @@ -180,7 +220,7 @@ protected: } template - Result optimize(NLopt &nl, Fn &&fn, const Input &initvals, + Result optimize(NLoptRAII &nl, Fn &&fn, const Input &initvals, const std::tuple &equalities, const std::tuple &inequalities) { @@ -221,36 +261,6 @@ protected: return r; } -public: - - template - Result optimize(Func&& func, - const Input &initvals, - const Bounds& bounds, - const std::tuple &equalities, - const std::tuple &inequalities) - { - NLopt nl{alg, N}; - set_up(nl, bounds, m_stopcr); - - return optimize(nl, std::forward(func), initvals, - equalities, inequalities); - } - - explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} - - void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } - const StopCriteria &get_criteria() const noexcept { return m_stopcr; } - void set_dir(OptDir dir) noexcept { m_dir = dir; } - - void seed(long s) { nlopt_srand(s); } -}; - -template -class NLoptOpt>: public NLoptOpt> -{ - using Base = NLoptOpt>; - StopCriteria m_loc_stopcr; public: template @@ -260,22 +270,59 @@ public: const std::tuple &equalities, const std::tuple &inequalities) { - NLopt nl_glob{glob, N}, nl_loc{loc, N}; + if constexpr (IsAUGLAG) { + NLoptRAII nl_wrap{NLOPT_AUGLAG, N}; + set_up(nl_wrap, bounds, get_criteria()); - Base::set_up(nl_glob, bounds, Base::get_criteria()); - Base::set_up(nl_loc, bounds, m_loc_stopcr); - nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + NLoptRAII nl_glob{GetNLoptAlg_Global, N}; + set_up(nl_glob, bounds, get_criteria()); + nlopt_set_local_optimizer(nl_wrap.ptr, nl_glob.ptr); - return Base::optimize(nl_glob, std::forward(f), initvals, - equalities, inequalities); + if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { + NLoptRAII nl_loc{GetNLoptAlg_Local, N}; + set_up(nl_loc, bounds, m_loc_stopcr); + nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + + return optimize(nl_wrap, std::forward(f), initvals, + equalities, inequalities); + } else { + return optimize(nl_wrap, std::forward(f), initvals, + equalities, inequalities); + } + } else { + NLoptRAII nl_glob{GetNLoptAlg_Global, N}; + set_up(nl_glob, bounds, get_criteria()); + + if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { + NLoptRAII nl_loc{GetNLoptAlg_Local, N}; + set_up(nl_loc, bounds, m_loc_stopcr); + nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + + return optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); + } else { + return optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); + } + } + + assert(false); + + return {}; } - explicit NLoptOpt(StopCriteria stopcr = {}) - : Base{stopcr}, m_loc_stopcr{stopcr} + explicit NLoptOpt(const StopCriteria &stopcr_glob = {}) + : m_stopcr(stopcr_glob) {} + void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } + const StopCriteria &get_criteria() const noexcept { return m_stopcr; } + void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; } const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } + + void set_dir(OptDir dir) noexcept { m_dir = dir; } + void seed(long s) { nlopt_srand(s); } }; template struct AlgFeatures_ { @@ -345,8 +392,12 @@ using AlgNLoptORIG_DIRECT = detail::NLoptAlg; using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptAGS = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; -using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; +using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; +using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb; + +// To craft auglag algorithms (constraint support through object function transformation) +using detail::NLoptAUGLAG; namespace detail { @@ -370,6 +421,11 @@ template<> struct AlgFeatures_ { static constexpr bool SupportsEqualities = true; }; +template struct AlgFeatures_> { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + } // namespace detail }} // namespace Slic3r::opt From 02b06f0107ea24a09e6856f440bced42c249687d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 18:47:24 +0100 Subject: [PATCH 106/206] try 2 phase optimization with auglag and inequalities --- src/libslic3r/SLA/SupportTreeUtils.hpp | 366 ++++++++++++++++++-- tests/sla_print/sla_supptreeutils_tests.cpp | 10 +- 2 files changed, 343 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 94da64844..b0e76682f 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -492,24 +492,26 @@ GroundConnection deepsearch_ground_connection( const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); - auto criteria_heavy = get_criteria(sm.cfg); - criteria_heavy.max_iterations(30000); - criteria_heavy.abs_score_diff(NaNd); - criteria_heavy.rel_score_diff(NaNd); + auto criteria = get_criteria(sm.cfg); + criteria.max_iterations(2000); + criteria.abs_score_diff(NaNd); + criteria.rel_score_diff(NaNd); + + auto criteria_loc = criteria; + criteria_loc.max_iterations(100); + criteria_loc.abs_score_diff(EPSILON); + criteria_loc.rel_score_diff(0.05); // Cobyla (local method) supports inequality constraints which will be // needed here. - Optimizer solver_heavy(criteria_heavy); - solver_heavy.seed(0); + Optimizer> solver(criteria); + solver.set_loc_criteria(criteria_loc); + solver.seed(0); - auto criteria_easy = get_criteria(sm.cfg); - criteria_easy.max_iterations(1000); - criteria_easy.abs_score_diff(NaNd); - criteria_easy.rel_score_diff(NaNd); - - Optimizer solver_easy(criteria_easy); - solver_easy.set_loc_criteria(StopCriteria{}.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); - solver_easy.seed(0); + constexpr double Cap = 1e6; + Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); + solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); + solver_initial.seed(0); size_t icnt = 0; auto l_fn = [&](const opt::Input<3> &input) { @@ -543,20 +545,27 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - gnd_hit_d = gnd_hit_d - min_gap + gap; + + if (gap < min_gap) { + gnd_hit_d = down_l - min_gap + gap; + } } ret = bridge_len + gnd_hit_d; } + if (std::isinf(ret)) { + ret = Cap + EPSILON; + } + return ret; }; @@ -578,7 +587,16 @@ GroundConnection deepsearch_ground_connection( double l = l_fn(input); double r = h - l; - return r; + return r; // <= 0 + }; + + auto ineq_fn_gnd = [&](const opt::Input<3> &input) { + auto &[plr, azm, bridge_l] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + + return gndlvl - bridge_end.z(); // <= 0 }; auto [plr_init, azm_init] = dir_to_spheric(init_dir); @@ -587,22 +605,24 @@ GroundConnection deepsearch_ground_connection( plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); auto bound_constraints = bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - auto oresult_init = solver_easy.to_max().optimize( + auto oresult_init = solver_initial.to_max().optimize( l_fn, - initvals({plr_init, azm_init, 0.}), // start with a zero bridge - bound_constraints - ); + initvals({plr_init, azm_init, 0.}), + bound_constraints/*, + {}, + std::make_tuple(ineq_fn_gnd)*/ + ); - auto oresult = solver_heavy.to_min().optimize( + auto oresult = solver.to_min().optimize( h_fn, oresult_init.optimum, bound_constraints, {}, - std::make_tuple(ineq_fn) - ); + std::make_tuple(ineq_fn, ineq_fn_gnd) + ); std::cout << "Iterations: " << icnt << std::endl; @@ -624,13 +644,303 @@ GroundConnection deepsearch_ground_connection( if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (ineq_fn(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) + if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; return conn; } +//template> > +//GroundConnection deepsearch_ground_connection( +// Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// WideningFn &&wideningfn, +// const Vec3d &init_dir = DOWN) +//{ +// const auto sd = sm.cfg.safety_distance(source.r); +// const auto gndlvl = ground_level(sm); + +// auto criteria_heavy = get_criteria(sm.cfg); +// criteria_heavy.max_iterations(10000); +// criteria_heavy.abs_score_diff(NaNd); +// criteria_heavy.rel_score_diff(NaNd); + +// // Cobyla (local method) supports inequality constraints which will be +// // needed here. +// Optimizer solver_heavy(criteria_heavy); +// solver_heavy.seed(0); + +// // Score is the total lenght of the route. Feasible routes will have +// // infinite length (rays not colliding with model), thus the stop score +// // should be a reasonably big number. +// constexpr double StopScore = 1e6; + +// auto criteria_easy = get_criteria(sm.cfg); +// criteria_easy.max_iterations(1000); +// criteria_easy.abs_score_diff(NaNd); +// criteria_easy.rel_score_diff(NaNd); +// criteria_easy.stop_score(StopScore); + +// Optimizer solver_easy(criteria_easy); +// solver_easy.set_loc_criteria(criteria_easy.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); +// solver_easy.seed(0); + +// size_t icnt = 0; +// auto optfn = [&](const opt::Input<3> &input) { +// ++icnt; + +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + +// if (std::isinf(gndhit.distance())) { +// // Ground route is free with this bridge + +// if (sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; +// if (gap < max_gap) +// ret = full_len - max_gap + gap; +// else // success +// ret = StopScore + EPSILON; +// } else { +// // No zero elevation, return success +// ret = StopScore + EPSILON; +// } +// } else { +// // Ground route is not free +// ret = bridge_len + gndhit.distance(); +// } +// } + +// return ret; +// }; + +// auto l_fn = [&](const opt::Input<3> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); +// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + +// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; +// gnd_hit_d = gnd_hit_d - min_gap + gap; +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// return ret; +// }; + +// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn = [&](const opt::Input<3> &input) { +// double h = h_fn(input); +// double l = l_fn(input); +// double r = h - l; + +// return r; +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); +// auto bound_constraints = +// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle +// {-PI, PI}, // bounds for azimuth +// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + +// auto oresult_init = solver_easy.to_max().optimize( +// optfn, +// initvals({plr_init, azm_init, 0.}), // start with a zero bridge +// bound_constraints +// ); + +// auto l_fn_len = [&](const opt::Input<1> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &bridge_len = input[0]; +// auto &[plr, azm, _] = oresult_init.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); +// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + +// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; +// gnd_hit_d = gnd_hit_d - min_gap + gap; +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// return ret; +// }; + +// auto h_fn_len = [&source, gndlvl, &oresult_init](const opt::Input<1> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &bridge_l = input[0]; +// auto &[plr, azm, _] = oresult_init.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn_len = [&](const opt::Input<1> &input) { +// double h = h_fn_len(input); +// double l = l_fn_len(input); +// double r = h - l; + +// return r; +// }; + +// auto oresult = solver_heavy.to_min().optimize( +// h_fn_len, +// opt::Input<1>({oresult_init.optimum[2]}), +// {bound_constraints[2]}, +// {}, +// std::make_tuple(ineq_fn_len) +// ); + +// std::cout << "Iterations: " << icnt << std::endl; + +// GroundConnection conn; + +// // Extract and apply the result +//// auto &[plr, azm, bridge_l] = oresult.optimum; +// double plr = oresult_init.optimum[0]; +// double azm = oresult_init.optimum[1]; +// double bridge_l = oresult.optimum[0]; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; +// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); +// double down_l = bridge_end.z() - gndlvl; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + +// conn.path.emplace_back(source); +// if (bridge_l > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// if (ineq_fn_len(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + +// return conn; +//} + template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 17bd17a03..c8abbb831 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -74,7 +74,7 @@ static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, { using namespace Slic3r; -#ifndef NDEBUG +//#ifndef NDEBUG sla::SupportTreeBuilder builder; @@ -87,7 +87,7 @@ static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, its_merge(mesh, builder.merged_mesh()); its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh); -#endif +//#endif REQUIRE(bool(conn)); @@ -118,7 +118,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); } @@ -133,7 +133,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); REQUIRE(conn.pillar_base->r_top == Approx(0.)); } @@ -149,7 +149,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); } } From 5e34bbcbe597105066b13ddb5f1b3a3d56b265c3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 14:02:48 +0100 Subject: [PATCH 107/206] try z level optimization with post processing --- src/libslic3r/SLA/SupportTreeUtils.hpp | 274 +++++++++++++++++++------ 1 file changed, 210 insertions(+), 64 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index b0e76682f..bc6f86919 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -489,32 +489,31 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - const auto sd = sm.cfg.safety_distance(source.r); + auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); auto criteria = get_criteria(sm.cfg); - criteria.max_iterations(2000); + criteria.max_iterations(5000); criteria.abs_score_diff(NaNd); criteria.rel_score_diff(NaNd); + criteria.stop_score(gndlvl); auto criteria_loc = criteria; criteria_loc.max_iterations(100); criteria_loc.abs_score_diff(EPSILON); criteria_loc.rel_score_diff(0.05); - // Cobyla (local method) supports inequality constraints which will be - // needed here. - Optimizer> solver(criteria); + Optimizer solver(criteria); solver.set_loc_criteria(criteria_loc); solver.seed(0); - constexpr double Cap = 1e6; - Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); - solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); - solver_initial.seed(0); - + // functor returns the z height of collision point, given a polar and + // azimuth angles as bridge direction and bridge length. The route is + // traced from source, throught this bridge and an attached pillar. If there + // is a collision with the mesh, the Z height is returned. Otherwise the + // z level of ground is returned. size_t icnt = 0; - auto l_fn = [&](const opt::Input<3> &input) { + auto z_fn = [&](const opt::Input<3> &input) { ++icnt; double ret = NaNd; @@ -537,7 +536,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist; + ret = (source.pos + brhit_dist * n).z(); } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -545,9 +544,9 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); @@ -559,46 +558,12 @@ GroundConnection deepsearch_ground_connection( } } - ret = bridge_len + gnd_hit_d; - } - - if (std::isinf(ret)) { - ret = Cap + EPSILON; + ret = bridge_end.z() - gnd_hit_d; } return ret; }; - auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { - // solver suggests polar, azimuth and bridge length values: - auto &[plr, azm, bridge_l] = input; - - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_l * n; - - double down_l = bridge_end.z() - gndlvl; - double full_l = bridge_l + down_l; - - return full_l; - }; - - auto ineq_fn = [&](const opt::Input<3> &input) { - double h = h_fn(input); - double l = l_fn(input); - double r = h - l; - - return r; // <= 0 - }; - - auto ineq_fn_gnd = [&](const opt::Input<3> &input) { - auto &[plr, azm, bridge_l] = input; - - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_l * n; - - return gndlvl - bridge_end.z(); // <= 0 - }; - auto [plr_init, azm_init] = dir_to_spheric(init_dir); // Saturate the polar angle to max tilt defined in config @@ -608,20 +573,15 @@ GroundConnection deepsearch_ground_connection( {-PI, PI}, // bounds for azimuth {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - auto oresult_init = solver_initial.to_max().optimize( - l_fn, - initvals({plr_init, azm_init, 0.}), - bound_constraints/*, - {}, - std::make_tuple(ineq_fn_gnd)*/ - ); - + // The optimizer can navigate fairly well on the mesh surface, finding + // lower and lower Z coordinates as collision points. MLSL is not a local + // search method, so it should not be trapped in a local minima. Eventually, + // this search should arrive at a ground location, like water flows down a + // surface. auto oresult = solver.to_min().optimize( - h_fn, - oresult_init.optimum, - bound_constraints, - {}, - std::make_tuple(ineq_fn, ineq_fn_gnd) + z_fn, + initvals({plr_init, azm_init, 0.}), + bound_constraints ); std::cout << "Iterations: " << icnt << std::endl; @@ -629,7 +589,22 @@ GroundConnection deepsearch_ground_connection( GroundConnection conn; // Extract and apply the result - auto &[plr, azm, bridge_l] = oresult.optimum; + auto [plr, azm, bridge_l] = oresult.optimum; + + // Now that the optimizer gave a possible route to ground with a bridge + // direction and lenght. This lenght can be shortened further by + // brute-force queries of free route straigt down for a possible pillar. + // NOTE: This requirement could be added to the optimization, but it would + // not find quickly enough an accurate solution. + double l = 0.; + double zlvl = std::numeric_limits::infinity(); + while(zlvl > gndlvl && l < sm.cfg.max_bridge_length_mm) { + zlvl = z_fn({plr, azm, l}); + if (zlvl <= gndlvl) + bridge_l = l; + + l += source.r; + } Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_l * n; @@ -644,7 +619,7 @@ GroundConnection deepsearch_ground_connection( if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) + if (z_fn(opt::Input<3>({plr, azm, bridge_l})) <= gndlvl) conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; @@ -663,6 +638,177 @@ GroundConnection deepsearch_ground_connection( // const auto sd = sm.cfg.safety_distance(source.r); // const auto gndlvl = ground_level(sm); +// auto criteria = get_criteria(sm.cfg); +// criteria.max_iterations(2000); +// criteria.abs_score_diff(NaNd); +// criteria.rel_score_diff(NaNd); + +// auto criteria_loc = criteria; +// criteria_loc.max_iterations(100); +// criteria_loc.abs_score_diff(EPSILON); +// criteria_loc.rel_score_diff(0.05); + +// // Cobyla (local method) supports inequality constraints which will be +// // needed here. +// Optimizer> solver(criteria); +// solver.set_loc_criteria(criteria_loc); +// solver.seed(0); + +// constexpr double Cap = 1e6; +// Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); +// solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); +// solver_initial.seed(0); + +// size_t icnt = 0; +// auto l_fn = [&](const opt::Input<3> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); +// double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); + +// if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + +// if (gap < min_gap) { +// gnd_hit_d = down_l - min_gap + gap; +// } +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// if (std::isinf(ret)) { +// ret = Cap + EPSILON; +// } + +// return ret; +// }; + +// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn = [&](const opt::Input<3> &input) { +// double h = h_fn(input); +// double l = l_fn(input); +// double r = h - l; + +// return r; // <= 0 +// }; + +// auto ineq_fn_gnd = [&](const opt::Input<3> &input) { +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// return gndlvl - bridge_end.z(); // <= 0 +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); +// auto bound_constraints = +// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle +// {-PI, PI}, // bounds for azimuth +// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + +// auto oresult_init = solver_initial.to_max().optimize( +// l_fn, +// initvals({plr_init, azm_init, 0.}), +// bound_constraints/*, +// {}, +// std::make_tuple(ineq_fn_gnd)*/ +// ); + +// auto oresult = solver.to_min().optimize( +// h_fn, +// oresult_init.optimum, +// bound_constraints, +// {}, +// std::make_tuple(ineq_fn, ineq_fn_gnd) +// ); + +// std::cout << "Iterations: " << icnt << std::endl; + +// GroundConnection conn; + +// // Extract and apply the result +// auto &[plr, azm, bridge_l] = oresult.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; +// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); +// double down_l = bridge_end.z() - gndlvl; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + +// conn.path.emplace_back(source); +// if (bridge_l > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + +// return conn; +//} + +//template> > +//GroundConnection deepsearch_ground_connection( +// Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// WideningFn &&wideningfn, +// const Vec3d &init_dir = DOWN) +//{ +// const auto sd = sm.cfg.safety_distance(source.r); +// const auto gndlvl = ground_level(sm); + // auto criteria_heavy = get_criteria(sm.cfg); // criteria_heavy.max_iterations(10000); // criteria_heavy.abs_score_diff(NaNd); From 3d81800d152e4dcdea8f0344346ff774ded82b3d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:17:15 +0100 Subject: [PATCH 108/206] Improve code --- src/libslic3r/SLA/SupportTreeUtils.hpp | 642 +++++-------------------- 1 file changed, 110 insertions(+), 532 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index bc6f86919..13586d937 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -110,31 +110,34 @@ inline StopCriteria get_criteria(const SupportTreeConfig &cfg) // A simple sphere with a center and a radius struct Ball { Vec3d p; double R; }; -struct Beam { // Defines a set of rays displaced along a cone's surface - static constexpr size_t SAMPLES = 8; +template +struct Beam_ { // Defines a set of rays displaced along a cone's surface + static constexpr size_t SAMPLES = Samples; - Vec3d src; - Vec3d dir; + Vec3d src; + Vec3d dir; double r1; double r2; // radius of the beam 1 unit further from src in dir direction - Beam(const Vec3d &s, const Vec3d &d, double R1, double R2): - src{s}, dir{d}, r1{R1}, r2{R2} {}; + Beam_(const Vec3d &s, const Vec3d &d, double R1, double R2) + : src{s}, dir{d}, r1{R1}, r2{R2} {}; - Beam(const Ball &src_ball, const Ball &dst_ball): - src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R} + Beam_(const Ball &src_ball, const Ball &dst_ball) + : src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R} { - r2 = src_ball.R + - (dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); + r2 = src_ball.R + + (dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); } - Beam(const Vec3d &s, const Vec3d &d, double R) + Beam_(const Vec3d &s, const Vec3d &d, double R) : src{s}, dir{d}, r1{R}, r2{R} {} }; -template -Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) +using Beam = Beam_<8>; + +template +Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) { Vec3d src = beam.src; Vec3d dst = src + beam.dir; @@ -143,12 +146,12 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) Vec3d D = (dst - src); Vec3d dir = D.normalized(); - PointRing ring{dir}; + PointRing ring{dir}; using Hit = AABBMesh::hit_result; // Hit results - std::array hits; + std::array hits; execution::for_each( ex, size_t(0), hits.size(), @@ -172,7 +175,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) } } else hit = hr; - }, std::min(execution::max_concurrency(ex), Beam::SAMPLES)); + }, std::min(execution::max_concurrency(ex), S)); return min_hit(hits.begin(), hits.end()); } @@ -480,6 +483,79 @@ constexpr bool IsWideningFn = std::is_invocable_r_v; +template struct BeamSamples { static constexpr size_t Value = 8; }; +template constexpr size_t BeamSamplesV = BeamSamples>::Value; + + +enum class GroundRouteCheck { Full, PillarOnly }; + +template> > +Vec3d check_ground_route( + Ex policy, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &dir, + double bridge_len, + WideningFn &&wideningfn, + GroundRouteCheck type = GroundRouteCheck::Full + ) +{ + static const constexpr auto Samples = BeamSamplesV; + + Vec3d ret; + + const auto sd = sm.cfg.safety_distance(source.r); + const auto gndlvl = ground_level(sm); + + Vec3d bridge_end = source.pos + bridge_len * dir; + + double down_l = bridge_end.z() - gndlvl; + double bridge_r = wideningfn(Ball{source.pos, source.r}, dir, bridge_len); + double brhit_dist = 0.; + + if (bridge_len > EPSILON && type == GroundRouteCheck::Full) { + // beam_mesh_hit with a zero lenght bridge is invalid + + Beam_ bridgebeam{Ball{source.pos, source.r}, + Ball{bridge_end, bridge_r}}; + + auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); + brhit_dist = brhit.distance(); + } else { + brhit_dist = bridge_len; + } + + if (brhit_dist < bridge_len) { + ret = (source.pos + brhit_dist * dir); + } else { + // check if pillar can be placed below + auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + double end_radius = wideningfn( + Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + + Beam_ gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; + auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + + if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + // Dealing with zero elevation mode, to not route pillars + // into the gap between the optional pad and the model + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + + if (gap < min_gap) { + gnd_hit_d = down_l - min_gap + gap; + } + } + + ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d}; + } + + return ret; +} + template> > GroundConnection deepsearch_ground_connection( @@ -489,7 +565,6 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); auto criteria = get_criteria(sm.cfg); @@ -512,56 +587,15 @@ GroundConnection deepsearch_ground_connection( // traced from source, throught this bridge and an attached pillar. If there // is a collision with the mesh, the Z height is returned. Otherwise the // z level of ground is returned. - size_t icnt = 0; auto z_fn = [&](const opt::Input<3> &input) { - ++icnt; - double ret = NaNd; - // solver suggests polar, azimuth and bridge length values: auto &[plr, azm, bridge_len] = input; Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_len * n; - double down_l = bridge_end.z() - gndlvl; - double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); - double brhit_dist = 0.; + Vec3d hitpt = check_ground_route(policy, sm, source, n, bridge_len, wideningfn); - if (bridge_len > EPSILON) { - // beam_mesh_hit with a zero lenght bridge is invalid - - Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; - auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); - brhit_dist = brhit.distance(); - } - - if (brhit_dist < bridge_len) { - ret = (source.pos + brhit_dist * n).z(); - } else { - // check if pillar can be placed below - auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; - double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - - Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; - auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { - // Dealing with zero elevation mode, to not route pillars - // into the gap between the optional pad and the model - double gap = std::sqrt(sm.emesh.squared_distance(gp)); - double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - - if (gap < min_gap) { - gnd_hit_d = down_l - min_gap + gap; - } - } - - ret = bridge_end.z() - gnd_hit_d; - } - - return ret; + return hitpt.z(); }; auto [plr_init, azm_init] = dir_to_spheric(init_dir); @@ -584,29 +618,30 @@ GroundConnection deepsearch_ground_connection( bound_constraints ); - std::cout << "Iterations: " << icnt << std::endl; - GroundConnection conn; // Extract and apply the result auto [plr, azm, bridge_l] = oresult.optimum; + Vec3d n = spheric_to_dir(plr, azm); - // Now that the optimizer gave a possible route to ground with a bridge - // direction and lenght. This lenght can be shortened further by - // brute-force queries of free route straigt down for a possible pillar. - // NOTE: This requirement could be added to the optimization, but it would - // not find quickly enough an accurate solution. - double l = 0.; + // Now the optimizer gave a possible route to ground with a bridge direction + // and length. This length can be shortened further by brute-force queries + // of free route straigt down for a possible pillar. + // NOTE: This requirement could be incorporated into the optimization as a + // constraint, but it would not find quickly enough an accurate solution. + double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); - while(zlvl > gndlvl && l < sm.cfg.max_bridge_length_mm) { - zlvl = z_fn({plr, azm, l}); + while(zlvl > gndlvl && l <= l_max) { + + zlvl = check_ground_route(policy, sm, source, n, l, wideningfn, + GroundRouteCheck::PillarOnly).z(); + if (zlvl <= gndlvl) bridge_l = l; l += source.r; } - Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_l * n; Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -626,467 +661,6 @@ GroundConnection deepsearch_ground_connection( return conn; } -//template> > -//GroundConnection deepsearch_ground_connection( -// Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// WideningFn &&wideningfn, -// const Vec3d &init_dir = DOWN) -//{ -// const auto sd = sm.cfg.safety_distance(source.r); -// const auto gndlvl = ground_level(sm); - -// auto criteria = get_criteria(sm.cfg); -// criteria.max_iterations(2000); -// criteria.abs_score_diff(NaNd); -// criteria.rel_score_diff(NaNd); - -// auto criteria_loc = criteria; -// criteria_loc.max_iterations(100); -// criteria_loc.abs_score_diff(EPSILON); -// criteria_loc.rel_score_diff(0.05); - -// // Cobyla (local method) supports inequality constraints which will be -// // needed here. -// Optimizer> solver(criteria); -// solver.set_loc_criteria(criteria_loc); -// solver.seed(0); - -// constexpr double Cap = 1e6; -// Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); -// solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); -// solver_initial.seed(0); - -// size_t icnt = 0; -// auto l_fn = [&](const opt::Input<3> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); -// double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); - -// if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - -// if (gap < min_gap) { -// gnd_hit_d = down_l - min_gap + gap; -// } -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// if (std::isinf(ret)) { -// ret = Cap + EPSILON; -// } - -// return ret; -// }; - -// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn = [&](const opt::Input<3> &input) { -// double h = h_fn(input); -// double l = l_fn(input); -// double r = h - l; - -// return r; // <= 0 -// }; - -// auto ineq_fn_gnd = [&](const opt::Input<3> &input) { -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// return gndlvl - bridge_end.z(); // <= 0 -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); -// auto bound_constraints = -// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle -// {-PI, PI}, // bounds for azimuth -// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - -// auto oresult_init = solver_initial.to_max().optimize( -// l_fn, -// initvals({plr_init, azm_init, 0.}), -// bound_constraints/*, -// {}, -// std::make_tuple(ineq_fn_gnd)*/ -// ); - -// auto oresult = solver.to_min().optimize( -// h_fn, -// oresult_init.optimum, -// bound_constraints, -// {}, -// std::make_tuple(ineq_fn, ineq_fn_gnd) -// ); - -// std::cout << "Iterations: " << icnt << std::endl; - -// GroundConnection conn; - -// // Extract and apply the result -// auto &[plr, azm, bridge_l] = oresult.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; -// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); -// double down_l = bridge_end.z() - gndlvl; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - -// conn.path.emplace_back(source); -// if (bridge_l > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - -// return conn; -//} - -//template> > -//GroundConnection deepsearch_ground_connection( -// Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// WideningFn &&wideningfn, -// const Vec3d &init_dir = DOWN) -//{ -// const auto sd = sm.cfg.safety_distance(source.r); -// const auto gndlvl = ground_level(sm); - -// auto criteria_heavy = get_criteria(sm.cfg); -// criteria_heavy.max_iterations(10000); -// criteria_heavy.abs_score_diff(NaNd); -// criteria_heavy.rel_score_diff(NaNd); - -// // Cobyla (local method) supports inequality constraints which will be -// // needed here. -// Optimizer solver_heavy(criteria_heavy); -// solver_heavy.seed(0); - -// // Score is the total lenght of the route. Feasible routes will have -// // infinite length (rays not colliding with model), thus the stop score -// // should be a reasonably big number. -// constexpr double StopScore = 1e6; - -// auto criteria_easy = get_criteria(sm.cfg); -// criteria_easy.max_iterations(1000); -// criteria_easy.abs_score_diff(NaNd); -// criteria_easy.rel_score_diff(NaNd); -// criteria_easy.stop_score(StopScore); - -// Optimizer solver_easy(criteria_easy); -// solver_easy.set_loc_criteria(criteria_easy.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); -// solver_easy.seed(0); - -// size_t icnt = 0; -// auto optfn = [&](const opt::Input<3> &input) { -// ++icnt; - -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - -// if (std::isinf(gndhit.distance())) { -// // Ground route is free with this bridge - -// if (sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; -// if (gap < max_gap) -// ret = full_len - max_gap + gap; -// else // success -// ret = StopScore + EPSILON; -// } else { -// // No zero elevation, return success -// ret = StopScore + EPSILON; -// } -// } else { -// // Ground route is not free -// ret = bridge_len + gndhit.distance(); -// } -// } - -// return ret; -// }; - -// auto l_fn = [&](const opt::Input<3> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); -// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - -// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; -// gnd_hit_d = gnd_hit_d - min_gap + gap; -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// return ret; -// }; - -// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn = [&](const opt::Input<3> &input) { -// double h = h_fn(input); -// double l = l_fn(input); -// double r = h - l; - -// return r; -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); -// auto bound_constraints = -// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle -// {-PI, PI}, // bounds for azimuth -// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - -// auto oresult_init = solver_easy.to_max().optimize( -// optfn, -// initvals({plr_init, azm_init, 0.}), // start with a zero bridge -// bound_constraints -// ); - -// auto l_fn_len = [&](const opt::Input<1> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &bridge_len = input[0]; -// auto &[plr, azm, _] = oresult_init.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); -// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - -// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; -// gnd_hit_d = gnd_hit_d - min_gap + gap; -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// return ret; -// }; - -// auto h_fn_len = [&source, gndlvl, &oresult_init](const opt::Input<1> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &bridge_l = input[0]; -// auto &[plr, azm, _] = oresult_init.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn_len = [&](const opt::Input<1> &input) { -// double h = h_fn_len(input); -// double l = l_fn_len(input); -// double r = h - l; - -// return r; -// }; - -// auto oresult = solver_heavy.to_min().optimize( -// h_fn_len, -// opt::Input<1>({oresult_init.optimum[2]}), -// {bound_constraints[2]}, -// {}, -// std::make_tuple(ineq_fn_len) -// ); - -// std::cout << "Iterations: " << icnt << std::endl; - -// GroundConnection conn; - -// // Extract and apply the result -//// auto &[plr, azm, bridge_l] = oresult.optimum; -// double plr = oresult_init.optimum[0]; -// double azm = oresult_init.optimum[1]; -// double bridge_l = oresult.optimum[0]; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; -// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); -// double down_l = bridge_end.z() - gndlvl; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - -// conn.path.emplace_back(source); -// if (bridge_l > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// if (ineq_fn_len(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - -// return conn; -//} - template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, @@ -1123,6 +697,10 @@ struct DefaultWideningModel { }; }; +template<> struct BeamSamples { + static constexpr size_t Value = 16; +}; + template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, From 60b59d08b9717c90cb455e11532cceba706e3b81 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:34:52 +0100 Subject: [PATCH 109/206] Disable subtree rescure when discarding subtrees It generates many abandoned single pillars --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 1ad0fd93f..4230e38bb 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -70,6 +70,22 @@ class BranchingTreeBuilder: public branchingtree::Builder { } void discard_subtree(size_t root) + { + // Discard all the support points connecting to this branch. + traverse(m_cloud, root, [this](const branchingtree::Node &node) { + int suppid_parent = m_cloud.get_leaf_id(node.id); + int suppid_left = m_cloud.get_leaf_id(node.left); + int suppid_right = m_cloud.get_leaf_id(node.right); + if (suppid_parent >= 0) + m_unroutable_pinheads.emplace_back(suppid_parent); + if (suppid_left >= 0) + m_unroutable_pinheads.emplace_back(suppid_left); + if (suppid_right >= 0) + m_unroutable_pinheads.emplace_back(suppid_right); + }); + } + + void discard_subtree_rescure(size_t root) { // Discard all the support points connecting to this branch. // As a last resort, try to route child nodes to ground and stop From dfea5e5633c6216b3241614110ff4e915063584a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:40:52 +0100 Subject: [PATCH 110/206] prepare new test data --- tests/data/disk_with_hole.obj | 1696 +++++++++++++++++++++++++++++++++ 1 file changed, 1696 insertions(+) create mode 100644 tests/data/disk_with_hole.obj diff --git a/tests/data/disk_with_hole.obj b/tests/data/disk_with_hole.obj new file mode 100644 index 000000000..f16cafb87 --- /dev/null +++ b/tests/data/disk_with_hole.obj @@ -0,0 +1,1696 @@ +#### +# +# OBJ File Generated by Meshlab +# +#### +# Object disk_with_hole.obj +# +# Vertices: 720 +# Faces: 240 +# +#### +vn -0.328454 -0.737720 0.000000 +v -25.000000 -43.301270 -5.000000 +vn -0.310446 -0.697273 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.638901 -1.434994 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.328454 -0.737720 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.310446 -0.697273 0.000000 +v -25.000000 -43.301270 -5.000000 +vn -0.638901 -1.434994 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.725904 -0.235860 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -0.768012 -0.249542 0.000000 +v -48.907379 -10.395584 5.000000 +vn -1.493916 -0.485403 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -0.725904 -0.235860 0.000000 +v -48.907379 -10.395584 5.000000 +vn -0.768012 -0.249542 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -1.493916 -0.485403 0.000000 +v -45.677273 -20.336832 5.000000 +vn 0.725904 0.235860 0.000000 +v 48.907379 10.395584 5.000000 +vn 0.768012 0.249542 0.000000 +v 45.677273 20.336832 -5.000000 +vn 1.493916 0.485403 0.000000 +v 45.677273 20.336832 5.000000 +vn 0.725904 0.235860 0.000000 +v 45.677273 20.336832 -5.000000 +vn 0.768012 0.249542 0.000000 +v 48.907379 10.395584 5.000000 +vn 1.493916 0.485403 0.000000 +v 48.907379 10.395584 -5.000000 +vn 0.759080 0.079783 0.000000 +v 50.000000 0.000000 5.000000 +vn 0.803112 0.084411 0.000000 +v 48.907379 10.395584 -5.000000 +vn 1.562191 0.164193 0.000000 +v 48.907379 10.395584 5.000000 +vn 0.759080 0.079783 0.000000 +v 48.907379 10.395584 -5.000000 +vn 0.803112 0.084411 0.000000 +v 50.000000 0.000000 5.000000 +vn 1.562191 0.164193 0.000000 +v 50.000000 0.000000 -5.000000 +vn 0.661003 0.381630 0.000000 +v 45.677273 20.336832 5.000000 +vn 0.699346 0.403768 0.000000 +v 40.450851 29.389263 -5.000000 +vn 1.360350 0.785398 0.000000 +v 40.450851 29.389263 5.000000 +vn 0.661003 0.381630 0.000000 +v 40.450851 29.389263 -5.000000 +vn 0.699346 0.403768 0.000000 +v 45.677273 20.336832 5.000000 +vn 1.360350 0.785398 0.000000 +v 45.677273 20.336832 -5.000000 +vn 0.474657 -0.653310 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.448633 -0.617491 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.923291 -1.270801 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.474657 -0.653310 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.448633 -0.617491 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.923291 -1.270801 0.000000 +v 33.456528 -37.157242 -5.000000 +vn 0.328454 0.737720 0.000000 +v 25.000000 43.301270 -5.000000 +vn 0.310446 0.697273 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.638901 1.434994 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.328454 0.737720 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.310446 0.697273 0.000000 +v 25.000000 43.301270 -5.000000 +vn 0.638901 1.434994 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.759080 -0.079783 0.000000 +v 48.907379 -10.395584 5.000000 +vn 0.803112 -0.084411 0.000000 +v 50.000000 0.000000 -5.000000 +vn 1.562191 -0.164193 0.000000 +v 50.000000 0.000000 5.000000 +vn 0.759080 -0.079783 0.000000 +v 50.000000 0.000000 -5.000000 +vn 0.803112 -0.084411 0.000000 +v 48.907379 -10.395584 5.000000 +vn 1.562191 -0.164193 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 0.567213 0.510721 0.000000 +v 40.450851 29.389263 5.000000 +vn 0.600116 0.540347 0.000000 +v 33.456528 37.157242 -5.000000 +vn 1.167329 1.051068 0.000000 +v 33.456528 37.157242 5.000000 +vn 0.567213 0.510721 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.600116 0.540347 0.000000 +v 40.450851 29.389263 5.000000 +vn 1.167329 1.051068 0.000000 +v 40.450851 29.389263 -5.000000 +vn -0.661003 0.381630 0.000000 +v -45.677273 20.336832 -5.000000 +vn -0.699346 0.403768 0.000000 +v -40.450851 29.389263 5.000000 +vn -1.360350 0.785398 0.000000 +v -40.450851 29.389263 -5.000000 +vn -0.661003 0.381630 0.000000 +v -40.450851 29.389263 5.000000 +vn -0.699346 0.403768 0.000000 +v -45.677273 20.336832 -5.000000 +vn -1.360350 0.785398 0.000000 +v -45.677273 20.336832 5.000000 +vn 0.567213 -0.510721 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.600116 -0.540347 0.000000 +v 40.450851 -29.389263 -5.000000 +vn 1.167329 -1.051068 0.000000 +v 40.450851 -29.389263 5.000000 +vn 0.567213 -0.510721 0.000000 +v 40.450851 -29.389263 -5.000000 +vn 0.600116 -0.540347 0.000000 +v 33.456528 -37.157242 5.000000 +vn 1.167329 -1.051068 0.000000 +v 33.456528 -37.157242 -5.000000 +vn -0.567213 0.510721 0.000000 +v -40.450851 29.389263 -5.000000 +vn -0.600116 0.540347 0.000000 +v -33.456528 37.157242 5.000000 +vn -1.167329 1.051068 0.000000 +v -33.456528 37.157242 -5.000000 +vn -0.567213 0.510721 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.600116 0.540347 0.000000 +v -40.450851 29.389263 -5.000000 +vn -1.167329 1.051068 0.000000 +v -40.450851 29.389263 5.000000 +vn 0.661003 -0.381630 0.000000 +v 40.450851 -29.389263 5.000000 +vn 0.699346 -0.403768 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 1.360350 -0.785398 0.000000 +v 45.677273 -20.336832 5.000000 +vn 0.661003 -0.381630 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 0.699346 -0.403768 0.000000 +v 40.450851 -29.389263 5.000000 +vn 1.360350 -0.785398 0.000000 +v 40.450851 -29.389263 -5.000000 +vn -0.474657 0.653310 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.448633 0.617491 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.923291 1.270801 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.474657 0.653310 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.448633 0.617491 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.923291 1.270801 0.000000 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 0.397030 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 0.971546 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.773017 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 0.533261 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 0.983586 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.624746 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 0.935258 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 0.079637 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 2.126698 +v 28.090170 14.122147 5.000000 +vn 0.000000 0.000000 0.710460 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.068680 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.362453 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 0.095914 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.821343 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 1.224335 +v 28.090170 14.122147 5.000000 +vn 0.000000 0.000000 0.849935 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 1.114545 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.177112 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 0.129351 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.696999 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.315244 +v 29.781475 17.920883 5.000000 +vn 0.000000 0.000000 0.109378 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 2.035788 +v 29.781475 17.920883 5.000000 +vn 0.000000 0.000000 0.996427 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 0.131250 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 2.066767 +v 29.781475 22.079117 5.000000 +vn 0.000000 0.000000 0.943575 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 0.161065 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.696263 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.000000 1.284264 +v 29.781475 22.079117 5.000000 +vn 0.000000 0.000000 0.162839 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.323985 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 1.654769 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.000000 0.163690 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.800790 +v 26.691305 27.431448 5.000000 +vn 0.000000 0.000000 1.177113 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 0.797639 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 1.263865 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.080089 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.175248 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.416103 +v 25.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.550242 +v 26.691305 27.431448 5.000000 +vn 0.000000 0.000000 0.152239 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.054425 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 1.934929 +v 25.000000 28.660254 5.000000 +vn 0.000000 0.000000 0.150263 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.492361 +v 21.045284 29.945217 5.000000 +vn 0.000000 0.000000 1.498969 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 0.137161 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.145761 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 1.858670 +v 21.045284 29.945217 5.000000 +vn 0.000000 0.000000 0.955486 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.621466 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 1.564641 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.583804 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 0.115742 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.442047 +v 16.909828 29.510565 5.000000 +vn 0.000000 0.000000 0.104548 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.128057 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.908987 +v 16.909828 29.510565 5.000000 +vn 0.000000 0.000000 0.926960 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.458257 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.756376 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.764718 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 0.086613 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.290263 +v 13.308694 27.431448 5.000000 +vn 0.000000 0.000000 2.060768 +v 13.308694 27.431448 5.000000 +vn 0.000000 0.000000 0.074549 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.006277 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 0.947726 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.349832 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 1.844034 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.938074 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.282044 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 1.921476 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.994924 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 0.062953 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.083717 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 1.054154 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.068696 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 2.018744 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 1.891912 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 0.312011 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.937670 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.099058 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 0.057667 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.984868 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.936816 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 0.258266 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 0.946511 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.099133 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 0.048750 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.993709 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 1.936892 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 0.223894 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 0.980807 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.071817 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 0.041770 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 2.028005 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 1.909575 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 0.200963 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 1.031054 +v 33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 1.901099 +v 33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 0.185196 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 1.055297 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.212075 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 0.036463 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.893055 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 0.174414 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.126786 +v 15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.840393 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 0.032941 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.798030 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.310621 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.031018 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.721914 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.388663 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 0.030639 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.500477 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.610479 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 0.031757 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.271469 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.838368 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 0.034542 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.178902 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.928151 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 0.038851 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.051199 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 2.051544 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.985271 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 0.052938 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.103383 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 0.166514 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.169712 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.805367 +v 15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 0.961197 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.239254 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 1.941141 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.161147 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.250946 +v -5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.729500 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 2.008394 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 0.045050 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.088148 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 0.157350 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.303035 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.681207 +v -5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.004346 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 0.211341 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.925906 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.154864 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.388627 +v -25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.598102 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 0.192291 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 1.888957 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.060345 +v -40.450851 29.389263 5.000000 +vn 0.000000 0.000000 0.153677 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.444390 +v -33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 1.543527 +v -25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 0.179392 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 1.871808 +v -40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.090393 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 0.153352 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.500477 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.487764 +v -33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 0.170109 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.807220 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.164264 +v -48.907379 10.395584 5.000000 +vn 0.000000 0.000000 0.154128 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.586425 +v -45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.401039 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 0.163654 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.767889 +v -48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.210049 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 0.156018 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.639846 +v -48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.345728 +v -45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 0.158937 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.690347 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.292307 +v -48.907379 -10.395584 5.000000 +vn 0.725904 -0.235860 0.000000 +v 45.677273 -20.336832 5.000000 +vn 0.768012 -0.249542 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 1.493916 -0.485403 0.000000 +v 48.907379 -10.395584 5.000000 +vn 0.725904 -0.235860 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 0.768012 -0.249542 0.000000 +v 45.677273 -20.336832 5.000000 +vn 1.493916 -0.485403 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.807535 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.763261 0.000000 +v -5.226422 49.726093 5.000000 +vn 0.000000 1.570796 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.807535 0.000000 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.763261 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.000000 1.570796 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.474657 -0.653310 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.448633 -0.617491 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.923291 -1.270801 0.000000 +v -33.456528 -37.157242 5.000000 +vn -0.474657 -0.653310 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.448633 -0.617491 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.923291 -1.270801 0.000000 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.312011 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.891912 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.937670 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.258266 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -1.936816 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.946511 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.068696 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.054154 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -2.018744 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -0.223894 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -1.936892 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.980807 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.079637 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.935258 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -2.126698 +v 28.090170 14.122147 -5.000000 +vn 0.000000 0.000000 -0.200963 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.909575 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.031054 +v 33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -2.035788 +v 29.781475 17.920883 -5.000000 +vn 0.000000 0.000000 -0.109378 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.996427 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -1.068680 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.710460 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.362453 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -2.066767 +v 29.781475 22.079117 -5.000000 +vn 0.000000 0.000000 -0.131250 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.943575 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.696999 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -0.129351 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.315244 +v 29.781475 17.920883 -5.000000 +vn 0.000000 0.000000 -0.983586 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.533261 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -1.624746 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.821343 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -0.095914 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -1.224335 +v 28.090170 14.122147 -5.000000 +vn 0.000000 0.000000 -0.185196 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.901099 +v 33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -1.055297 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.971546 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.397030 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -1.773017 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.057667 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -1.099058 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -1.984868 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -0.048750 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -1.099133 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -1.993709 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -0.041770 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.071817 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -2.028005 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -0.036463 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.212075 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.893055 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.126786 +v 15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -0.174414 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.840393 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.169712 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -0.166514 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.805367 +v 15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.798030 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -0.032941 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.310621 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.250946 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -0.161147 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.729500 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.303035 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -0.157350 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.681207 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.721914 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -0.031018 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.388663 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.388627 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.154864 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.598102 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.444390 +v -33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -0.153677 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.543527 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.696263 +v 29.135454 24.067366 -5.000000 +vn 0.000000 0.000000 -0.161065 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.284264 +v 29.781475 22.079117 -5.000000 +vn 0.000000 0.000000 -1.323985 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -0.162839 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.654769 +v 29.135454 24.067366 -5.000000 +vn 0.000000 0.000000 -1.114545 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.849935 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -1.177112 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.800790 +v 26.691305 27.431448 -5.000000 +vn 0.000000 0.000000 -0.163690 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.177113 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -1.416103 +v 25.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.175248 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.550242 +v 26.691305 27.431448 -5.000000 +vn 0.000000 0.000000 -1.054425 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -0.152239 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.934929 +v 25.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -1.263865 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -0.797639 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -1.080089 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.492361 +v 21.045284 29.945217 -5.000000 +vn 0.000000 0.000000 -0.150263 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.498969 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -1.145761 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -0.137161 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.858670 +v 21.045284 29.945217 -5.000000 +vn 0.000000 0.000000 -0.115742 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.583804 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -1.442047 +v 16.909828 29.510565 -5.000000 +vn 0.000000 0.000000 -0.621466 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -0.955486 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.564641 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.128057 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.104548 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.908987 +v 16.909828 29.510565 -5.000000 +vn 0.000000 0.000000 -0.086613 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.764718 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -1.290263 +v 13.308694 27.431448 -5.000000 +vn 0.000000 0.000000 -0.074549 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -2.060768 +v 13.308694 27.431448 -5.000000 +vn 0.000000 0.000000 -1.006277 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -0.062953 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.994924 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -1.083717 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -0.052938 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.985271 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -1.103383 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -0.458257 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.926960 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.756376 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -0.045050 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -2.008394 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -1.088148 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.500477 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.153352 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.487764 +v -33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -1.500477 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -0.030639 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.610479 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -0.349832 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -0.947726 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.844034 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.586425 +v -45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.154128 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.401039 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.282044 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -0.938074 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.921476 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.639846 +v -48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.156018 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.345728 +v -45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.239254 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -0.961197 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.941141 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.690347 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.158937 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.292307 +v -48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.211341 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.004346 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.925906 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.271469 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -0.031757 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.838368 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.051199 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -0.038851 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -2.051544 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.767889 +v -48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.163654 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.210049 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.888957 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -0.192291 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -1.060345 +v -40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.807220 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.170109 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.164264 +v -48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -1.871808 +v -40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.179392 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -1.090393 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.178902 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -0.034542 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.928151 +v 10.218524 17.920883 -5.000000 +vn -0.328454 0.737720 0.000000 +v -15.450850 47.552826 -5.000000 +vn -0.310446 0.697273 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.638901 1.434994 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.328454 0.737720 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.310446 0.697273 0.000000 +v -15.450850 47.552826 -5.000000 +vn -0.638901 1.434994 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.759080 -0.079783 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -0.803112 -0.084411 0.000000 +v -50.000000 0.000000 5.000000 +vn -1.562191 -0.164193 0.000000 +v -50.000000 0.000000 -5.000000 +vn -0.759080 -0.079783 0.000000 +v -50.000000 0.000000 5.000000 +vn -0.803112 -0.084411 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -1.562191 -0.164193 0.000000 +v -48.907379 -10.395584 5.000000 +vn -0.567213 -0.510721 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.600116 -0.540347 0.000000 +v -40.450851 -29.389263 5.000000 +vn -1.167329 -1.051068 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -0.567213 -0.510721 0.000000 +v -40.450851 -29.389263 5.000000 +vn -0.600116 -0.540347 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -1.167329 -1.051068 0.000000 +v -33.456528 -37.157242 5.000000 +vn -0.661003 -0.381630 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -0.699346 -0.403768 0.000000 +v -45.677273 -20.336832 5.000000 +vn -1.360350 -0.785398 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -0.661003 -0.381630 0.000000 +v -45.677273 -20.336832 5.000000 +vn -0.699346 -0.403768 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -1.360350 -0.785398 0.000000 +v -40.450851 -29.389263 5.000000 +vn 0.167896 -0.789889 0.000000 +v 5.226422 -49.726093 -5.000000 +vn 0.158691 -0.746582 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.326587 -1.536471 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.167896 -0.789889 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.158691 -0.746582 0.000000 +v 5.226422 -49.726093 -5.000000 +vn 0.326587 -1.536471 0.000000 +v 15.450850 -47.552826 -5.000000 +vn -0.759080 0.079783 0.000000 +v -50.000000 0.000000 -5.000000 +vn -0.803112 0.084411 0.000000 +v -48.907379 10.395584 5.000000 +vn -1.562191 0.164193 0.000000 +v -48.907379 10.395584 -5.000000 +vn -0.759080 0.079783 0.000000 +v -48.907379 10.395584 5.000000 +vn -0.803112 0.084411 0.000000 +v -50.000000 0.000000 -5.000000 +vn -1.562191 0.164193 0.000000 +v -50.000000 0.000000 5.000000 +vn 0.167896 0.789889 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.158691 0.746582 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.326587 1.536471 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.167896 0.789889 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.158691 0.746582 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.326587 1.536471 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.474657 0.653310 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.448633 0.617491 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.923291 1.270801 0.000000 +v 33.456528 37.157242 5.000000 +vn 0.474657 0.653310 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.448633 0.617491 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.923291 1.270801 0.000000 +v 25.000000 43.301270 -5.000000 +vn -0.167896 0.789889 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.158691 0.746582 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.326587 1.536471 0.000000 +v -5.226422 49.726093 5.000000 +vn -0.167896 0.789889 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.158691 0.746582 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.326587 1.536471 0.000000 +v -15.450850 47.552826 -5.000000 +vn 0.328454 -0.737720 0.000000 +v 15.450850 -47.552826 -5.000000 +vn 0.310446 -0.697273 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.638901 -1.434994 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.328454 -0.737720 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.310446 -0.697273 0.000000 +v 15.450850 -47.552826 -5.000000 +vn 0.638901 -1.434994 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 -0.807535 0.000000 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 -0.763261 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.000000 -1.570796 0.000000 +v -5.226422 -49.726093 5.000000 +vn 0.000000 -0.807535 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.000000 -0.763261 0.000000 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 -1.570796 0.000000 +v 5.226422 -49.726093 -5.000000 +vn -0.167896 -0.789889 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.158691 -0.746582 0.000000 +v -5.226422 -49.726093 5.000000 +vn -0.326587 -1.536471 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.167896 -0.789889 0.000000 +v -5.226422 -49.726093 5.000000 +vn -0.158691 -0.746582 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.326587 -1.536471 0.000000 +v -5.226422 -49.726093 -5.000000 +vn -0.725904 0.235860 0.000000 +v -48.907379 10.395584 -5.000000 +vn -0.768012 0.249542 0.000000 +v -45.677273 20.336832 5.000000 +vn -1.493916 0.485403 0.000000 +v -45.677273 20.336832 -5.000000 +vn -0.725904 0.235860 0.000000 +v -45.677273 20.336832 5.000000 +vn -0.768012 0.249542 0.000000 +v -48.907379 10.395584 -5.000000 +vn -1.493916 0.485403 0.000000 +v -48.907379 10.395584 5.000000 +vn -1.297914 -0.421718 0.000000 +v 29.781475 22.079117 -5.000000 +vn -0.196002 -0.063685 0.000000 +v 29.135454 24.067366 5.000000 +vn -1.493916 -0.485403 0.000000 +v 29.135454 24.067366 -5.000000 +vn -1.297914 -0.421718 0.000000 +v 29.135454 24.067366 5.000000 +vn -0.196002 -0.063685 0.000000 +v 29.781475 22.079117 -5.000000 +vn -1.493916 -0.485403 0.000000 +v 29.781475 22.079117 5.000000 +vn -1.357231 -0.142651 0.000000 +v 30.000000 20.000000 -5.000000 +vn -0.204960 -0.021542 0.000000 +v 29.781475 22.079117 5.000000 +vn -1.562191 -0.164194 0.000000 +v 29.781475 22.079117 -5.000000 +vn -1.357231 -0.142651 0.000000 +v 29.781475 22.079117 5.000000 +vn -0.204960 -0.021542 0.000000 +v 30.000000 20.000000 -5.000000 +vn -1.562191 -0.164194 0.000000 +v 30.000000 20.000000 5.000000 +vn -1.181872 -0.682353 0.000000 +v 29.135454 24.067366 -5.000000 +vn -0.178478 -0.103044 0.000000 +v 28.090170 25.877853 5.000000 +vn -1.360350 -0.785397 0.000000 +v 28.090170 25.877853 -5.000000 +vn -1.181872 -0.682353 0.000000 +v 28.090170 25.877853 5.000000 +vn -0.178478 -0.103044 0.000000 +v 29.135454 24.067366 -5.000000 +vn -1.360350 -0.785397 0.000000 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.206089 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.000000 1.364708 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.000000 1.570796 0.000000 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.206089 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.000000 1.364708 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.000000 1.570796 0.000000 +v 18.954716 10.054781 -5.000000 +vn -0.083824 0.188272 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.555077 1.246722 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.638901 1.434994 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.083824 0.188272 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.555077 1.246722 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.638901 1.434994 0.000000 +v 23.090170 10.489434 -5.000000 +vn 1.297914 0.421717 0.000000 +v 10.864545 15.932633 5.000000 +vn 0.196002 0.063685 0.000000 +v 10.218524 17.920883 -5.000000 +vn 1.493916 0.485402 0.000000 +v 10.218524 17.920883 5.000000 +vn 1.297914 0.421717 0.000000 +v 10.218524 17.920883 -5.000000 +vn 0.196002 0.063685 0.000000 +v 10.864545 15.932633 5.000000 +vn 1.493916 0.485402 0.000000 +v 10.864545 15.932633 -5.000000 +vn -1.357231 0.142651 0.000000 +v 29.781475 17.920883 -5.000000 +vn -0.204960 0.021542 0.000000 +v 30.000000 20.000000 5.000000 +vn -1.562191 0.164194 0.000000 +v 30.000000 20.000000 -5.000000 +vn -1.357231 0.142651 0.000000 +v 30.000000 20.000000 5.000000 +vn -0.204960 0.021542 0.000000 +v 29.781475 17.920883 -5.000000 +vn -1.562191 0.164194 0.000000 +v 29.781475 17.920883 5.000000 +vn 1.014175 -0.913168 0.000000 +v 11.909829 25.877853 5.000000 +vn 0.153154 -0.137900 0.000000 +v 13.308694 27.431448 -5.000000 +vn 1.167328 -1.051069 0.000000 +v 13.308694 27.431448 5.000000 +vn 1.014175 -0.913168 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.153154 -0.137900 0.000000 +v 11.909829 25.877853 5.000000 +vn 1.167328 -1.051069 0.000000 +v 11.909829 25.877853 -5.000000 +vn 0.042848 0.201585 0.000000 +v 18.954716 10.054781 -5.000000 +vn 0.283738 1.334885 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.326586 1.536471 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.042848 0.201585 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.283738 1.334885 0.000000 +v 18.954716 10.054781 -5.000000 +vn 0.326586 1.536471 0.000000 +v 16.909828 10.489434 -5.000000 +vn -1.297914 0.421717 0.000000 +v 29.135454 15.932633 -5.000000 +vn -0.196002 0.063685 0.000000 +v 29.781475 17.920883 5.000000 +vn -1.493916 0.485402 0.000000 +v 29.781475 17.920883 -5.000000 +vn -1.297914 0.421717 0.000000 +v 29.781475 17.920883 5.000000 +vn -0.196002 0.063685 0.000000 +v 29.135454 15.932633 -5.000000 +vn -1.493916 0.485402 0.000000 +v 29.135454 15.932633 5.000000 +vn 0.083824 0.188271 0.000000 +v 16.909828 10.489434 -5.000000 +vn 0.555077 1.246722 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.638901 1.434994 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.083824 0.188271 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.555077 1.246722 0.000000 +v 16.909828 10.489434 -5.000000 +vn 0.638901 1.434994 0.000000 +v 15.000000 11.339746 -5.000000 +vn -0.042848 -0.201585 0.000000 +v 21.045284 29.945217 -5.000000 +vn -0.283738 -1.334886 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.326586 -1.536471 0.000000 +v 21.045284 29.945217 5.000000 +vn -0.042848 -0.201585 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.283738 -1.334886 0.000000 +v 21.045284 29.945217 -5.000000 +vn -0.326586 -1.536471 0.000000 +v 23.090170 29.510565 -5.000000 +vn 0.000000 -0.206089 0.000000 +v 18.954716 29.945217 -5.000000 +vn 0.000000 -1.364708 0.000000 +v 21.045284 29.945217 5.000000 +vn 0.000000 -1.570796 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.000000 -0.206089 0.000000 +v 21.045284 29.945217 5.000000 +vn 0.000000 -1.364708 0.000000 +v 18.954716 29.945217 -5.000000 +vn 0.000000 -1.570796 0.000000 +v 21.045284 29.945217 -5.000000 +vn -1.181872 0.682353 0.000000 +v 28.090170 14.122147 -5.000000 +vn -0.178478 0.103044 0.000000 +v 29.135454 15.932633 5.000000 +vn -1.360350 0.785398 0.000000 +v 29.135454 15.932633 -5.000000 +vn -1.181872 0.682353 0.000000 +v 29.135454 15.932633 5.000000 +vn -0.178478 0.103044 0.000000 +v 28.090170 14.122147 -5.000000 +vn -1.360350 0.785398 0.000000 +v 28.090170 14.122147 5.000000 +vn 1.297914 -0.421718 0.000000 +v 10.218524 22.079117 5.000000 +vn 0.196002 -0.063685 0.000000 +v 10.864545 24.067366 -5.000000 +vn 1.493916 -0.485403 0.000000 +v 10.864545 24.067366 5.000000 +vn 1.297914 -0.421718 0.000000 +v 10.864545 24.067366 -5.000000 +vn 0.196002 -0.063685 0.000000 +v 10.218524 22.079117 5.000000 +vn 1.493916 -0.485403 0.000000 +v 10.218524 22.079117 -5.000000 +vn 1.014175 0.913168 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.153154 0.137900 0.000000 +v 11.909829 14.122147 -5.000000 +vn 1.167329 1.051068 0.000000 +v 11.909829 14.122147 5.000000 +vn 1.014175 0.913168 0.000000 +v 11.909829 14.122147 -5.000000 +vn 0.153154 0.137900 0.000000 +v 13.308694 12.568551 5.000000 +vn 1.167329 1.051068 0.000000 +v 13.308694 12.568551 -5.000000 +vn -1.014175 -0.913168 0.000000 +v 28.090170 25.877853 -5.000000 +vn -0.153154 -0.137900 0.000000 +v 26.691305 27.431448 5.000000 +vn -1.167328 -1.051069 0.000000 +v 26.691305 27.431448 -5.000000 +vn -1.014175 -0.913168 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.153154 -0.137900 0.000000 +v 28.090170 25.877853 -5.000000 +vn -1.167328 -1.051069 0.000000 +v 28.090170 25.877853 5.000000 +vn 1.357232 -0.142651 0.000000 +v 10.000000 20.000000 5.000000 +vn 0.204960 -0.021542 0.000000 +v 10.218524 22.079117 -5.000000 +vn 1.562191 -0.164193 0.000000 +v 10.218524 22.079117 5.000000 +vn 1.357232 -0.142651 0.000000 +v 10.218524 22.079117 -5.000000 +vn 0.204960 -0.021542 0.000000 +v 10.000000 20.000000 5.000000 +vn 1.562191 -0.164193 0.000000 +v 10.000000 20.000000 -5.000000 +vn 0.042848 -0.201585 0.000000 +v 16.909828 29.510565 -5.000000 +vn 0.283737 -1.334885 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.326586 -1.536471 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.042848 -0.201585 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.283737 -1.334885 0.000000 +v 16.909828 29.510565 -5.000000 +vn 0.326586 -1.536471 0.000000 +v 18.954716 29.945217 -5.000000 +vn -0.121136 -0.166729 0.000000 +v 25.000000 28.660254 -5.000000 +vn -0.802155 -1.104072 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.923291 -1.270801 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.121136 -0.166729 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.802155 -1.104072 0.000000 +v 25.000000 28.660254 -5.000000 +vn -0.923291 -1.270801 0.000000 +v 26.691305 27.431448 -5.000000 +vn 0.083824 -0.188271 0.000000 +v 15.000000 28.660254 -5.000000 +vn 0.555077 -1.246722 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.638901 -1.434994 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.083824 -0.188271 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.555077 -1.246722 0.000000 +v 15.000000 28.660254 -5.000000 +vn 0.638901 -1.434994 0.000000 +v 16.909828 29.510565 -5.000000 +vn -1.014175 0.913168 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.153154 0.137900 0.000000 +v 28.090170 14.122147 5.000000 +vn -1.167329 1.051068 0.000000 +v 28.090170 14.122147 -5.000000 +vn -1.014175 0.913168 0.000000 +v 28.090170 14.122147 5.000000 +vn -0.153154 0.137900 0.000000 +v 26.691305 12.568551 -5.000000 +vn -1.167329 1.051068 0.000000 +v 26.691305 12.568551 5.000000 +vn -0.083824 -0.188272 0.000000 +v 23.090170 29.510565 -5.000000 +vn -0.555077 -1.246722 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.638901 -1.434994 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.083824 -0.188272 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.555077 -1.246722 0.000000 +v 23.090170 29.510565 -5.000000 +vn -0.638901 -1.434994 0.000000 +v 25.000000 28.660254 -5.000000 +vn 0.121136 0.166729 0.000000 +v 15.000000 11.339746 -5.000000 +vn 0.802155 1.104072 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.923291 1.270801 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.121136 0.166729 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.802155 1.104072 0.000000 +v 15.000000 11.339746 -5.000000 +vn 0.923291 1.270801 0.000000 +v 13.308694 12.568551 -5.000000 +vn -0.121136 0.166729 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.802155 1.104072 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.923291 1.270801 0.000000 +v 26.691305 12.568551 5.000000 +vn -0.121136 0.166729 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.802155 1.104072 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.923291 1.270801 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.042848 0.201585 0.000000 +v 23.090170 10.489434 -5.000000 +vn -0.283738 1.334885 0.000000 +v 21.045284 10.054781 5.000000 +vn -0.326587 1.536471 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.042848 0.201585 0.000000 +v 21.045284 10.054781 5.000000 +vn -0.283738 1.334885 0.000000 +v 23.090170 10.489434 -5.000000 +vn -0.326587 1.536471 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.121136 -0.166729 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.802155 -1.104072 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.923291 -1.270801 0.000000 +v 13.308694 27.431448 5.000000 +vn 0.121136 -0.166729 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.802155 -1.104072 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.923291 -1.270801 0.000000 +v 15.000000 28.660254 -5.000000 +vn 1.181872 0.682353 0.000000 +v 11.909829 14.122147 5.000000 +vn 0.178478 0.103044 0.000000 +v 10.864545 15.932633 -5.000000 +vn 1.360350 0.785398 0.000000 +v 10.864545 15.932633 5.000000 +vn 1.181872 0.682353 0.000000 +v 10.864545 15.932633 -5.000000 +vn 0.178478 0.103044 0.000000 +v 11.909829 14.122147 5.000000 +vn 1.360350 0.785398 0.000000 +v 11.909829 14.122147 -5.000000 +vn 1.357232 0.142651 0.000000 +v 10.218524 17.920883 5.000000 +vn 0.204960 0.021542 0.000000 +v 10.000000 20.000000 -5.000000 +vn 1.562191 0.164193 0.000000 +v 10.000000 20.000000 5.000000 +vn 1.357232 0.142651 0.000000 +v 10.000000 20.000000 -5.000000 +vn 0.204960 0.021542 0.000000 +v 10.218524 17.920883 5.000000 +vn 1.562191 0.164193 0.000000 +v 10.218524 17.920883 -5.000000 +vn 1.181872 -0.682353 0.000000 +v 10.864545 24.067366 5.000000 +vn 0.178478 -0.103044 0.000000 +v 11.909829 25.877853 -5.000000 +vn 1.360350 -0.785397 0.000000 +v 11.909829 25.877853 5.000000 +vn 1.181872 -0.682353 0.000000 +v 11.909829 25.877853 -5.000000 +vn 0.178478 -0.103044 0.000000 +v 10.864545 24.067366 5.000000 +vn 1.360350 -0.785397 0.000000 +v 10.864545 24.067366 -5.000000 +# 720 vertices, 0 vertices normals + +f 1//1 2//2 3//3 +f 4//4 5//5 6//6 +f 7//7 8//8 9//9 +f 10//10 11//11 12//12 +f 13//13 14//14 15//15 +f 16//16 17//17 18//18 +f 19//19 20//20 21//21 +f 22//22 23//23 24//24 +f 25//25 26//26 27//27 +f 28//28 29//29 30//30 +f 31//31 32//32 33//33 +f 34//34 35//35 36//36 +f 37//37 38//38 39//39 +f 40//40 41//41 42//42 +f 43//43 44//44 45//45 +f 46//46 47//47 48//48 +f 49//49 50//50 51//51 +f 52//52 53//53 54//54 +f 55//55 56//56 57//57 +f 58//58 59//59 60//60 +f 61//61 62//62 63//63 +f 64//64 65//65 66//66 +f 67//67 68//68 69//69 +f 70//70 71//71 72//72 +f 73//73 74//74 75//75 +f 76//76 77//77 78//78 +f 79//79 80//80 81//81 +f 82//82 83//83 84//84 +f 85//85 86//86 87//87 +f 88//88 89//89 90//90 +f 91//91 92//92 93//93 +f 94//94 95//95 96//96 +f 97//97 98//98 99//99 +f 100//100 101//101 102//102 +f 103//103 104//104 105//105 +f 106//106 107//107 108//108 +f 109//109 110//110 111//111 +f 112//112 113//113 114//114 +f 115//115 116//116 117//117 +f 118//118 119//119 120//120 +f 121//121 122//122 123//123 +f 124//124 125//125 126//126 +f 127//127 128//128 129//129 +f 130//130 131//131 132//132 +f 133//133 134//134 135//135 +f 136//136 137//137 138//138 +f 139//139 140//140 141//141 +f 142//142 143//143 144//144 +f 145//145 146//146 147//147 +f 148//148 149//149 150//150 +f 151//151 152//152 153//153 +f 154//154 155//155 156//156 +f 157//157 158//158 159//159 +f 160//160 161//161 162//162 +f 163//163 164//164 165//165 +f 166//166 167//167 168//168 +f 169//169 170//170 171//171 +f 172//172 173//173 174//174 +f 175//175 176//176 177//177 +f 178//178 179//179 180//180 +f 181//181 182//182 183//183 +f 184//184 185//185 186//186 +f 187//187 188//188 189//189 +f 190//190 191//191 192//192 +f 193//193 194//194 195//195 +f 196//196 197//197 198//198 +f 199//199 200//200 201//201 +f 202//202 203//203 204//204 +f 205//205 206//206 207//207 +f 208//208 209//209 210//210 +f 211//211 212//212 213//213 +f 214//214 215//215 216//216 +f 217//217 218//218 219//219 +f 220//220 221//221 222//222 +f 223//223 224//224 225//225 +f 226//226 227//227 228//228 +f 229//229 230//230 231//231 +f 232//232 233//233 234//234 +f 235//235 236//236 237//237 +f 238//238 239//239 240//240 +f 241//241 242//242 243//243 +f 244//244 245//245 246//246 +f 247//247 248//248 249//249 +f 250//250 251//251 252//252 +f 253//253 254//254 255//255 +f 256//256 257//257 258//258 +f 259//259 260//260 261//261 +f 262//262 263//263 264//264 +f 265//265 266//266 267//267 +f 268//268 269//269 270//270 +f 271//271 272//272 273//273 +f 274//274 275//275 276//276 +f 277//277 278//278 279//279 +f 280//280 281//281 282//282 +f 283//283 284//284 285//285 +f 286//286 287//287 288//288 +f 289//289 290//290 291//291 +f 292//292 293//293 294//294 +f 295//295 296//296 297//297 +f 298//298 299//299 300//300 +f 301//301 302//302 303//303 +f 304//304 305//305 306//306 +f 307//307 308//308 309//309 +f 310//310 311//311 312//312 +f 313//313 314//314 315//315 +f 316//316 317//317 318//318 +f 319//319 320//320 321//321 +f 322//322 323//323 324//324 +f 325//325 326//326 327//327 +f 328//328 329//329 330//330 +f 331//331 332//332 333//333 +f 334//334 335//335 336//336 +f 337//337 338//338 339//339 +f 340//340 341//341 342//342 +f 343//343 344//344 345//345 +f 346//346 347//347 348//348 +f 349//349 350//350 351//351 +f 352//352 353//353 354//354 +f 355//355 356//356 357//357 +f 358//358 359//359 360//360 +f 361//361 362//362 363//363 +f 364//364 365//365 366//366 +f 367//367 368//368 369//369 +f 370//370 371//371 372//372 +f 373//373 374//374 375//375 +f 376//376 377//377 378//378 +f 379//379 380//380 381//381 +f 382//382 383//383 384//384 +f 385//385 386//386 387//387 +f 388//388 389//389 390//390 +f 391//391 392//392 393//393 +f 394//394 395//395 396//396 +f 397//397 398//398 399//399 +f 400//400 401//401 402//402 +f 403//403 404//404 405//405 +f 406//406 407//407 408//408 +f 409//409 410//410 411//411 +f 412//412 413//413 414//414 +f 415//415 416//416 417//417 +f 418//418 419//419 420//420 +f 421//421 422//422 423//423 +f 424//424 425//425 426//426 +f 427//427 428//428 429//429 +f 430//430 431//431 432//432 +f 433//433 434//434 435//435 +f 436//436 437//437 438//438 +f 439//439 440//440 441//441 +f 442//442 443//443 444//444 +f 445//445 446//446 447//447 +f 448//448 449//449 450//450 +f 451//451 452//452 453//453 +f 454//454 455//455 456//456 +f 457//457 458//458 459//459 +f 460//460 461//461 462//462 +f 463//463 464//464 465//465 +f 466//466 467//467 468//468 +f 469//469 470//470 471//471 +f 472//472 473//473 474//474 +f 475//475 476//476 477//477 +f 478//478 479//479 480//480 +f 481//481 482//482 483//483 +f 484//484 485//485 486//486 +f 487//487 488//488 489//489 +f 490//490 491//491 492//492 +f 493//493 494//494 495//495 +f 496//496 497//497 498//498 +f 499//499 500//500 501//501 +f 502//502 503//503 504//504 +f 505//505 506//506 507//507 +f 508//508 509//509 510//510 +f 511//511 512//512 513//513 +f 514//514 515//515 516//516 +f 517//517 518//518 519//519 +f 520//520 521//521 522//522 +f 523//523 524//524 525//525 +f 526//526 527//527 528//528 +f 529//529 530//530 531//531 +f 532//532 533//533 534//534 +f 535//535 536//536 537//537 +f 538//538 539//539 540//540 +f 541//541 542//542 543//543 +f 544//544 545//545 546//546 +f 547//547 548//548 549//549 +f 550//550 551//551 552//552 +f 553//553 554//554 555//555 +f 556//556 557//557 558//558 +f 559//559 560//560 561//561 +f 562//562 563//563 564//564 +f 565//565 566//566 567//567 +f 568//568 569//569 570//570 +f 571//571 572//572 573//573 +f 574//574 575//575 576//576 +f 577//577 578//578 579//579 +f 580//580 581//581 582//582 +f 583//583 584//584 585//585 +f 586//586 587//587 588//588 +f 589//589 590//590 591//591 +f 592//592 593//593 594//594 +f 595//595 596//596 597//597 +f 598//598 599//599 600//600 +f 601//601 602//602 603//603 +f 604//604 605//605 606//606 +f 607//607 608//608 609//609 +f 610//610 611//611 612//612 +f 613//613 614//614 615//615 +f 616//616 617//617 618//618 +f 619//619 620//620 621//621 +f 622//622 623//623 624//624 +f 625//625 626//626 627//627 +f 628//628 629//629 630//630 +f 631//631 632//632 633//633 +f 634//634 635//635 636//636 +f 637//637 638//638 639//639 +f 640//640 641//641 642//642 +f 643//643 644//644 645//645 +f 646//646 647//647 648//648 +f 649//649 650//650 651//651 +f 652//652 653//653 654//654 +f 655//655 656//656 657//657 +f 658//658 659//659 660//660 +f 661//661 662//662 663//663 +f 664//664 665//665 666//666 +f 667//667 668//668 669//669 +f 670//670 671//671 672//672 +f 673//673 674//674 675//675 +f 676//676 677//677 678//678 +f 679//679 680//680 681//681 +f 682//682 683//683 684//684 +f 685//685 686//686 687//687 +f 688//688 689//689 690//690 +f 691//691 692//692 693//693 +f 694//694 695//695 696//696 +f 697//697 698//698 699//699 +f 700//700 701//701 702//702 +f 703//703 704//704 705//705 +f 706//706 707//707 708//708 +f 709//709 710//710 711//711 +f 712//712 713//713 714//714 +f 715//715 716//716 717//717 +f 718//718 719//719 720//720 +# 240 faces, 0 coords texture + +# End of File From 44bc8d8f5fb05f2d6e82aa5d098e5a73ca55c093 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 10:52:08 +0100 Subject: [PATCH 111/206] Small refactor and comments --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 84 +++++++++++++++-------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index f360c6753..9e423ff91 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -392,7 +392,7 @@ using AlgNLoptORIG_DIRECT = detail::NLoptAlg; using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptAGS = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Subplx = detail::NLoptAlgComb; using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 13586d937..78e9afb88 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -136,8 +136,11 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface using Beam = Beam_<8>; -template -Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) +template +Hit beam_mesh_hit(Ex policy, + const AABBMesh &mesh, + const Beam_ &beam, + double sd) { Vec3d src = beam.src; Vec3d dst = src + beam.dir; @@ -146,15 +149,15 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) Vec3d D = (dst - src); Vec3d dir = D.normalized(); - PointRing ring{dir}; + PointRing ring{dir}; using Hit = AABBMesh::hit_result; // Hit results - std::array hits; + std::array hits; execution::for_each( - ex, size_t(0), hits.size(), + policy, size_t(0), hits.size(), [&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) { Hit &hit = hits[i]; @@ -175,7 +178,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) } } else hit = hr; - }, std::min(execution::max_concurrency(ex), S)); + }, std::min(execution::max_concurrency(policy), RayCount)); return min_hit(hits.begin(), hits.end()); } @@ -359,7 +362,7 @@ bool optimize_pinhead_placement(Ex policy, // viable normal that doesn't collide with the model // geometry and its very close to the default. - Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); + Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( @@ -483,21 +486,27 @@ constexpr bool IsWideningFn = std::is_invocable_r_v; +// A widening function can determine how many ray samples should a beam contain +// (see in beam_mesh_hit) template struct BeamSamples { static constexpr size_t Value = 8; }; template constexpr size_t BeamSamplesV = BeamSamples>::Value; - +// To use with check_ground_route, full will check the bridge and the pillar, +// PillarOnly checks only the pillar for collisions. enum class GroundRouteCheck { Full, PillarOnly }; +// Returns the collision point with mesh if there is a collision or a ground point, +// given a source point with a direction of a potential avoidance bridge and +// a bridge length. template> > Vec3d check_ground_route( Ex policy, const SupportableMesh &sm, - const Junction &source, - const Vec3d &dir, - double bridge_len, - WideningFn &&wideningfn, + const Junction &source, // source location + const Vec3d &dir, // direction of the bridge from the source + double bridge_len, // lenght of the avoidance bridge + WideningFn &&wideningfn, // Widening strategy GroundRouteCheck type = GroundRouteCheck::Full ) { @@ -556,6 +565,9 @@ Vec3d check_ground_route( return ret; } +// Searching a ground connection from an arbitrary source point. +// Currently, the result will contain one avoidance bridge (at most) and a +// pillar to the ground, if it's feasible template> > GroundConnection deepsearch_ground_connection( @@ -565,26 +577,35 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { + constexpr unsigned MaxIterationsGlobal = 5000; + constexpr unsigned MaxIterationsLocal = 100; + constexpr double RelScoreDiff = 0.05; + const auto gndlvl = ground_level(sm); - auto criteria = get_criteria(sm.cfg); - criteria.max_iterations(5000); + // The used solver (AlgNLoptMLSL_Subplx search method) is composed of a global (MLSL) + // and a local (Subplex) search method. Criteria can be set in a way that + // local searches are quick and less accurate. The global method will only + // consider the max iteration number and the stop score (Z level <= ground) + + auto criteria = get_criteria(sm.cfg); // get defaults from cfg + criteria.max_iterations(MaxIterationsGlobal); criteria.abs_score_diff(NaNd); criteria.rel_score_diff(NaNd); criteria.stop_score(gndlvl); auto criteria_loc = criteria; - criteria_loc.max_iterations(100); + criteria_loc.max_iterations(MaxIterationsLocal); criteria_loc.abs_score_diff(EPSILON); - criteria_loc.rel_score_diff(0.05); + criteria_loc.rel_score_diff(RelScoreDiff); - Optimizer solver(criteria); + Optimizer solver(criteria); solver.set_loc_criteria(criteria_loc); - solver.seed(0); + solver.seed(0); // require repeatability // functor returns the z height of collision point, given a polar and // azimuth angles as bridge direction and bridge length. The route is - // traced from source, throught this bridge and an attached pillar. If there + // traced from source, through this bridge and an attached pillar. If there // is a collision with the mesh, the Z height is returned. Otherwise the // z level of ground is returned. auto z_fn = [&](const opt::Input<3> &input) { @@ -598,20 +619,22 @@ GroundConnection deepsearch_ground_connection( return hitpt.z(); }; + // Calculate the initial direction of the search by + // saturating the polar angle to max tilt defined in config auto [plr_init, azm_init] = dir_to_spheric(init_dir); - - // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + auto bound_constraints = - bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + bounds({ + {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} // bounds bridge length + }); // The optimizer can navigate fairly well on the mesh surface, finding // lower and lower Z coordinates as collision points. MLSL is not a local // search method, so it should not be trapped in a local minima. Eventually, - // this search should arrive at a ground location, like water flows down a - // surface. + // this search should arrive at a ground location. auto oresult = solver.to_min().optimize( z_fn, initvals({plr_init, azm_init, 0.}), @@ -628,7 +651,9 @@ GroundConnection deepsearch_ground_connection( // and length. This length can be shortened further by brute-force queries // of free route straigt down for a possible pillar. // NOTE: This requirement could be incorporated into the optimization as a - // constraint, but it would not find quickly enough an accurate solution. + // constraint, but it would not find quickly enough an accurate solution, + // and it would be very hard to define a stop score which is very useful in + // terminating the search as soon as the ground is found. double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { @@ -650,10 +675,14 @@ GroundConnection deepsearch_ground_connection( double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + // Even if the search was not succesful, the result is populated by the + // source and the last best result of the optimization. conn.path.emplace_back(source); if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); + // The resulting ground connection is only valid if the pillar base is set. + // At this point it will only be set if the search was succesful. if (z_fn(opt::Input<3>({plr, azm, bridge_l})) <= gndlvl) conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; @@ -661,6 +690,7 @@ GroundConnection deepsearch_ground_connection( return conn; } +// Ground route search with a predefined end radius template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, From 7eb5ca7396ada2122f4a674167c3385a5eba1ab5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 12:06:16 +0100 Subject: [PATCH 112/206] Log support tree creation time --- src/libslic3r/SLA/SupportTree.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 37c2e85e9..cfafdf7e9 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -18,6 +18,8 @@ #include #include +#include + //! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -30,6 +32,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, auto builder = make_unique(ctl); if (sm.cfg.enabled) { + Benchmark bench; + bench.start(); + switch (sm.cfg.tree_type) { case SupportTreeType::Default: { create_default_tree(*builder, sm); @@ -45,6 +50,12 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, default:; } + bench.stop(); + + BOOST_LOG_TRIVIAL(info) << "Support tree creation took: " + << bench.getElapsedSec() + << " seconds"; + builder->merge_and_cleanup(); // clean metadata, leave only the meshes. } From ebb8f9bc80b4b1f8915ad7014146a1aab4eb8bfa Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 12:06:37 +0100 Subject: [PATCH 113/206] Disable parallel beam hits in branching tree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 4230e38bb..5c7e751fa 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -13,6 +13,8 @@ namespace Slic3r { namespace sla { +inline constexpr const auto &beam_ex_policy = ex_tbb; + class BranchingTreeBuilder: public branchingtree::Builder { SupportTreeBuilder &m_builder; const SupportableMesh &m_sm; @@ -178,7 +180,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, Vec3d fromd = from.pos.cast(), tod = to.pos.cast(); double fromR = get_radius(from), toR = get_radius(to); Beam beam{Ball{fromd, fromR}, Ball{tod, toR}}; - auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam, + auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam, m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); @@ -201,8 +203,8 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node, Beam beam2{Ball{from2d, closestR}, Ball{tod, mergeR}}; auto sd = m_sm.cfg.safety_distance_mm ; - auto hit1 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam1, sd); - auto hit2 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam2, sd); + auto hit1 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam1, sd); + auto hit2 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam2, sd); bool ret = hit1.distance() > (tod - from1d).norm() && hit2.distance() > (tod - from2d).norm(); @@ -222,7 +224,7 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); - auto conn = deepsearch_ground_connection(ex_tbb, m_sm, j, + auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j, get_radius(to), init_dir); if (conn) { @@ -255,13 +257,13 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, auto anchor = m_sm.cfg.ground_facing_only ? std::optional{} : // If no mesh connections are allowed - calculate_anchor_placement(ex_tbb, m_sm, fromj, + calculate_anchor_placement(beam_ex_policy , m_sm, fromj, to.pos.cast()); if (anchor) { sla::Junction toj = {anchor->junction_point(), anchor->r_back_mm}; - auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, + auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh, Beam{{fromj.pos, fromj.r}, {toj.pos, toj.r}}, 0.); if (hit.distance() > distance(fromj.pos, toj.pos)) { From 58acc893b3c9da4af912a648f743afebba6709e3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:49:26 +0100 Subject: [PATCH 114/206] Solve mini pillar widening by enforcing min radius on bed points Use subtree rescue after all --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 5c7e751fa..d88bdb1b0 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -150,7 +150,7 @@ public: << " " << j.pos.y() << " " << j.pos.z(); // Discard all the support points connecting to this branch. - discard_subtree(j.id); + discard_subtree_rescure(j.id); } const std::vector& unroutable_pinheads() const @@ -336,6 +336,9 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s float(props.ground_level()), props.sampling_radius()); + for (auto &bp : bedpts) + bp.Rmin = sm.cfg.head_back_radius_mm; + branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts), std::move(leafs), props}; From d4a46d373a4b269b84b9b14ab85a4221795a1aea Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:50:12 +0100 Subject: [PATCH 115/206] Solve mini pillar widening in DefaultWideningStrategy --- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 78e9afb88..a47f8d662 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -723,7 +723,7 @@ struct DefaultWideningModel { "DefaultWideningModel is not a widening function"); double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len; - return src.R + w; + return std::max(src.R, sm.cfg.head_back_radius_mm) + w; }; }; From e16c886a1adf2062e7bb34d75826bafa3dbe1c2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:50:36 +0100 Subject: [PATCH 116/206] Remove dead code --- src/libslic3r/BranchingTree/PointCloud.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 4075a20c2..1497f0894 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -74,8 +74,6 @@ std::vector sample_mesh(const indexed_triangle_set &its, double radius) std::vector sample_bed(const ExPolygons &bed, float z, double radius) { - std::vector ret; - auto triangles = triangulate_expolygons_3d(bed, z); indexed_triangle_set its; its.vertices.reserve(triangles.size()); From c79a46e6cb0dbb7591469354f9a2db90411cf5dd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 14:03:25 +0100 Subject: [PATCH 117/206] Remove unnecessary stuff --- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/OrganicTree/OrganicTree.hpp | 95 - src/libslic3r/OrganicTree/OrganicTreeImpl.hpp | 11 - src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 - tests/data/disk_with_hole.obj | 1696 ----------------- 5 files changed, 1808 deletions(-) delete mode 100644 src/libslic3r/OrganicTree/OrganicTree.hpp delete mode 100644 src/libslic3r/OrganicTree/OrganicTreeImpl.hpp delete mode 100644 tests/data/disk_with_hole.obj diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 232285cee..7e02598d8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -357,8 +357,6 @@ set(SLIC3R_SOURCES BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp BranchingTree/PointCloud.hpp - OrganicTree/OrganicTree.hpp - OrganicTree/OrganicTreeImpl.hpp Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp diff --git a/src/libslic3r/OrganicTree/OrganicTree.hpp b/src/libslic3r/OrganicTree/OrganicTree.hpp deleted file mode 100644 index cf34c9eb3..000000000 --- a/src/libslic3r/OrganicTree/OrganicTree.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef ORGANICTREE_HPP -#define ORGANICTREE_HPP - -#include -#include -#include - -namespace Slic3r { namespace organictree { - -enum class NodeType { Bed, Mesh, Junction }; - -template struct DomainTraits_ { - using Node = typename T::Node; - - static void push(const T &dom, const Node &n) - { - dom.push_junction(n); - } - - static Node pop(T &dom) { return dom.pop(); } - - static bool empty(const T &dom) { return dom.empty(); } - - static std::optional> - closest(const T &dom, const Node &n) - { - return dom.closest(n); - } - - static Node merge_node(const T &dom, const Node &a, const Node &b) - { - return dom.merge_node(a, b); - } - - static void bridge(T &dom, const Node &from, const Node &to) - { - dom.bridge(from, to); - } - - static void anchor(T &dom, const Node &from, const Node &to) - { - dom.anchor(from, to); - } - - static void pillar(T &dom, const Node &from, const Node &to) - { - dom.pillar(from, to); - } - - static void merge (T &dom, const Node &n1, const Node &n2, const Node &mrg) - { - dom.merge(n1, n2, mrg); - } - - static void report_fail(T &dom, const Node &n) { dom.report_fail(n); } -}; - -template -void build_tree(Domain &&D) -{ - using Dom = DomainTraits_>>; - using Node = typename Dom::Node; - - while (! Dom::empty(D)) { - Node n = Dom::pop(D); - - std::optional> C = Dom::closest(D, n); - - if (!C) { - Dom::report_fail(D, n); - } else switch (C->second) { - case NodeType::Bed: - Dom::pillar(D, n, C->first); - break; - case NodeType::Mesh: - Dom::anchor(D, n, C->first); - break; - case NodeType::Junction: { - Node M = Dom::merge_node(D, n, C->first); - - if (M == C->first) { - Dom::bridge(D, n, C->first); - } else { - Dom::push(D, M); - Dom::merge(D, n, M, C->first); - } - break; - } - } - } -} - -}} // namespace Slic3r::organictree - -#endif // ORGANICTREE_HPP diff --git a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp deleted file mode 100644 index 6e18550da..000000000 --- a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef ORGANICTREEIMPL_HPP -#define ORGANICTREEIMPL_HPP - -namespace Slic3r { namespace organictree { - - - - -}} - -#endif // ORGANICTREEIMPL_HPP diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index e0f3acb70..c22cdf606 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -12,8 +12,6 @@ #include #include -#include - #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" @@ -1021,8 +1019,6 @@ void GLGizmoSlaSupports::select_point(int i) m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; } else { - if (!m_editing_cache[i].selected) - BOOST_LOG_TRIVIAL(debug) << "Support point selected [" << i << "]: " << m_editing_cache[i].support_point.pos.transpose() << " \tnormal: " << m_editing_cache[i].normal.transpose(); m_editing_cache[i].selected = true; m_selection_empty = false; m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; diff --git a/tests/data/disk_with_hole.obj b/tests/data/disk_with_hole.obj deleted file mode 100644 index f16cafb87..000000000 --- a/tests/data/disk_with_hole.obj +++ /dev/null @@ -1,1696 +0,0 @@ -#### -# -# OBJ File Generated by Meshlab -# -#### -# Object disk_with_hole.obj -# -# Vertices: 720 -# Faces: 240 -# -#### -vn -0.328454 -0.737720 0.000000 -v -25.000000 -43.301270 -5.000000 -vn -0.310446 -0.697273 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.638901 -1.434994 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.328454 -0.737720 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.310446 -0.697273 0.000000 -v -25.000000 -43.301270 -5.000000 -vn -0.638901 -1.434994 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.725904 -0.235860 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -0.768012 -0.249542 0.000000 -v -48.907379 -10.395584 5.000000 -vn -1.493916 -0.485403 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -0.725904 -0.235860 0.000000 -v -48.907379 -10.395584 5.000000 -vn -0.768012 -0.249542 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -1.493916 -0.485403 0.000000 -v -45.677273 -20.336832 5.000000 -vn 0.725904 0.235860 0.000000 -v 48.907379 10.395584 5.000000 -vn 0.768012 0.249542 0.000000 -v 45.677273 20.336832 -5.000000 -vn 1.493916 0.485403 0.000000 -v 45.677273 20.336832 5.000000 -vn 0.725904 0.235860 0.000000 -v 45.677273 20.336832 -5.000000 -vn 0.768012 0.249542 0.000000 -v 48.907379 10.395584 5.000000 -vn 1.493916 0.485403 0.000000 -v 48.907379 10.395584 -5.000000 -vn 0.759080 0.079783 0.000000 -v 50.000000 0.000000 5.000000 -vn 0.803112 0.084411 0.000000 -v 48.907379 10.395584 -5.000000 -vn 1.562191 0.164193 0.000000 -v 48.907379 10.395584 5.000000 -vn 0.759080 0.079783 0.000000 -v 48.907379 10.395584 -5.000000 -vn 0.803112 0.084411 0.000000 -v 50.000000 0.000000 5.000000 -vn 1.562191 0.164193 0.000000 -v 50.000000 0.000000 -5.000000 -vn 0.661003 0.381630 0.000000 -v 45.677273 20.336832 5.000000 -vn 0.699346 0.403768 0.000000 -v 40.450851 29.389263 -5.000000 -vn 1.360350 0.785398 0.000000 -v 40.450851 29.389263 5.000000 -vn 0.661003 0.381630 0.000000 -v 40.450851 29.389263 -5.000000 -vn 0.699346 0.403768 0.000000 -v 45.677273 20.336832 5.000000 -vn 1.360350 0.785398 0.000000 -v 45.677273 20.336832 -5.000000 -vn 0.474657 -0.653310 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.448633 -0.617491 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.923291 -1.270801 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.474657 -0.653310 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.448633 -0.617491 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.923291 -1.270801 0.000000 -v 33.456528 -37.157242 -5.000000 -vn 0.328454 0.737720 0.000000 -v 25.000000 43.301270 -5.000000 -vn 0.310446 0.697273 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.638901 1.434994 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.328454 0.737720 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.310446 0.697273 0.000000 -v 25.000000 43.301270 -5.000000 -vn 0.638901 1.434994 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.759080 -0.079783 0.000000 -v 48.907379 -10.395584 5.000000 -vn 0.803112 -0.084411 0.000000 -v 50.000000 0.000000 -5.000000 -vn 1.562191 -0.164193 0.000000 -v 50.000000 0.000000 5.000000 -vn 0.759080 -0.079783 0.000000 -v 50.000000 0.000000 -5.000000 -vn 0.803112 -0.084411 0.000000 -v 48.907379 -10.395584 5.000000 -vn 1.562191 -0.164193 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 0.567213 0.510721 0.000000 -v 40.450851 29.389263 5.000000 -vn 0.600116 0.540347 0.000000 -v 33.456528 37.157242 -5.000000 -vn 1.167329 1.051068 0.000000 -v 33.456528 37.157242 5.000000 -vn 0.567213 0.510721 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.600116 0.540347 0.000000 -v 40.450851 29.389263 5.000000 -vn 1.167329 1.051068 0.000000 -v 40.450851 29.389263 -5.000000 -vn -0.661003 0.381630 0.000000 -v -45.677273 20.336832 -5.000000 -vn -0.699346 0.403768 0.000000 -v -40.450851 29.389263 5.000000 -vn -1.360350 0.785398 0.000000 -v -40.450851 29.389263 -5.000000 -vn -0.661003 0.381630 0.000000 -v -40.450851 29.389263 5.000000 -vn -0.699346 0.403768 0.000000 -v -45.677273 20.336832 -5.000000 -vn -1.360350 0.785398 0.000000 -v -45.677273 20.336832 5.000000 -vn 0.567213 -0.510721 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.600116 -0.540347 0.000000 -v 40.450851 -29.389263 -5.000000 -vn 1.167329 -1.051068 0.000000 -v 40.450851 -29.389263 5.000000 -vn 0.567213 -0.510721 0.000000 -v 40.450851 -29.389263 -5.000000 -vn 0.600116 -0.540347 0.000000 -v 33.456528 -37.157242 5.000000 -vn 1.167329 -1.051068 0.000000 -v 33.456528 -37.157242 -5.000000 -vn -0.567213 0.510721 0.000000 -v -40.450851 29.389263 -5.000000 -vn -0.600116 0.540347 0.000000 -v -33.456528 37.157242 5.000000 -vn -1.167329 1.051068 0.000000 -v -33.456528 37.157242 -5.000000 -vn -0.567213 0.510721 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.600116 0.540347 0.000000 -v -40.450851 29.389263 -5.000000 -vn -1.167329 1.051068 0.000000 -v -40.450851 29.389263 5.000000 -vn 0.661003 -0.381630 0.000000 -v 40.450851 -29.389263 5.000000 -vn 0.699346 -0.403768 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 1.360350 -0.785398 0.000000 -v 45.677273 -20.336832 5.000000 -vn 0.661003 -0.381630 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 0.699346 -0.403768 0.000000 -v 40.450851 -29.389263 5.000000 -vn 1.360350 -0.785398 0.000000 -v 40.450851 -29.389263 -5.000000 -vn -0.474657 0.653310 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.448633 0.617491 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.923291 1.270801 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.474657 0.653310 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.448633 0.617491 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.923291 1.270801 0.000000 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 0.397030 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 0.971546 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.773017 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 0.533261 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 0.983586 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.624746 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 0.935258 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 0.079637 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 2.126698 -v 28.090170 14.122147 5.000000 -vn 0.000000 0.000000 0.710460 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.068680 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.362453 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 0.095914 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.821343 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 1.224335 -v 28.090170 14.122147 5.000000 -vn 0.000000 0.000000 0.849935 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 1.114545 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.177112 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 0.129351 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.696999 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.315244 -v 29.781475 17.920883 5.000000 -vn 0.000000 0.000000 0.109378 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 2.035788 -v 29.781475 17.920883 5.000000 -vn 0.000000 0.000000 0.996427 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 0.131250 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 2.066767 -v 29.781475 22.079117 5.000000 -vn 0.000000 0.000000 0.943575 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 0.161065 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.696263 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.000000 1.284264 -v 29.781475 22.079117 5.000000 -vn 0.000000 0.000000 0.162839 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.323985 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 1.654769 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.000000 0.163690 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.800790 -v 26.691305 27.431448 5.000000 -vn 0.000000 0.000000 1.177113 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 0.797639 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 1.263865 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.080089 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.175248 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.416103 -v 25.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.550242 -v 26.691305 27.431448 5.000000 -vn 0.000000 0.000000 0.152239 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.054425 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 1.934929 -v 25.000000 28.660254 5.000000 -vn 0.000000 0.000000 0.150263 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.492361 -v 21.045284 29.945217 5.000000 -vn 0.000000 0.000000 1.498969 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 0.137161 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.145761 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 1.858670 -v 21.045284 29.945217 5.000000 -vn 0.000000 0.000000 0.955486 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.621466 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 1.564641 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.583804 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 0.115742 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.442047 -v 16.909828 29.510565 5.000000 -vn 0.000000 0.000000 0.104548 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.128057 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.908987 -v 16.909828 29.510565 5.000000 -vn 0.000000 0.000000 0.926960 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.458257 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.756376 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.764718 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 0.086613 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.290263 -v 13.308694 27.431448 5.000000 -vn 0.000000 0.000000 2.060768 -v 13.308694 27.431448 5.000000 -vn 0.000000 0.000000 0.074549 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.006277 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 0.947726 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.349832 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 1.844034 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.938074 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.282044 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 1.921476 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.994924 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 0.062953 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.083717 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 1.054154 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.068696 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 2.018744 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 1.891912 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 0.312011 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.937670 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.099058 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 0.057667 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.984868 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.936816 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 0.258266 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 0.946511 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.099133 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 0.048750 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.993709 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 1.936892 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 0.223894 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 0.980807 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.071817 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 0.041770 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 2.028005 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 1.909575 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 0.200963 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 1.031054 -v 33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 1.901099 -v 33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 0.185196 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 1.055297 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.212075 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 0.036463 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.893055 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 0.174414 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.126786 -v 15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.840393 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 0.032941 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.798030 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.310621 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.031018 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.721914 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.388663 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 0.030639 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.500477 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.610479 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 0.031757 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.271469 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.838368 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 0.034542 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.178902 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.928151 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 0.038851 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.051199 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 2.051544 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.985271 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 0.052938 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.103383 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 0.166514 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.169712 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.805367 -v 15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 0.961197 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.239254 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 1.941141 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.161147 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.250946 -v -5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.729500 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 2.008394 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 0.045050 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.088148 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 0.157350 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.303035 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.681207 -v -5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.004346 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 0.211341 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.925906 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.154864 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.388627 -v -25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.598102 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 0.192291 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 1.888957 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.060345 -v -40.450851 29.389263 5.000000 -vn 0.000000 0.000000 0.153677 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.444390 -v -33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 1.543527 -v -25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 0.179392 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 1.871808 -v -40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.090393 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 0.153352 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.500477 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.487764 -v -33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 0.170109 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.807220 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.164264 -v -48.907379 10.395584 5.000000 -vn 0.000000 0.000000 0.154128 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.586425 -v -45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.401039 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 0.163654 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.767889 -v -48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.210049 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 0.156018 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.639846 -v -48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.345728 -v -45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 0.158937 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.690347 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.292307 -v -48.907379 -10.395584 5.000000 -vn 0.725904 -0.235860 0.000000 -v 45.677273 -20.336832 5.000000 -vn 0.768012 -0.249542 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 1.493916 -0.485403 0.000000 -v 48.907379 -10.395584 5.000000 -vn 0.725904 -0.235860 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 0.768012 -0.249542 0.000000 -v 45.677273 -20.336832 5.000000 -vn 1.493916 -0.485403 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.807535 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.763261 0.000000 -v -5.226422 49.726093 5.000000 -vn 0.000000 1.570796 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.807535 0.000000 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.763261 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.000000 1.570796 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.474657 -0.653310 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.448633 -0.617491 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.923291 -1.270801 0.000000 -v -33.456528 -37.157242 5.000000 -vn -0.474657 -0.653310 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.448633 -0.617491 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.923291 -1.270801 0.000000 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.312011 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.891912 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.937670 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.258266 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -1.936816 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.946511 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.068696 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.054154 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -2.018744 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -0.223894 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -1.936892 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.980807 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.079637 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.935258 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -2.126698 -v 28.090170 14.122147 -5.000000 -vn 0.000000 0.000000 -0.200963 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.909575 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.031054 -v 33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -2.035788 -v 29.781475 17.920883 -5.000000 -vn 0.000000 0.000000 -0.109378 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.996427 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -1.068680 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.710460 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.362453 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -2.066767 -v 29.781475 22.079117 -5.000000 -vn 0.000000 0.000000 -0.131250 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.943575 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.696999 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -0.129351 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.315244 -v 29.781475 17.920883 -5.000000 -vn 0.000000 0.000000 -0.983586 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.533261 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -1.624746 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.821343 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -0.095914 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -1.224335 -v 28.090170 14.122147 -5.000000 -vn 0.000000 0.000000 -0.185196 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.901099 -v 33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -1.055297 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.971546 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.397030 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -1.773017 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.057667 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -1.099058 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -1.984868 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -0.048750 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -1.099133 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -1.993709 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -0.041770 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.071817 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -2.028005 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -0.036463 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.212075 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.893055 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.126786 -v 15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -0.174414 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.840393 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.169712 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -0.166514 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.805367 -v 15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.798030 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -0.032941 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.310621 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.250946 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -0.161147 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.729500 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.303035 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -0.157350 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.681207 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.721914 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -0.031018 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.388663 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.388627 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.154864 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.598102 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.444390 -v -33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -0.153677 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.543527 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.696263 -v 29.135454 24.067366 -5.000000 -vn 0.000000 0.000000 -0.161065 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.284264 -v 29.781475 22.079117 -5.000000 -vn 0.000000 0.000000 -1.323985 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -0.162839 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.654769 -v 29.135454 24.067366 -5.000000 -vn 0.000000 0.000000 -1.114545 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.849935 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -1.177112 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.800790 -v 26.691305 27.431448 -5.000000 -vn 0.000000 0.000000 -0.163690 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.177113 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -1.416103 -v 25.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.175248 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.550242 -v 26.691305 27.431448 -5.000000 -vn 0.000000 0.000000 -1.054425 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -0.152239 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.934929 -v 25.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -1.263865 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -0.797639 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -1.080089 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.492361 -v 21.045284 29.945217 -5.000000 -vn 0.000000 0.000000 -0.150263 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.498969 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -1.145761 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -0.137161 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.858670 -v 21.045284 29.945217 -5.000000 -vn 0.000000 0.000000 -0.115742 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.583804 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -1.442047 -v 16.909828 29.510565 -5.000000 -vn 0.000000 0.000000 -0.621466 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -0.955486 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.564641 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.128057 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.104548 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.908987 -v 16.909828 29.510565 -5.000000 -vn 0.000000 0.000000 -0.086613 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.764718 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -1.290263 -v 13.308694 27.431448 -5.000000 -vn 0.000000 0.000000 -0.074549 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -2.060768 -v 13.308694 27.431448 -5.000000 -vn 0.000000 0.000000 -1.006277 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -0.062953 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.994924 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -1.083717 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -0.052938 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.985271 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -1.103383 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -0.458257 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.926960 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.756376 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -0.045050 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -2.008394 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -1.088148 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.500477 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.153352 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.487764 -v -33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -1.500477 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -0.030639 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.610479 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -0.349832 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -0.947726 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.844034 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.586425 -v -45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.154128 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.401039 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.282044 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -0.938074 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.921476 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.639846 -v -48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.156018 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.345728 -v -45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.239254 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -0.961197 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.941141 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.690347 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.158937 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.292307 -v -48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.211341 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.004346 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.925906 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.271469 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -0.031757 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.838368 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.051199 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -0.038851 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -2.051544 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.767889 -v -48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.163654 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.210049 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.888957 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -0.192291 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -1.060345 -v -40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.807220 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.170109 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.164264 -v -48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -1.871808 -v -40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.179392 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -1.090393 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.178902 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -0.034542 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.928151 -v 10.218524 17.920883 -5.000000 -vn -0.328454 0.737720 0.000000 -v -15.450850 47.552826 -5.000000 -vn -0.310446 0.697273 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.638901 1.434994 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.328454 0.737720 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.310446 0.697273 0.000000 -v -15.450850 47.552826 -5.000000 -vn -0.638901 1.434994 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.759080 -0.079783 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -0.803112 -0.084411 0.000000 -v -50.000000 0.000000 5.000000 -vn -1.562191 -0.164193 0.000000 -v -50.000000 0.000000 -5.000000 -vn -0.759080 -0.079783 0.000000 -v -50.000000 0.000000 5.000000 -vn -0.803112 -0.084411 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -1.562191 -0.164193 0.000000 -v -48.907379 -10.395584 5.000000 -vn -0.567213 -0.510721 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.600116 -0.540347 0.000000 -v -40.450851 -29.389263 5.000000 -vn -1.167329 -1.051068 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -0.567213 -0.510721 0.000000 -v -40.450851 -29.389263 5.000000 -vn -0.600116 -0.540347 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -1.167329 -1.051068 0.000000 -v -33.456528 -37.157242 5.000000 -vn -0.661003 -0.381630 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -0.699346 -0.403768 0.000000 -v -45.677273 -20.336832 5.000000 -vn -1.360350 -0.785398 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -0.661003 -0.381630 0.000000 -v -45.677273 -20.336832 5.000000 -vn -0.699346 -0.403768 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -1.360350 -0.785398 0.000000 -v -40.450851 -29.389263 5.000000 -vn 0.167896 -0.789889 0.000000 -v 5.226422 -49.726093 -5.000000 -vn 0.158691 -0.746582 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.326587 -1.536471 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.167896 -0.789889 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.158691 -0.746582 0.000000 -v 5.226422 -49.726093 -5.000000 -vn 0.326587 -1.536471 0.000000 -v 15.450850 -47.552826 -5.000000 -vn -0.759080 0.079783 0.000000 -v -50.000000 0.000000 -5.000000 -vn -0.803112 0.084411 0.000000 -v -48.907379 10.395584 5.000000 -vn -1.562191 0.164193 0.000000 -v -48.907379 10.395584 -5.000000 -vn -0.759080 0.079783 0.000000 -v -48.907379 10.395584 5.000000 -vn -0.803112 0.084411 0.000000 -v -50.000000 0.000000 -5.000000 -vn -1.562191 0.164193 0.000000 -v -50.000000 0.000000 5.000000 -vn 0.167896 0.789889 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.158691 0.746582 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.326587 1.536471 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.167896 0.789889 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.158691 0.746582 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.326587 1.536471 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.474657 0.653310 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.448633 0.617491 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.923291 1.270801 0.000000 -v 33.456528 37.157242 5.000000 -vn 0.474657 0.653310 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.448633 0.617491 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.923291 1.270801 0.000000 -v 25.000000 43.301270 -5.000000 -vn -0.167896 0.789889 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.158691 0.746582 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.326587 1.536471 0.000000 -v -5.226422 49.726093 5.000000 -vn -0.167896 0.789889 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.158691 0.746582 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.326587 1.536471 0.000000 -v -15.450850 47.552826 -5.000000 -vn 0.328454 -0.737720 0.000000 -v 15.450850 -47.552826 -5.000000 -vn 0.310446 -0.697273 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.638901 -1.434994 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.328454 -0.737720 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.310446 -0.697273 0.000000 -v 15.450850 -47.552826 -5.000000 -vn 0.638901 -1.434994 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 -0.807535 0.000000 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 -0.763261 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.000000 -1.570796 0.000000 -v -5.226422 -49.726093 5.000000 -vn 0.000000 -0.807535 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.000000 -0.763261 0.000000 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 -1.570796 0.000000 -v 5.226422 -49.726093 -5.000000 -vn -0.167896 -0.789889 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.158691 -0.746582 0.000000 -v -5.226422 -49.726093 5.000000 -vn -0.326587 -1.536471 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.167896 -0.789889 0.000000 -v -5.226422 -49.726093 5.000000 -vn -0.158691 -0.746582 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.326587 -1.536471 0.000000 -v -5.226422 -49.726093 -5.000000 -vn -0.725904 0.235860 0.000000 -v -48.907379 10.395584 -5.000000 -vn -0.768012 0.249542 0.000000 -v -45.677273 20.336832 5.000000 -vn -1.493916 0.485403 0.000000 -v -45.677273 20.336832 -5.000000 -vn -0.725904 0.235860 0.000000 -v -45.677273 20.336832 5.000000 -vn -0.768012 0.249542 0.000000 -v -48.907379 10.395584 -5.000000 -vn -1.493916 0.485403 0.000000 -v -48.907379 10.395584 5.000000 -vn -1.297914 -0.421718 0.000000 -v 29.781475 22.079117 -5.000000 -vn -0.196002 -0.063685 0.000000 -v 29.135454 24.067366 5.000000 -vn -1.493916 -0.485403 0.000000 -v 29.135454 24.067366 -5.000000 -vn -1.297914 -0.421718 0.000000 -v 29.135454 24.067366 5.000000 -vn -0.196002 -0.063685 0.000000 -v 29.781475 22.079117 -5.000000 -vn -1.493916 -0.485403 0.000000 -v 29.781475 22.079117 5.000000 -vn -1.357231 -0.142651 0.000000 -v 30.000000 20.000000 -5.000000 -vn -0.204960 -0.021542 0.000000 -v 29.781475 22.079117 5.000000 -vn -1.562191 -0.164194 0.000000 -v 29.781475 22.079117 -5.000000 -vn -1.357231 -0.142651 0.000000 -v 29.781475 22.079117 5.000000 -vn -0.204960 -0.021542 0.000000 -v 30.000000 20.000000 -5.000000 -vn -1.562191 -0.164194 0.000000 -v 30.000000 20.000000 5.000000 -vn -1.181872 -0.682353 0.000000 -v 29.135454 24.067366 -5.000000 -vn -0.178478 -0.103044 0.000000 -v 28.090170 25.877853 5.000000 -vn -1.360350 -0.785397 0.000000 -v 28.090170 25.877853 -5.000000 -vn -1.181872 -0.682353 0.000000 -v 28.090170 25.877853 5.000000 -vn -0.178478 -0.103044 0.000000 -v 29.135454 24.067366 -5.000000 -vn -1.360350 -0.785397 0.000000 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.206089 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.000000 1.364708 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.000000 1.570796 0.000000 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.206089 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.000000 1.364708 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.000000 1.570796 0.000000 -v 18.954716 10.054781 -5.000000 -vn -0.083824 0.188272 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.555077 1.246722 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.638901 1.434994 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.083824 0.188272 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.555077 1.246722 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.638901 1.434994 0.000000 -v 23.090170 10.489434 -5.000000 -vn 1.297914 0.421717 0.000000 -v 10.864545 15.932633 5.000000 -vn 0.196002 0.063685 0.000000 -v 10.218524 17.920883 -5.000000 -vn 1.493916 0.485402 0.000000 -v 10.218524 17.920883 5.000000 -vn 1.297914 0.421717 0.000000 -v 10.218524 17.920883 -5.000000 -vn 0.196002 0.063685 0.000000 -v 10.864545 15.932633 5.000000 -vn 1.493916 0.485402 0.000000 -v 10.864545 15.932633 -5.000000 -vn -1.357231 0.142651 0.000000 -v 29.781475 17.920883 -5.000000 -vn -0.204960 0.021542 0.000000 -v 30.000000 20.000000 5.000000 -vn -1.562191 0.164194 0.000000 -v 30.000000 20.000000 -5.000000 -vn -1.357231 0.142651 0.000000 -v 30.000000 20.000000 5.000000 -vn -0.204960 0.021542 0.000000 -v 29.781475 17.920883 -5.000000 -vn -1.562191 0.164194 0.000000 -v 29.781475 17.920883 5.000000 -vn 1.014175 -0.913168 0.000000 -v 11.909829 25.877853 5.000000 -vn 0.153154 -0.137900 0.000000 -v 13.308694 27.431448 -5.000000 -vn 1.167328 -1.051069 0.000000 -v 13.308694 27.431448 5.000000 -vn 1.014175 -0.913168 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.153154 -0.137900 0.000000 -v 11.909829 25.877853 5.000000 -vn 1.167328 -1.051069 0.000000 -v 11.909829 25.877853 -5.000000 -vn 0.042848 0.201585 0.000000 -v 18.954716 10.054781 -5.000000 -vn 0.283738 1.334885 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.326586 1.536471 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.042848 0.201585 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.283738 1.334885 0.000000 -v 18.954716 10.054781 -5.000000 -vn 0.326586 1.536471 0.000000 -v 16.909828 10.489434 -5.000000 -vn -1.297914 0.421717 0.000000 -v 29.135454 15.932633 -5.000000 -vn -0.196002 0.063685 0.000000 -v 29.781475 17.920883 5.000000 -vn -1.493916 0.485402 0.000000 -v 29.781475 17.920883 -5.000000 -vn -1.297914 0.421717 0.000000 -v 29.781475 17.920883 5.000000 -vn -0.196002 0.063685 0.000000 -v 29.135454 15.932633 -5.000000 -vn -1.493916 0.485402 0.000000 -v 29.135454 15.932633 5.000000 -vn 0.083824 0.188271 0.000000 -v 16.909828 10.489434 -5.000000 -vn 0.555077 1.246722 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.638901 1.434994 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.083824 0.188271 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.555077 1.246722 0.000000 -v 16.909828 10.489434 -5.000000 -vn 0.638901 1.434994 0.000000 -v 15.000000 11.339746 -5.000000 -vn -0.042848 -0.201585 0.000000 -v 21.045284 29.945217 -5.000000 -vn -0.283738 -1.334886 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.326586 -1.536471 0.000000 -v 21.045284 29.945217 5.000000 -vn -0.042848 -0.201585 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.283738 -1.334886 0.000000 -v 21.045284 29.945217 -5.000000 -vn -0.326586 -1.536471 0.000000 -v 23.090170 29.510565 -5.000000 -vn 0.000000 -0.206089 0.000000 -v 18.954716 29.945217 -5.000000 -vn 0.000000 -1.364708 0.000000 -v 21.045284 29.945217 5.000000 -vn 0.000000 -1.570796 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.000000 -0.206089 0.000000 -v 21.045284 29.945217 5.000000 -vn 0.000000 -1.364708 0.000000 -v 18.954716 29.945217 -5.000000 -vn 0.000000 -1.570796 0.000000 -v 21.045284 29.945217 -5.000000 -vn -1.181872 0.682353 0.000000 -v 28.090170 14.122147 -5.000000 -vn -0.178478 0.103044 0.000000 -v 29.135454 15.932633 5.000000 -vn -1.360350 0.785398 0.000000 -v 29.135454 15.932633 -5.000000 -vn -1.181872 0.682353 0.000000 -v 29.135454 15.932633 5.000000 -vn -0.178478 0.103044 0.000000 -v 28.090170 14.122147 -5.000000 -vn -1.360350 0.785398 0.000000 -v 28.090170 14.122147 5.000000 -vn 1.297914 -0.421718 0.000000 -v 10.218524 22.079117 5.000000 -vn 0.196002 -0.063685 0.000000 -v 10.864545 24.067366 -5.000000 -vn 1.493916 -0.485403 0.000000 -v 10.864545 24.067366 5.000000 -vn 1.297914 -0.421718 0.000000 -v 10.864545 24.067366 -5.000000 -vn 0.196002 -0.063685 0.000000 -v 10.218524 22.079117 5.000000 -vn 1.493916 -0.485403 0.000000 -v 10.218524 22.079117 -5.000000 -vn 1.014175 0.913168 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.153154 0.137900 0.000000 -v 11.909829 14.122147 -5.000000 -vn 1.167329 1.051068 0.000000 -v 11.909829 14.122147 5.000000 -vn 1.014175 0.913168 0.000000 -v 11.909829 14.122147 -5.000000 -vn 0.153154 0.137900 0.000000 -v 13.308694 12.568551 5.000000 -vn 1.167329 1.051068 0.000000 -v 13.308694 12.568551 -5.000000 -vn -1.014175 -0.913168 0.000000 -v 28.090170 25.877853 -5.000000 -vn -0.153154 -0.137900 0.000000 -v 26.691305 27.431448 5.000000 -vn -1.167328 -1.051069 0.000000 -v 26.691305 27.431448 -5.000000 -vn -1.014175 -0.913168 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.153154 -0.137900 0.000000 -v 28.090170 25.877853 -5.000000 -vn -1.167328 -1.051069 0.000000 -v 28.090170 25.877853 5.000000 -vn 1.357232 -0.142651 0.000000 -v 10.000000 20.000000 5.000000 -vn 0.204960 -0.021542 0.000000 -v 10.218524 22.079117 -5.000000 -vn 1.562191 -0.164193 0.000000 -v 10.218524 22.079117 5.000000 -vn 1.357232 -0.142651 0.000000 -v 10.218524 22.079117 -5.000000 -vn 0.204960 -0.021542 0.000000 -v 10.000000 20.000000 5.000000 -vn 1.562191 -0.164193 0.000000 -v 10.000000 20.000000 -5.000000 -vn 0.042848 -0.201585 0.000000 -v 16.909828 29.510565 -5.000000 -vn 0.283737 -1.334885 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.326586 -1.536471 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.042848 -0.201585 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.283737 -1.334885 0.000000 -v 16.909828 29.510565 -5.000000 -vn 0.326586 -1.536471 0.000000 -v 18.954716 29.945217 -5.000000 -vn -0.121136 -0.166729 0.000000 -v 25.000000 28.660254 -5.000000 -vn -0.802155 -1.104072 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.923291 -1.270801 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.121136 -0.166729 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.802155 -1.104072 0.000000 -v 25.000000 28.660254 -5.000000 -vn -0.923291 -1.270801 0.000000 -v 26.691305 27.431448 -5.000000 -vn 0.083824 -0.188271 0.000000 -v 15.000000 28.660254 -5.000000 -vn 0.555077 -1.246722 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.638901 -1.434994 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.083824 -0.188271 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.555077 -1.246722 0.000000 -v 15.000000 28.660254 -5.000000 -vn 0.638901 -1.434994 0.000000 -v 16.909828 29.510565 -5.000000 -vn -1.014175 0.913168 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.153154 0.137900 0.000000 -v 28.090170 14.122147 5.000000 -vn -1.167329 1.051068 0.000000 -v 28.090170 14.122147 -5.000000 -vn -1.014175 0.913168 0.000000 -v 28.090170 14.122147 5.000000 -vn -0.153154 0.137900 0.000000 -v 26.691305 12.568551 -5.000000 -vn -1.167329 1.051068 0.000000 -v 26.691305 12.568551 5.000000 -vn -0.083824 -0.188272 0.000000 -v 23.090170 29.510565 -5.000000 -vn -0.555077 -1.246722 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.638901 -1.434994 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.083824 -0.188272 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.555077 -1.246722 0.000000 -v 23.090170 29.510565 -5.000000 -vn -0.638901 -1.434994 0.000000 -v 25.000000 28.660254 -5.000000 -vn 0.121136 0.166729 0.000000 -v 15.000000 11.339746 -5.000000 -vn 0.802155 1.104072 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.923291 1.270801 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.121136 0.166729 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.802155 1.104072 0.000000 -v 15.000000 11.339746 -5.000000 -vn 0.923291 1.270801 0.000000 -v 13.308694 12.568551 -5.000000 -vn -0.121136 0.166729 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.802155 1.104072 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.923291 1.270801 0.000000 -v 26.691305 12.568551 5.000000 -vn -0.121136 0.166729 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.802155 1.104072 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.923291 1.270801 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.042848 0.201585 0.000000 -v 23.090170 10.489434 -5.000000 -vn -0.283738 1.334885 0.000000 -v 21.045284 10.054781 5.000000 -vn -0.326587 1.536471 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.042848 0.201585 0.000000 -v 21.045284 10.054781 5.000000 -vn -0.283738 1.334885 0.000000 -v 23.090170 10.489434 -5.000000 -vn -0.326587 1.536471 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.121136 -0.166729 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.802155 -1.104072 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.923291 -1.270801 0.000000 -v 13.308694 27.431448 5.000000 -vn 0.121136 -0.166729 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.802155 -1.104072 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.923291 -1.270801 0.000000 -v 15.000000 28.660254 -5.000000 -vn 1.181872 0.682353 0.000000 -v 11.909829 14.122147 5.000000 -vn 0.178478 0.103044 0.000000 -v 10.864545 15.932633 -5.000000 -vn 1.360350 0.785398 0.000000 -v 10.864545 15.932633 5.000000 -vn 1.181872 0.682353 0.000000 -v 10.864545 15.932633 -5.000000 -vn 0.178478 0.103044 0.000000 -v 11.909829 14.122147 5.000000 -vn 1.360350 0.785398 0.000000 -v 11.909829 14.122147 -5.000000 -vn 1.357232 0.142651 0.000000 -v 10.218524 17.920883 5.000000 -vn 0.204960 0.021542 0.000000 -v 10.000000 20.000000 -5.000000 -vn 1.562191 0.164193 0.000000 -v 10.000000 20.000000 5.000000 -vn 1.357232 0.142651 0.000000 -v 10.000000 20.000000 -5.000000 -vn 0.204960 0.021542 0.000000 -v 10.218524 17.920883 5.000000 -vn 1.562191 0.164193 0.000000 -v 10.218524 17.920883 -5.000000 -vn 1.181872 -0.682353 0.000000 -v 10.864545 24.067366 5.000000 -vn 0.178478 -0.103044 0.000000 -v 11.909829 25.877853 -5.000000 -vn 1.360350 -0.785397 0.000000 -v 11.909829 25.877853 5.000000 -vn 1.181872 -0.682353 0.000000 -v 11.909829 25.877853 -5.000000 -vn 0.178478 -0.103044 0.000000 -v 10.864545 24.067366 5.000000 -vn 1.360350 -0.785397 0.000000 -v 10.864545 24.067366 -5.000000 -# 720 vertices, 0 vertices normals - -f 1//1 2//2 3//3 -f 4//4 5//5 6//6 -f 7//7 8//8 9//9 -f 10//10 11//11 12//12 -f 13//13 14//14 15//15 -f 16//16 17//17 18//18 -f 19//19 20//20 21//21 -f 22//22 23//23 24//24 -f 25//25 26//26 27//27 -f 28//28 29//29 30//30 -f 31//31 32//32 33//33 -f 34//34 35//35 36//36 -f 37//37 38//38 39//39 -f 40//40 41//41 42//42 -f 43//43 44//44 45//45 -f 46//46 47//47 48//48 -f 49//49 50//50 51//51 -f 52//52 53//53 54//54 -f 55//55 56//56 57//57 -f 58//58 59//59 60//60 -f 61//61 62//62 63//63 -f 64//64 65//65 66//66 -f 67//67 68//68 69//69 -f 70//70 71//71 72//72 -f 73//73 74//74 75//75 -f 76//76 77//77 78//78 -f 79//79 80//80 81//81 -f 82//82 83//83 84//84 -f 85//85 86//86 87//87 -f 88//88 89//89 90//90 -f 91//91 92//92 93//93 -f 94//94 95//95 96//96 -f 97//97 98//98 99//99 -f 100//100 101//101 102//102 -f 103//103 104//104 105//105 -f 106//106 107//107 108//108 -f 109//109 110//110 111//111 -f 112//112 113//113 114//114 -f 115//115 116//116 117//117 -f 118//118 119//119 120//120 -f 121//121 122//122 123//123 -f 124//124 125//125 126//126 -f 127//127 128//128 129//129 -f 130//130 131//131 132//132 -f 133//133 134//134 135//135 -f 136//136 137//137 138//138 -f 139//139 140//140 141//141 -f 142//142 143//143 144//144 -f 145//145 146//146 147//147 -f 148//148 149//149 150//150 -f 151//151 152//152 153//153 -f 154//154 155//155 156//156 -f 157//157 158//158 159//159 -f 160//160 161//161 162//162 -f 163//163 164//164 165//165 -f 166//166 167//167 168//168 -f 169//169 170//170 171//171 -f 172//172 173//173 174//174 -f 175//175 176//176 177//177 -f 178//178 179//179 180//180 -f 181//181 182//182 183//183 -f 184//184 185//185 186//186 -f 187//187 188//188 189//189 -f 190//190 191//191 192//192 -f 193//193 194//194 195//195 -f 196//196 197//197 198//198 -f 199//199 200//200 201//201 -f 202//202 203//203 204//204 -f 205//205 206//206 207//207 -f 208//208 209//209 210//210 -f 211//211 212//212 213//213 -f 214//214 215//215 216//216 -f 217//217 218//218 219//219 -f 220//220 221//221 222//222 -f 223//223 224//224 225//225 -f 226//226 227//227 228//228 -f 229//229 230//230 231//231 -f 232//232 233//233 234//234 -f 235//235 236//236 237//237 -f 238//238 239//239 240//240 -f 241//241 242//242 243//243 -f 244//244 245//245 246//246 -f 247//247 248//248 249//249 -f 250//250 251//251 252//252 -f 253//253 254//254 255//255 -f 256//256 257//257 258//258 -f 259//259 260//260 261//261 -f 262//262 263//263 264//264 -f 265//265 266//266 267//267 -f 268//268 269//269 270//270 -f 271//271 272//272 273//273 -f 274//274 275//275 276//276 -f 277//277 278//278 279//279 -f 280//280 281//281 282//282 -f 283//283 284//284 285//285 -f 286//286 287//287 288//288 -f 289//289 290//290 291//291 -f 292//292 293//293 294//294 -f 295//295 296//296 297//297 -f 298//298 299//299 300//300 -f 301//301 302//302 303//303 -f 304//304 305//305 306//306 -f 307//307 308//308 309//309 -f 310//310 311//311 312//312 -f 313//313 314//314 315//315 -f 316//316 317//317 318//318 -f 319//319 320//320 321//321 -f 322//322 323//323 324//324 -f 325//325 326//326 327//327 -f 328//328 329//329 330//330 -f 331//331 332//332 333//333 -f 334//334 335//335 336//336 -f 337//337 338//338 339//339 -f 340//340 341//341 342//342 -f 343//343 344//344 345//345 -f 346//346 347//347 348//348 -f 349//349 350//350 351//351 -f 352//352 353//353 354//354 -f 355//355 356//356 357//357 -f 358//358 359//359 360//360 -f 361//361 362//362 363//363 -f 364//364 365//365 366//366 -f 367//367 368//368 369//369 -f 370//370 371//371 372//372 -f 373//373 374//374 375//375 -f 376//376 377//377 378//378 -f 379//379 380//380 381//381 -f 382//382 383//383 384//384 -f 385//385 386//386 387//387 -f 388//388 389//389 390//390 -f 391//391 392//392 393//393 -f 394//394 395//395 396//396 -f 397//397 398//398 399//399 -f 400//400 401//401 402//402 -f 403//403 404//404 405//405 -f 406//406 407//407 408//408 -f 409//409 410//410 411//411 -f 412//412 413//413 414//414 -f 415//415 416//416 417//417 -f 418//418 419//419 420//420 -f 421//421 422//422 423//423 -f 424//424 425//425 426//426 -f 427//427 428//428 429//429 -f 430//430 431//431 432//432 -f 433//433 434//434 435//435 -f 436//436 437//437 438//438 -f 439//439 440//440 441//441 -f 442//442 443//443 444//444 -f 445//445 446//446 447//447 -f 448//448 449//449 450//450 -f 451//451 452//452 453//453 -f 454//454 455//455 456//456 -f 457//457 458//458 459//459 -f 460//460 461//461 462//462 -f 463//463 464//464 465//465 -f 466//466 467//467 468//468 -f 469//469 470//470 471//471 -f 472//472 473//473 474//474 -f 475//475 476//476 477//477 -f 478//478 479//479 480//480 -f 481//481 482//482 483//483 -f 484//484 485//485 486//486 -f 487//487 488//488 489//489 -f 490//490 491//491 492//492 -f 493//493 494//494 495//495 -f 496//496 497//497 498//498 -f 499//499 500//500 501//501 -f 502//502 503//503 504//504 -f 505//505 506//506 507//507 -f 508//508 509//509 510//510 -f 511//511 512//512 513//513 -f 514//514 515//515 516//516 -f 517//517 518//518 519//519 -f 520//520 521//521 522//522 -f 523//523 524//524 525//525 -f 526//526 527//527 528//528 -f 529//529 530//530 531//531 -f 532//532 533//533 534//534 -f 535//535 536//536 537//537 -f 538//538 539//539 540//540 -f 541//541 542//542 543//543 -f 544//544 545//545 546//546 -f 547//547 548//548 549//549 -f 550//550 551//551 552//552 -f 553//553 554//554 555//555 -f 556//556 557//557 558//558 -f 559//559 560//560 561//561 -f 562//562 563//563 564//564 -f 565//565 566//566 567//567 -f 568//568 569//569 570//570 -f 571//571 572//572 573//573 -f 574//574 575//575 576//576 -f 577//577 578//578 579//579 -f 580//580 581//581 582//582 -f 583//583 584//584 585//585 -f 586//586 587//587 588//588 -f 589//589 590//590 591//591 -f 592//592 593//593 594//594 -f 595//595 596//596 597//597 -f 598//598 599//599 600//600 -f 601//601 602//602 603//603 -f 604//604 605//605 606//606 -f 607//607 608//608 609//609 -f 610//610 611//611 612//612 -f 613//613 614//614 615//615 -f 616//616 617//617 618//618 -f 619//619 620//620 621//621 -f 622//622 623//623 624//624 -f 625//625 626//626 627//627 -f 628//628 629//629 630//630 -f 631//631 632//632 633//633 -f 634//634 635//635 636//636 -f 637//637 638//638 639//639 -f 640//640 641//641 642//642 -f 643//643 644//644 645//645 -f 646//646 647//647 648//648 -f 649//649 650//650 651//651 -f 652//652 653//653 654//654 -f 655//655 656//656 657//657 -f 658//658 659//659 660//660 -f 661//661 662//662 663//663 -f 664//664 665//665 666//666 -f 667//667 668//668 669//669 -f 670//670 671//671 672//672 -f 673//673 674//674 675//675 -f 676//676 677//677 678//678 -f 679//679 680//680 681//681 -f 682//682 683//683 684//684 -f 685//685 686//686 687//687 -f 688//688 689//689 690//690 -f 691//691 692//692 693//693 -f 694//694 695//695 696//696 -f 697//697 698//698 699//699 -f 700//700 701//701 702//702 -f 703//703 704//704 705//705 -f 706//706 707//707 708//708 -f 709//709 710//710 711//711 -f 712//712 713//713 714//714 -f 715//715 716//716 717//717 -f 718//718 719//719 720//720 -# 240 faces, 0 coords texture - -# End of File From 878f3b30ddb41b6ffee37c96a4ff1e220b2d3951 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 17:46:27 +0100 Subject: [PATCH 118/206] wip adding separate config values for support tree algorithms --- src/libslic3r/Preset.cpp | 20 ++ src/libslic3r/PrintConfig.cpp | 366 +++++++++++++------------- src/libslic3r/PrintConfig.hpp | 57 ++++ src/libslic3r/SLAPrint.cpp | 80 ++++-- src/slic3r/GUI/ConfigManipulation.cpp | 55 ++-- src/slic3r/GUI/Tab.cpp | 96 +++++-- src/slic3r/GUI/Tab.hpp | 5 +- 7 files changed, 432 insertions(+), 247 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ce3c27bf6..9dad49d4c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -495,6 +495,7 @@ static std::vector s_Preset_sla_print_options { "faded_layers", "supports_enable", "support_tree_type", + "support_head_front_diameter", "support_head_penetration", "support_head_width", @@ -512,6 +513,25 @@ static std::vector s_Preset_sla_print_options { "support_max_bridge_length", "support_max_pillar_link_distance", "support_object_elevation", + + "branchingsupport_head_front_diameter", + "branchingsupport_head_penetration", + "branchingsupport_head_width", + "branchingsupport_pillar_diameter", + "branchingsupport_small_pillar_diameter_percent", + "branchingsupport_max_bridges_on_pillar", + "branchingsupport_max_weight_on_model", + "branchingsupport_pillar_connection_mode", + "branchingsupport_buildplate_only", + "branchingsupport_pillar_widening_factor", + "branchingsupport_base_diameter", + "branchingsupport_base_height", + "branchingsupport_base_safety_distance", + "branchingsupport_critical_angle", + "branchingsupport_max_bridge_length", + "branchingsupport_max_pillar_link_distance", + "branchingsupport_object_elevation", + "support_points_density_relative", "support_points_minimal_distance", "slice_closing_radius", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ebdfd2ff6..f39857759 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3273,6 +3273,191 @@ void PrintConfigDef::init_extruder_option_keys() assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); } +void PrintConfigDef::init_sla_support_params(const std::string &prefix) +{ + ConfigOptionDef* def; + + def = this->add(prefix + "support_head_front_diameter", coFloat); + def->label = L("Pinhead front diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter of the pointing side of the head"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add(prefix + "support_head_penetration", coFloat); + def->label = L("Head penetration"); + def->category = L("Supports"); + def->tooltip = L("How much the pinhead has to penetrate the model surface"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.2)); + + def = this->add(prefix + "support_head_width", coFloat); + def->label = L("Pinhead width"); + def->category = L("Supports"); + def->tooltip = L("Width from the back sphere center to the front sphere center"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 20; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add(prefix + "support_pillar_diameter", coFloat); + def->label = L("Pillar diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter in mm of the support pillars"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 15; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add(prefix + "support_small_pillar_diameter_percent", coPercent); + def->label = L("Small pillar diameter percent"); + def->category = L("Supports"); + def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter " + "which are used in problematic areas where a normal pilla cannot fit."); + def->sidetext = L("%"); + def->min = 1; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionPercent(50)); + + def = this->add(prefix + "support_max_bridges_on_pillar", coInt); + def->label = L("Max bridges on a pillar"); + def->tooltip = L( + "Maximum number of bridges that can be placed on a pillar. Bridges " + "hold support point pinheads and connect to pillars as small branches."); + def->min = 0; + def->max = 50; + def->mode = comExpert; + def->set_default_value(new ConfigOptionInt(3)); + + def = this->add(prefix + "support_max_weight_on_model", coFloat); + def->label = L("Max weight on model"); + def->category = L("Supports"); + def->tooltip = L( + "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " + "branches emanating from the endpoint."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10.)); + + def = this->add(prefix + "support_pillar_connection_mode", coEnum); + def->label = L("Pillar connection mode"); + def->tooltip = L("Controls the bridge type between two neighboring pillars." + " Can be zig-zag, cross (double zig-zag) or dynamic which" + " will automatically switch between the first two depending" + " on the distance of the two pillars."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = ConfigOptionEnum::get_enum_names(); + def->enum_labels = ConfigOptionEnum::get_enum_names(); + def->enum_labels[0] = L("Zig-Zag"); + def->enum_labels[1] = L("Cross"); + def->enum_labels[2] = L("Dynamic"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic)); + + def = this->add(prefix + "support_buildplate_only", coBool); + def->label = L("Support on build plate only"); + def->category = L("Supports"); + def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add(prefix + "support_pillar_widening_factor", coFloat); + def->label = L("Pillar widening factor"); + def->category = L("Supports"); + def->tooltip = L( + "Merging bridges or pillars into another pillars can " + "increase the radius. Zero means no increase, one means " + "full increase. The exact amount of increase is unspecified and can " + "change in the future. What is garanteed is that thickness will not " + "exceed \"support_base_diameter\""); + + def->min = 0; + def->max = 1; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.5)); + + def = this->add(prefix + "support_base_diameter", coFloat); + def->label = L("Support base diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter in mm of the pillar base"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(4.0)); + + def = this->add(prefix + "support_base_height", coFloat); + def->label = L("Support base height"); + def->category = L("Supports"); + def->tooltip = L("The height of the pillar base cone"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add(prefix + "support_base_safety_distance", coFloat); + def->label = L("Support base safety distance"); + def->category = L("Supports"); + def->tooltip = L( + "The minimum distance of the pillar base from the model in mm. " + "Makes sense in zero elevation mode where a gap according " + "to this parameter is inserted between the model and the pad."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add(prefix + "support_critical_angle", coFloat); + def->label = L("Critical angle"); + def->category = L("Supports"); + def->tooltip = L("The default angle for connecting support sticks and junctions."); + def->sidetext = L("°"); + def->min = 0; + def->max = 90; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(45)); + + def = this->add(prefix + "support_max_bridge_length", coFloat); + def->label = L("Max bridge length"); + def->category = L("Supports"); + def->tooltip = L("The max length of a bridge"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(15.0)); + + def = this->add(prefix + "support_max_pillar_link_distance", coFloat); + def->label = L("Max pillar linking distance"); + def->category = L("Supports"); + def->tooltip = L("The max distance of two pillars to get linked with each other." + " A zero value will prohibit pillar cascading."); + def->sidetext = L("mm"); + def->min = 0; // 0 means no linking + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10.0)); + + def = this->add(prefix + "support_object_elevation", coFloat); + def->label = L("Object elevation"); + def->category = L("Supports"); + def->tooltip = L("How much the supports should lift up the supported object. " + "If \"Pad around object\" is enabled, this value is ignored."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 150; // This is the max height of print on SL1 + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.0)); +} + void PrintConfigDef::init_sla_params() { ConfigOptionDef* def; @@ -3619,185 +3804,8 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); - def = this->add("support_head_front_diameter", coFloat); - def->label = L("Pinhead front diameter"); - def->category = L("Supports"); - def->tooltip = L("Diameter of the pointing side of the head"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("support_head_penetration", coFloat); - def->label = L("Head penetration"); - def->category = L("Supports"); - def->tooltip = L("How much the pinhead has to penetrate the model surface"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.2)); - - def = this->add("support_head_width", coFloat); - def->label = L("Pinhead width"); - def->category = L("Supports"); - def->tooltip = L("Width from the back sphere center to the front sphere center"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 20; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_pillar_diameter", coFloat); - def->label = L("Pillar diameter"); - def->category = L("Supports"); - def->tooltip = L("Diameter in mm of the support pillars"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 15; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_small_pillar_diameter_percent", coPercent); - def->label = L("Small pillar diameter percent"); - def->category = L("Supports"); - def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter " - "which are used in problematic areas where a normal pilla cannot fit."); - def->sidetext = L("%"); - def->min = 1; - def->max = 100; - def->mode = comExpert; - def->set_default_value(new ConfigOptionPercent(50)); - - def = this->add("support_max_bridges_on_pillar", coInt); - def->label = L("Max bridges on a pillar"); - def->tooltip = L( - "Maximum number of bridges that can be placed on a pillar. Bridges " - "hold support point pinheads and connect to pillars as small branches."); - def->min = 0; - def->max = 50; - def->mode = comExpert; - def->set_default_value(new ConfigOptionInt(3)); - - def = this->add("support_max_weight_on_model", coFloat); - def->label = L("Max weight on model"); - def->category = L("Supports"); - def->tooltip = L( - "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " - "branches emanating from the endpoint."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(10.)); - - def = this->add("support_pillar_connection_mode", coEnum); - def->label = L("Pillar connection mode"); - def->tooltip = L("Controls the bridge type between two neighboring pillars." - " Can be zig-zag, cross (double zig-zag) or dynamic which" - " will automatically switch between the first two depending" - " on the distance of the two pillars."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = ConfigOptionEnum::get_enum_names(); - def->enum_labels = ConfigOptionEnum::get_enum_names(); - def->enum_labels[0] = L("Zig-Zag"); - def->enum_labels[1] = L("Cross"); - def->enum_labels[2] = L("Dynamic"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic)); - - def = this->add("support_buildplate_only", coBool); - def->label = L("Support on build plate only"); - def->category = L("Supports"); - def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print."); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_pillar_widening_factor", coFloat); - def->label = L("Pillar widening factor"); - def->category = L("Supports"); - def->tooltip = L( - "Merging bridges or pillars into another pillars can " - "increase the radius. Zero means no increase, one means " - "full increase. The exact amount of increase is unspecified and can " - "change in the future. What is garanteed is that thickness will not " - "exceed \"support_base_diameter\""); - - def->min = 0; - def->max = 1; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0.15)); - - def = this->add("support_base_diameter", coFloat); - def->label = L("Support base diameter"); - def->category = L("Supports"); - def->tooltip = L("Diameter in mm of the pillar base"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 30; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(4.0)); - - def = this->add("support_base_height", coFloat); - def->label = L("Support base height"); - def->category = L("Supports"); - def->tooltip = L("The height of the pillar base cone"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_base_safety_distance", coFloat); - def->label = L("Support base safety distance"); - def->category = L("Supports"); - def->tooltip = L( - "The minimum distance of the pillar base from the model in mm. " - "Makes sense in zero elevation mode where a gap according " - "to this parameter is inserted between the model and the pad."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 10; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("support_critical_angle", coFloat); - def->label = L("Critical angle"); - def->category = L("Supports"); - def->tooltip = L("The default angle for connecting support sticks and junctions."); - def->sidetext = L("°"); - def->min = 0; - def->max = 90; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(45)); - - def = this->add("support_max_bridge_length", coFloat); - def->label = L("Max bridge length"); - def->category = L("Supports"); - def->tooltip = L("The max length of a bridge"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15.0)); - - def = this->add("support_max_pillar_link_distance", coFloat); - def->label = L("Max pillar linking distance"); - def->category = L("Supports"); - def->tooltip = L("The max distance of two pillars to get linked with each other." - " A zero value will prohibit pillar cascading."); - def->sidetext = L("mm"); - def->min = 0; // 0 means no linking - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(10.0)); - - def = this->add("support_object_elevation", coFloat); - def->label = L("Object elevation"); - def->category = L("Supports"); - def->tooltip = L("How much the supports should lift up the supported object. " - "If \"Pad around object\" is enabled, this value is ignored."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 150; // This is the max height of print on SL1 - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5.0)); + init_sla_support_params(""); + init_sla_support_params("branching"); def = this->add("support_points_density_relative", coInt); def->label = L("Support points density"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 47c43148a..5dee704ba 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -185,6 +185,7 @@ private: void init_fff_params(); void init_extruder_option_keys(); void init_sla_params(); + void init_sla_support_params(const std::string &method_prefix); std::vector m_extruder_option_keys; std::vector m_extruder_retract_keys; @@ -891,6 +892,62 @@ PRINT_CONFIG_CLASS_DEFINE( // and the model object's bounding box bottom. Units in mm. ((ConfigOptionFloat, support_object_elevation))/*= 5.0*/ + + // Branching tree + + // Diameter in mm of the pointing side of the head. + ((ConfigOptionFloat, branchingsupport_head_front_diameter))/*= 0.2*/ + + // How much the pinhead has to penetrate the model surface + ((ConfigOptionFloat, branchingsupport_head_penetration))/*= 0.2*/ + + // Width in mm from the back sphere center to the front sphere center. + ((ConfigOptionFloat, branchingsupport_head_width))/*= 1.0*/ + + // Radius in mm of the support pillars. + ((ConfigOptionFloat, branchingsupport_pillar_diameter))/*= 0.8*/ + + // The percentage of smaller pillars compared to the normal pillar diameter + // which are used in problematic areas where a normal pilla cannot fit. + ((ConfigOptionPercent, branchingsupport_small_pillar_diameter_percent)) + + // How much bridge (supporting another pinhead) can be placed on a pillar. + ((ConfigOptionInt, branchingsupport_max_bridges_on_pillar)) + + // How the pillars are bridged together + ((ConfigOptionEnum, branchingsupport_pillar_connection_mode)) + + // Generate only ground facing supports + ((ConfigOptionBool, branchingsupport_buildplate_only)) + + ((ConfigOptionFloat, branchingsupport_max_weight_on_model)) + + ((ConfigOptionFloat, branchingsupport_pillar_widening_factor)) + + // Radius in mm of the pillar base. + ((ConfigOptionFloat, branchingsupport_base_diameter))/*= 2.0*/ + + // The height of the pillar base cone in mm. + ((ConfigOptionFloat, branchingsupport_base_height))/*= 1.0*/ + + // The minimum distance of the pillar base from the model in mm. + ((ConfigOptionFloat, branchingsupport_base_safety_distance)) /*= 1.0*/ + + // The default angle for connecting support sticks and junctions. + ((ConfigOptionFloat, branchingsupport_critical_angle))/*= 45*/ + + // The max length of a bridge in mm + ((ConfigOptionFloat, branchingsupport_max_bridge_length))/*= 15.0*/ + + // The max distance of two pillars to get cross linked. + ((ConfigOptionFloat, branchingsupport_max_pillar_link_distance)) + + // The elevation in Z direction upwards. This is the space between the pad + // and the model object's bounding box bottom. Units in mm. + ((ConfigOptionFloat, branchingsupport_object_elevation))/*= 5.0*/ + + + /////// Following options influence automatic support points placement: ((ConfigOptionInt, support_points_density_relative)) ((ConfigOptionFloat, support_points_minimal_distance)) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 21895f943..6bdf415f9 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -44,29 +44,63 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) scfg.enabled = c.supports_enable.getBool(); scfg.tree_type = c.support_tree_type.value; - scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); - double pillar_r = 0.5 * c.support_pillar_diameter.getFloat(); - scfg.head_back_radius_mm = pillar_r; - scfg.head_fallback_radius_mm = - 0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r; - scfg.head_penetration_mm = c.support_head_penetration.getFloat(); - scfg.head_width_mm = c.support_head_width.getFloat(); - scfg.object_elevation_mm = is_zero_elevation(c) ? - 0. : c.support_object_elevation.getFloat(); - scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; - scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); - scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); - scfg.pillar_connection_mode = c.support_pillar_connection_mode.value; - scfg.ground_facing_only = c.support_buildplate_only.getBool(); - scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); - scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); - scfg.base_height_mm = c.support_base_height.getFloat(); - scfg.pillar_base_safety_distance_mm = - c.support_base_safety_distance.getFloat() < EPSILON ? - scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); - - scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); - scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat(); + + switch(scfg.tree_type) { + case sla::SupportTreeType::Default: { + scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); + double pillar_r = 0.5 * c.support_pillar_diameter.getFloat(); + scfg.head_back_radius_mm = pillar_r; + scfg.head_fallback_radius_mm = + 0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r; + scfg.head_penetration_mm = c.support_head_penetration.getFloat(); + scfg.head_width_mm = c.support_head_width.getFloat(); + scfg.object_elevation_mm = is_zero_elevation(c) ? + 0. : c.support_object_elevation.getFloat(); + scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; + scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); + scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); + scfg.pillar_connection_mode = c.support_pillar_connection_mode.value; + scfg.ground_facing_only = c.support_buildplate_only.getBool(); + scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); + scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); + scfg.base_height_mm = c.support_base_height.getFloat(); + scfg.pillar_base_safety_distance_mm = + c.support_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); + + scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); + scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat(); + break; + } + case sla::SupportTreeType::Branching: + [[fallthrough]]; + case sla::SupportTreeType::Organic:{ + scfg.head_front_radius_mm = 0.5*c.branchingsupport_head_front_diameter.getFloat(); + double pillar_r = 0.5 * c.branchingsupport_pillar_diameter.getFloat(); + scfg.head_back_radius_mm = pillar_r; + scfg.head_fallback_radius_mm = + 0.01 * c.branchingsupport_small_pillar_diameter_percent.getFloat() * pillar_r; + scfg.head_penetration_mm = c.branchingsupport_head_penetration.getFloat(); + scfg.head_width_mm = c.branchingsupport_head_width.getFloat(); + scfg.object_elevation_mm = is_zero_elevation(c) ? + 0. : c.branchingsupport_object_elevation.getFloat(); + scfg.bridge_slope = c.branchingsupport_critical_angle.getFloat() * PI / 180.0 ; + scfg.max_bridge_length_mm = c.branchingsupport_max_bridge_length.getFloat(); + scfg.max_pillar_link_distance_mm = c.branchingsupport_max_pillar_link_distance.getFloat(); + scfg.pillar_connection_mode = c.branchingsupport_pillar_connection_mode.value; + scfg.ground_facing_only = c.branchingsupport_buildplate_only.getBool(); + scfg.pillar_widening_factor = c.branchingsupport_pillar_widening_factor.getFloat(); + scfg.base_radius_mm = 0.5*c.branchingsupport_base_diameter.getFloat(); + scfg.base_height_mm = c.branchingsupport_base_height.getFloat(); + scfg.pillar_base_safety_distance_mm = + c.branchingsupport_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.branchingsupport_base_safety_distance.getFloat(); + + scfg.max_bridges_on_pillar = unsigned(c.branchingsupport_max_bridges_on_pillar.getInt()); + scfg.max_weight_on_model_support = c.branchingsupport_max_weight_on_model.getFloat(); + break; + } + } return scfg; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 952d453a2..9dd854029 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -374,25 +374,45 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) bool is_default_tree = treetype == sla::SupportTreeType::Default; bool is_branching_tree = treetype == sla::SupportTreeType::Branching; - toggle_field("support_head_front_diameter", supports_en); - toggle_field("support_head_penetration", supports_en); - toggle_field("support_head_width", supports_en); - toggle_field("support_pillar_diameter", supports_en); - toggle_field("support_small_pillar_diameter_percent", supports_en); + toggle_field("support_tree_type", supports_en); + + toggle_field("support_head_front_diameter", supports_en && is_default_tree); + toggle_field("support_head_penetration", supports_en && is_default_tree); + toggle_field("support_head_width", supports_en && is_default_tree); + toggle_field("support_pillar_diameter", supports_en && is_default_tree); + toggle_field("support_small_pillar_diameter_percent", supports_en && is_default_tree); toggle_field("support_max_bridges_on_pillar", supports_en && is_default_tree); toggle_field("support_pillar_connection_mode", supports_en && is_default_tree); - toggle_field("support_tree_type", supports_en); - toggle_field("support_buildplate_only", supports_en); - toggle_field("support_base_diameter", supports_en); - toggle_field("support_base_height", supports_en); - toggle_field("support_base_safety_distance", supports_en); - toggle_field("support_critical_angle", supports_en); - toggle_field("support_max_bridge_length", supports_en); + toggle_field("support_buildplate_only", supports_en && is_default_tree); + toggle_field("support_base_diameter", supports_en && is_default_tree); + toggle_field("support_base_height", supports_en && is_default_tree); + toggle_field("support_base_safety_distance", supports_en && is_default_tree); + toggle_field("support_critical_angle", supports_en && is_default_tree); + toggle_field("support_max_bridge_length", supports_en && is_default_tree); toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree); - toggle_field("support_pillar_widening_factor", supports_en && is_branching_tree); - toggle_field("support_max_weight_on_model", supports_en && is_branching_tree); - toggle_field("support_points_density_relative", supports_en); - toggle_field("support_points_minimal_distance", supports_en); + toggle_field("support_pillar_widening_factor", supports_en && is_default_tree); + toggle_field("support_max_weight_on_model", supports_en && is_default_tree); + toggle_field("support_points_density_relative", supports_en && is_default_tree); + toggle_field("support_points_minimal_distance", supports_en && is_default_tree); + + toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree); + toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree); + toggle_field("branchingsupport_head_width", supports_en && is_branching_tree); + toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree); + toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_bridges_on_pillar", supports_en && is_branching_tree); + toggle_field("branchingsupport_pillar_connection_mode", supports_en && is_branching_tree); + toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree); + toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree); + toggle_field("branchingsupport_base_height", supports_en && is_branching_tree); + toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree); + toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_pillar_link_distance", supports_en && is_branching_tree); + toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree); + toggle_field("branchingsupport_points_density_relative", supports_en && is_branching_tree); + toggle_field("branchingsupport_points_minimal_distance", supports_en && is_branching_tree); bool pad_en = config->opt_bool("pad_enable"); @@ -407,7 +427,8 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) bool zero_elev = config->opt_bool("pad_around_object") && pad_en; - toggle_field("support_object_elevation", supports_en && !zero_elev); + toggle_field("support_object_elevation", supports_en && is_default_tree && !zero_elev); + toggle_field("branchingsupport_object_elevation", supports_en && is_branching_tree && !zero_elev); toggle_field("pad_object_gap", zero_elev); toggle_field("pad_around_object_everywhere", zero_elev); toggle_field("pad_object_connector_stride", zero_elev); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 67f603434..60742dd4d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4821,6 +4821,43 @@ void TabSLAMaterial::update() wxGetApp().mainframe->on_config_changed(m_config); } +void TabSLAPrint::build_sla_support_params(const std::string &prefix, + const Slic3r::GUI::PageShp &page) +{ + auto optgroup = page->new_optgroup(L("Support head")); + optgroup->append_single_option_line(prefix + "support_head_front_diameter"); + optgroup->append_single_option_line(prefix + "support_head_penetration"); + optgroup->append_single_option_line(prefix + "support_head_width"); + + optgroup = page->new_optgroup(L("Support pillar")); + optgroup->append_single_option_line(prefix + "support_pillar_diameter"); + optgroup->append_single_option_line(prefix + "support_small_pillar_diameter_percent"); + optgroup->append_single_option_line(prefix + "support_max_bridges_on_pillar"); + + optgroup->append_single_option_line(prefix + "support_pillar_connection_mode"); + optgroup->append_single_option_line(prefix + "support_buildplate_only"); + optgroup->append_single_option_line(prefix + "support_pillar_widening_factor"); + optgroup->append_single_option_line(prefix + "support_max_weight_on_model"); + optgroup->append_single_option_line(prefix + "support_base_diameter"); + optgroup->append_single_option_line(prefix + "support_base_height"); + optgroup->append_single_option_line(prefix + "support_base_safety_distance"); + + // Mirrored parameter from Pad page for toggling elevation on the same page + optgroup->append_single_option_line(prefix + "support_object_elevation"); + + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_support_object_elevation_description_line); + }; + optgroup->append_line(line); + + optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); + optgroup->append_single_option_line(prefix + "support_critical_angle"); + optgroup->append_single_option_line(prefix + "support_max_bridge_length"); + optgroup->append_single_option_line(prefix + "support_max_pillar_link_distance"); +} + void TabSLAPrint::build() { m_presets = &m_preset_bundle->sla_prints; @@ -4833,42 +4870,47 @@ void TabSLAPrint::build() optgroup->append_single_option_line("faded_layers"); page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); + +// page = add_options_page(L("Branching"), "supports"); + optgroup = page->new_optgroup(L("Supports")); optgroup->append_single_option_line("supports_enable"); optgroup->append_single_option_line("support_tree_type"); - optgroup = page->new_optgroup(L("Support head")); - optgroup->append_single_option_line("support_head_front_diameter"); - optgroup->append_single_option_line("support_head_penetration"); - optgroup->append_single_option_line("support_head_width"); + build_sla_support_params("", page); + build_sla_support_params("branching", page); +// optgroup = page->new_optgroup(L("Support head")); +// optgroup->append_single_option_line("support_head_front_diameter"); +// optgroup->append_single_option_line("support_head_penetration"); +// optgroup->append_single_option_line("support_head_width"); - optgroup = page->new_optgroup(L("Support pillar")); - optgroup->append_single_option_line("support_pillar_diameter"); - optgroup->append_single_option_line("support_small_pillar_diameter_percent"); - optgroup->append_single_option_line("support_max_bridges_on_pillar"); +// optgroup = page->new_optgroup(L("Support pillar")); +// optgroup->append_single_option_line("support_pillar_diameter"); +// optgroup->append_single_option_line("support_small_pillar_diameter_percent"); +// optgroup->append_single_option_line("support_max_bridges_on_pillar"); - optgroup->append_single_option_line("support_pillar_connection_mode"); - optgroup->append_single_option_line("support_buildplate_only"); - optgroup->append_single_option_line("support_pillar_widening_factor"); - optgroup->append_single_option_line("support_max_weight_on_model"); - optgroup->append_single_option_line("support_base_diameter"); - optgroup->append_single_option_line("support_base_height"); - optgroup->append_single_option_line("support_base_safety_distance"); +// optgroup->append_single_option_line("support_pillar_connection_mode"); +// optgroup->append_single_option_line("support_buildplate_only"); +// optgroup->append_single_option_line("support_pillar_widening_factor"); +// optgroup->append_single_option_line("support_max_weight_on_model"); +// optgroup->append_single_option_line("support_base_diameter"); +// optgroup->append_single_option_line("support_base_height"); +// optgroup->append_single_option_line("support_base_safety_distance"); - // Mirrored parameter from Pad page for toggling elevation on the same page - optgroup->append_single_option_line("support_object_elevation"); +// // Mirrored parameter from Pad page for toggling elevation on the same page +// optgroup->append_single_option_line("support_object_elevation"); - Line line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_support_object_elevation_description_line); - }; - optgroup->append_line(line); +// Line line{ "", "" }; +// line.full_width = 1; +// line.widget = [this](wxWindow* parent) { +// return description_line_widget(parent, &m_support_object_elevation_description_line); +// }; +// optgroup->append_line(line); - optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); - optgroup->append_single_option_line("support_critical_angle"); - optgroup->append_single_option_line("support_max_bridge_length"); - optgroup->append_single_option_line("support_max_pillar_link_distance"); +// optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); +// optgroup->append_single_option_line("support_critical_angle"); +// optgroup->append_single_option_line("support_max_bridge_length"); +// optgroup->append_single_option_line("support_max_pillar_link_distance"); optgroup = page->new_optgroup(L("Automatic generation")); optgroup->append_single_option_line("support_points_density_relative"); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index f5dd4c522..e95050f33 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -339,7 +339,7 @@ public: void on_roll_back_value(const bool to_sys = false); - PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); + PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); static wxString translate_category(const wxString& title, Preset::Type preset_type); virtual void OnActivate(); @@ -526,6 +526,9 @@ public: class TabSLAPrint : public Tab { + void build_sla_support_params(const std::string &prefix, + const Slic3r::GUI::PageShp &page); + public: TabSLAPrint(wxBookCtrlBase* parent) : Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} From 6238595ac6c80fb57961ee5ed34418b7040d5e8b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Dec 2022 13:14:25 +0100 Subject: [PATCH 119/206] adding separate config values for support tree algorithms Realize config matrix on supports tab --- src/libslic3r/PrintConfig.cpp | 52 ++++++++++++---- src/libslic3r/libslic3r.h | 2 + src/slic3r/GUI/ConfigManipulation.cpp | 17 +++-- src/slic3r/GUI/Tab.cpp | 90 +++++++++++---------------- src/slic3r/GUI/Tab.hpp | 5 +- 5 files changed, 90 insertions(+), 76 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f39857759..926cfd186 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3277,6 +3277,9 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) { ConfigOptionDef* def; + constexpr const char * pretext_unavailable = L("Unavailable for this method.\n"); + std::string pretext; + def = this->add(prefix + "support_head_front_diameter", coFloat); def->label = L("Pinhead front diameter"); def->category = L("Supports"); @@ -3326,20 +3329,28 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->mode = comExpert; def->set_default_value(new ConfigOptionPercent(50)); + pretext = ""; + if (prefix == "branching") + pretext = pretext_unavailable; + def = this->add(prefix + "support_max_bridges_on_pillar", coInt); def->label = L("Max bridges on a pillar"); - def->tooltip = L( + def->tooltip = pretext + L( "Maximum number of bridges that can be placed on a pillar. Bridges " "hold support point pinheads and connect to pillars as small branches."); def->min = 0; def->max = 50; def->mode = comExpert; - def->set_default_value(new ConfigOptionInt(3)); + def->set_default_value(new ConfigOptionInt(prefix == "branching" ? 2 : 3)); + + pretext = ""; + if (prefix.empty()) + pretext = pretext_unavailable; def = this->add(prefix + "support_max_weight_on_model", coFloat); def->label = L("Max weight on model"); def->category = L("Supports"); - def->tooltip = L( + def->tooltip = pretext + L( "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " "branches emanating from the endpoint."); def->sidetext = L("mm"); @@ -3347,9 +3358,13 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(10.)); + pretext = ""; + if (prefix == "branching") + pretext = pretext_unavailable; + def = this->add(prefix + "support_pillar_connection_mode", coEnum); def->label = L("Pillar connection mode"); - def->tooltip = L("Controls the bridge type between two neighboring pillars." + def->tooltip = pretext + L("Controls the bridge type between two neighboring pillars." " Can be zig-zag, cross (double zig-zag) or dynamic which" " will automatically switch between the first two depending" " on the distance of the two pillars."); @@ -3373,12 +3388,16 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def = this->add(prefix + "support_pillar_widening_factor", coFloat); def->label = L("Pillar widening factor"); def->category = L("Supports"); - def->tooltip = L( - "Merging bridges or pillars into another pillars can " + + pretext = ""; + if (prefix.empty()) + pretext = pretext_unavailable; + + def->tooltip = pretext + + L("Merging bridges or pillars into another pillars can " "increase the radius. Zero means no increase, one means " "full increase. The exact amount of increase is unspecified and can " - "change in the future. What is garanteed is that thickness will not " - "exceed \"support_base_diameter\""); + "change in the future."); def->min = 0; def->max = 1; @@ -3434,13 +3453,22 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15.0)); + + double default_val = 15.0; + if (prefix == "branching") + default_val = 5.0; + + def->set_default_value(new ConfigOptionFloat(default_val)); + + pretext = ""; + if (prefix == "branching") + pretext = pretext_unavailable; def = this->add(prefix + "support_max_pillar_link_distance", coFloat); def->label = L("Max pillar linking distance"); def->category = L("Supports"); - def->tooltip = L("The max distance of two pillars to get linked with each other." - " A zero value will prohibit pillar cascading."); + def->tooltip = pretext + L("The max distance of two pillars to get linked with each other." + " A zero value will prohibit pillar cascading."); def->sidetext = L("mm"); def->min = 0; // 0 means no linking def->mode = comAdvanced; @@ -3801,7 +3829,7 @@ void PrintConfigDef::init_sla_params() def->enum_labels[0] = L("Default"); def->enum_labels[1] = L("Branching"); // TODO: def->enum_labels[2] = L("Organic"); - def->mode = comAdvanced; + def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); init_sla_support_params(""); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index d5a21cf21..79945867b 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -380,6 +380,8 @@ inline IntegerOnly fast_round_up(double a) return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5)); } +template using SamePair = std::pair; + } // namespace Slic3r #endif // _libslic3r_h_ diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 9dd854029..85fe3e53e 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -390,29 +390,28 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) toggle_field("support_critical_angle", supports_en && is_default_tree); toggle_field("support_max_bridge_length", supports_en && is_default_tree); toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree); - toggle_field("support_pillar_widening_factor", supports_en && is_default_tree); - toggle_field("support_max_weight_on_model", supports_en && is_default_tree); - toggle_field("support_points_density_relative", supports_en && is_default_tree); - toggle_field("support_points_minimal_distance", supports_en && is_default_tree); + toggle_field("support_pillar_widening_factor", false); + toggle_field("support_max_weight_on_model", false); toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree); toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree); toggle_field("branchingsupport_head_width", supports_en && is_branching_tree); toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree); toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree); - toggle_field("branchingsupport_max_bridges_on_pillar", supports_en && is_branching_tree); - toggle_field("branchingsupport_pillar_connection_mode", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_bridges_on_pillar", false); + toggle_field("branchingsupport_pillar_connection_mode", false); toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree); toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree); toggle_field("branchingsupport_base_height", supports_en && is_branching_tree); toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree); toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree); toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree); - toggle_field("branchingsupport_max_pillar_link_distance", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_pillar_link_distance", false); toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree); toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree); - toggle_field("branchingsupport_points_density_relative", supports_en && is_branching_tree); - toggle_field("branchingsupport_points_minimal_distance", supports_en && is_branching_tree); + + toggle_field("support_points_density_relative", supports_en); + toggle_field("support_points_minimal_distance", supports_en); bool pad_en = config->opt_bool("pad_enable"); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 60742dd4d..481b16222 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4821,29 +4821,46 @@ void TabSLAMaterial::update() wxGetApp().mainframe->on_config_changed(m_config); } -void TabSLAPrint::build_sla_support_params(const std::string &prefix, +static void add_options_into_line(ConfigOptionsGroupShp &optgroup, + const std::vector> &prefixes, + const std::string &optkey) +{ + auto opt = optgroup->get_option(prefixes.front().first + optkey); + Line line{ opt.opt.label, "" }; + line.full_width = 1; + for (auto &prefix : prefixes) { + opt = optgroup->get_option(prefix.first + optkey); + opt.opt.label = prefix.second; + opt.opt.width = 12; // TODO + line.append_option(opt); + } + optgroup->append_line(line); +} + +void TabSLAPrint::build_sla_support_params(const std::vector> &prefixes, const Slic3r::GUI::PageShp &page) { + auto optgroup = page->new_optgroup(L("Support head")); - optgroup->append_single_option_line(prefix + "support_head_front_diameter"); - optgroup->append_single_option_line(prefix + "support_head_penetration"); - optgroup->append_single_option_line(prefix + "support_head_width"); + add_options_into_line(optgroup, prefixes, "support_head_front_diameter"); + add_options_into_line(optgroup, prefixes, "support_head_penetration"); + add_options_into_line(optgroup, prefixes, "support_head_width"); optgroup = page->new_optgroup(L("Support pillar")); - optgroup->append_single_option_line(prefix + "support_pillar_diameter"); - optgroup->append_single_option_line(prefix + "support_small_pillar_diameter_percent"); - optgroup->append_single_option_line(prefix + "support_max_bridges_on_pillar"); + add_options_into_line(optgroup, prefixes, "support_pillar_diameter"); + add_options_into_line(optgroup, prefixes, "support_small_pillar_diameter_percent"); + add_options_into_line(optgroup, prefixes, "support_max_bridges_on_pillar"); - optgroup->append_single_option_line(prefix + "support_pillar_connection_mode"); - optgroup->append_single_option_line(prefix + "support_buildplate_only"); - optgroup->append_single_option_line(prefix + "support_pillar_widening_factor"); - optgroup->append_single_option_line(prefix + "support_max_weight_on_model"); - optgroup->append_single_option_line(prefix + "support_base_diameter"); - optgroup->append_single_option_line(prefix + "support_base_height"); - optgroup->append_single_option_line(prefix + "support_base_safety_distance"); + add_options_into_line(optgroup, prefixes, "support_pillar_connection_mode"); + add_options_into_line(optgroup, prefixes, "support_buildplate_only"); + add_options_into_line(optgroup, prefixes, "support_pillar_widening_factor"); + add_options_into_line(optgroup, prefixes, "support_max_weight_on_model"); + add_options_into_line(optgroup, prefixes, "support_base_diameter"); + add_options_into_line(optgroup, prefixes, "support_base_height"); + add_options_into_line(optgroup, prefixes, "support_base_safety_distance"); // Mirrored parameter from Pad page for toggling elevation on the same page - optgroup->append_single_option_line(prefix + "support_object_elevation"); + add_options_into_line(optgroup, prefixes, "support_object_elevation"); Line line{ "", "" }; line.full_width = 1; @@ -4853,9 +4870,9 @@ void TabSLAPrint::build_sla_support_params(const std::string &prefix, optgroup->append_line(line); optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); - optgroup->append_single_option_line(prefix + "support_critical_angle"); - optgroup->append_single_option_line(prefix + "support_max_bridge_length"); - optgroup->append_single_option_line(prefix + "support_max_pillar_link_distance"); + add_options_into_line(optgroup, prefixes, "support_critical_angle"); + add_options_into_line(optgroup, prefixes, "support_max_bridge_length"); + add_options_into_line(optgroup, prefixes, "support_max_pillar_link_distance"); } void TabSLAPrint::build() @@ -4871,46 +4888,11 @@ void TabSLAPrint::build() page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); -// page = add_options_page(L("Branching"), "supports"); - optgroup = page->new_optgroup(L("Supports")); optgroup->append_single_option_line("supports_enable"); optgroup->append_single_option_line("support_tree_type"); - build_sla_support_params("", page); - build_sla_support_params("branching", page); -// optgroup = page->new_optgroup(L("Support head")); -// optgroup->append_single_option_line("support_head_front_diameter"); -// optgroup->append_single_option_line("support_head_penetration"); -// optgroup->append_single_option_line("support_head_width"); - -// optgroup = page->new_optgroup(L("Support pillar")); -// optgroup->append_single_option_line("support_pillar_diameter"); -// optgroup->append_single_option_line("support_small_pillar_diameter_percent"); -// optgroup->append_single_option_line("support_max_bridges_on_pillar"); - -// optgroup->append_single_option_line("support_pillar_connection_mode"); -// optgroup->append_single_option_line("support_buildplate_only"); -// optgroup->append_single_option_line("support_pillar_widening_factor"); -// optgroup->append_single_option_line("support_max_weight_on_model"); -// optgroup->append_single_option_line("support_base_diameter"); -// optgroup->append_single_option_line("support_base_height"); -// optgroup->append_single_option_line("support_base_safety_distance"); - -// // Mirrored parameter from Pad page for toggling elevation on the same page -// optgroup->append_single_option_line("support_object_elevation"); - -// Line line{ "", "" }; -// line.full_width = 1; -// line.widget = [this](wxWindow* parent) { -// return description_line_widget(parent, &m_support_object_elevation_description_line); -// }; -// optgroup->append_line(line); - -// optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); -// optgroup->append_single_option_line("support_critical_angle"); -// optgroup->append_single_option_line("support_max_bridge_length"); -// optgroup->append_single_option_line("support_max_pillar_link_distance"); + build_sla_support_params({{"", "Default"}, {"branching", "Branching"}}, page); optgroup = page->new_optgroup(L("Automatic generation")); optgroup->append_single_option_line("support_points_density_relative"); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index e95050f33..c060eb7fd 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -526,7 +526,10 @@ public: class TabSLAPrint : public Tab { - void build_sla_support_params(const std::string &prefix, + // Methods are a vector of method prefix -> method label pairs + // method prefix is the prefix whith which all the config values are prefixed + // for a particular method. The label is the friendly name for the method + void build_sla_support_params(const std::vector> &methods, const Slic3r::GUI::PageShp &page); public: From 234167534bcc0597b02feed48530ff7972469c63 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 16:25:31 +0100 Subject: [PATCH 120/206] wip on branching tree avoidance again --- src/libslic3r/BranchingTree/BranchingTree.cpp | 12 +++++- src/libslic3r/BranchingTree/BranchingTree.hpp | 6 +++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 43 +++++++++++++++++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 19 ++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 72ecf8c86..8f1d322a1 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,7 +76,17 @@ void build_tree(PointCloud &nodes, Builder &builder) switch (type) { case BED: { closest_node.weight = w; - if ((routed = builder.add_ground_bridge(node, closest_node))) { + if (closest_it->dst_branching > nodes.properties().max_branch_length()) { + auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; + Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; + new_node.id = int(nodes.next_junction_id()); + new_node.weight = nodes.get(node_id).weight + hl_br_len; + new_node.left = node.id; + if ((routed = builder.add_bridge(node, new_node))) { + size_t new_idx = nodes.insert_junction(new_node); + ptsqueue.push(new_idx); + } + } else if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; nodes.mark_unreachable(closest_node_id); diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 2d372452b..06ee3ee14 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -105,6 +105,12 @@ public: // Add an anchor bridge to the model body virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0; + virtual std::optional suggest_avoidance(const Node &from, + float max_bridge_len) + { + return {}; + } + // Report nodes that can not be routed to an endpoint (model or ground) virtual void report_unroutable(const Node &j) = 0; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index d88bdb1b0..d4778a0ac 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -29,7 +29,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour - static constexpr double WIDENING_SCALE = 0.02; + static constexpr double WIDENING_SCALE = 0.05; double get_radius(const branchingtree::Node &j) const { @@ -99,9 +99,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { int suppid_left = branchingtree::Node::ID_NONE; int suppid_right = branchingtree::Node::ID_NONE; + double glvl = ground_level(m_sm); branchingtree::Node dst = node; - dst.weight += node.pos.z(); - dst.Rmin = std::max(node.Rmin, dst.Rmin); + dst.pos.z() = glvl; + dst.weight += node.pos.z() - glvl; if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) ret.to_left = false; @@ -144,8 +145,18 @@ public: bool add_mesh_bridge(const branchingtree::Node &from, const branchingtree::Node &to) override; + std::optional suggest_avoidance(const branchingtree::Node &from, + float max_bridge_len) override;; + void report_unroutable(const branchingtree::Node &j) override { + double glvl = ground_level(m_sm); + branchingtree::Node dst = j; + dst.pos.z() = glvl; + dst.weight += j.pos.z() - glvl; + if (add_ground_bridge(j, dst)) + return; + BOOST_LOG_TRIVIAL(warning) << "Cannot route junction at " << j.pos.x() << " " << j.pos.y() << " " << j.pos.z(); @@ -279,6 +290,32 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, return bool(anchor); } +static std::optional get_avoidance(const GroundConnection &conn, + float maxdist) +{ + return {}; +} + +std::optional BranchingTreeBuilder::suggest_avoidance( + const branchingtree::Node &from, float max_bridge_len) +{ + double glvl = ground_level(m_sm); + branchingtree::Node dst = from; + dst.pos.z() = glvl; + dst.weight += from.pos.z() - glvl; + bool succ = add_ground_bridge(from, dst); + + std::optional ret; + + if (succ) { + auto it = m_gnd_connections.find(from.id); + if (it != m_gnd_connections.end()) + ret = get_avoidance(it->second, max_bridge_len); + } + + return ret; +} + inline void build_pillars(SupportTreeBuilder &builder, BranchingTreeBuilder &vbuilder, const SupportableMesh &sm) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index a47f8d662..6c1707953 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -547,7 +547,7 @@ Vec3d check_ground_route( auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + if (gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 6bdf415f9..d991dfe05 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -862,6 +862,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector Date: Mon, 2 Jan 2023 17:48:41 +0100 Subject: [PATCH 121/206] Still WIP on branching tree avoidance --- src/libslic3r/BranchingTree/BranchingTree.cpp | 24 ++++++-- src/libslic3r/SLA/BranchingTreeSLA.cpp | 55 ++++++++++++++++--- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 8f1d322a1..01f4b2724 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,16 +76,28 @@ void build_tree(PointCloud &nodes, Builder &builder) switch (type) { case BED: { closest_node.weight = w; - if (closest_it->dst_branching > nodes.properties().max_branch_length()) { - auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; - Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; - new_node.id = int(nodes.next_junction_id()); - new_node.weight = nodes.get(node_id).weight + hl_br_len; - new_node.left = node.id; + double max_br_len = nodes.properties().max_branch_length(); + if (closest_it->dst_branching > max_br_len) { + std::optional avo = builder.suggest_avoidance(node, max_br_len); + if (!avo) + break; + + Node new_node {*avo, node.Rmin}; + new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).squaredNorm(); + new_node.left = node.id; if ((routed = builder.add_bridge(node, new_node))) { size_t new_idx = nodes.insert_junction(new_node); ptsqueue.push(new_idx); } +// auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; +// Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; +// new_node.id = int(nodes.next_junction_id()); +// new_node.weight = nodes.get(node_id).weight + hl_br_len; +// new_node.left = node.id; +// if ((routed = builder.add_bridge(node, new_node))) { +// size_t new_idx = nodes.insert_junction(new_node); +// ptsqueue.push(new_idx); +// } } else if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index d4778a0ac..7f4b9853c 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -146,7 +146,7 @@ public: const branchingtree::Node &to) override; std::optional suggest_avoidance(const branchingtree::Node &from, - float max_bridge_len) override;; + float max_bridge_len) override; void report_unroutable(const branchingtree::Node &j) override { @@ -293,27 +293,64 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, static std::optional get_avoidance(const GroundConnection &conn, float maxdist) { - return {}; + std::optional ret; + + if (conn) { + if (conn.path.size() > 1) { + ret = conn.path[1].pos.cast(); + } else { + Vec3f pbeg = conn.path[0].pos.cast(); + Vec3f pend = conn.pillar_base->pos.cast(); + pbeg.z() = std::max(pbeg.z() - maxdist, pend.z()); + ret = pbeg; + } + } + + return ret; } std::optional BranchingTreeBuilder::suggest_avoidance( const branchingtree::Node &from, float max_bridge_len) { + std::optional ret; + double glvl = ground_level(m_sm); branchingtree::Node dst = from; dst.pos.z() = glvl; dst.weight += from.pos.z() - glvl; - bool succ = add_ground_bridge(from, dst); + sla::Junction j{from.pos.cast(), get_radius(from)}; - std::optional ret; +// auto found_it = m_ground_mem.find(from.id); +// if (found_it != m_ground_mem.end()) { +// // TODO look up the conn object +// } +// else if (auto conn = deepsearch_ground_connection( +// beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN)) { +// ret = get_avoidance(conn, max_bridge_len); +// } - if (succ) { - auto it = m_gnd_connections.find(from.id); - if (it != m_gnd_connections.end()) - ret = get_avoidance(it->second, max_bridge_len); - } + auto conn = deepsearch_ground_connection( + beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); + + ret = get_avoidance(conn, max_bridge_len); return ret; + +// double glvl = ground_level(m_sm); +// branchingtree::Node dst = from; +// dst.pos.z() = glvl; +// dst.weight += from.pos.z() - glvl; +// bool succ = add_ground_bridge(from, dst); + +// std::optional ret; + +// if (succ) { +// auto it = m_gnd_connections.find(m_pillars.size() - 1); +// if (it != m_gnd_connections.end()) +// ret = get_avoidance(it->second, max_bridge_len); +// } + +// return ret; } inline void build_pillars(SupportTreeBuilder &builder, From 816371f37ce7d8abdf6385df82f01b4938f230d7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 3 Jan 2023 17:51:20 +0100 Subject: [PATCH 122/206] Use avoidance suggestion when ground point is too far --- src/libslic3r/BranchingTree/BranchingTree.cpp | 11 +--- src/libslic3r/SLA/BranchingTreeSLA.cpp | 63 ++++++------------- src/libslic3r/SLA/SupportTreeUtils.hpp | 3 +- 3 files changed, 23 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 01f4b2724..98261311b 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -83,21 +83,12 @@ void build_tree(PointCloud &nodes, Builder &builder) break; Node new_node {*avo, node.Rmin}; - new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).squaredNorm(); + new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).norm(); new_node.left = node.id; if ((routed = builder.add_bridge(node, new_node))) { size_t new_idx = nodes.insert_junction(new_node); ptsqueue.push(new_idx); } -// auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; -// Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; -// new_node.id = int(nodes.next_junction_id()); -// new_node.weight = nodes.get(node_id).weight + hl_br_len; -// new_node.left = node.id; -// if ((routed = builder.add_bridge(node, new_node))) { -// size_t new_idx = nodes.insert_junction(new_node); -// ptsqueue.push(new_idx); -// } } else if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 7f4b9853c..e851fd437 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,12 +20,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { const SupportableMesh &m_sm; const branchingtree::PointCloud &m_cloud; - std::set m_ground_mem; - std::vector m_pillars; // to put an index over them // cache succesfull ground connections - std::map m_gnd_connections; + std::map m_gnd_connections; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -162,6 +160,7 @@ public: // Discard all the support points connecting to this branch. discard_subtree_rescure(j.id); +// discard_subtree(j.id); } const std::vector& unroutable_pinheads() const @@ -177,7 +176,7 @@ public: { const GroundConnection *ret = nullptr; - auto it = m_gnd_connections.find(pillar); + auto it = m_gnd_connections.find(m_pillars[pillar].id); if (it != m_gnd_connections.end()) ret = &it->second; @@ -230,25 +229,23 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; - auto it = m_ground_mem.find(from.id); - if (it == m_ground_mem.end()) { + auto it = m_gnd_connections.find(from.id); + if (it == m_gnd_connections.end()) { sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j, get_radius(to), init_dir); - if (conn) { - m_pillars.emplace_back(from); - m_gnd_connections[m_pillars.size() - 1] = conn; - - ret = true; - } - // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because // it is unlikely that search_ground_route would find a better solution - m_ground_mem.insert(from.id); + m_gnd_connections[from.id] = conn; + + if (conn) { + m_pillars.emplace_back(from); + ret = true; + } } if (ret) { @@ -320,37 +317,17 @@ std::optional BranchingTreeBuilder::suggest_avoidance( dst.weight += from.pos.z() - glvl; sla::Junction j{from.pos.cast(), get_radius(from)}; -// auto found_it = m_ground_mem.find(from.id); -// if (found_it != m_ground_mem.end()) { -// // TODO look up the conn object -// } -// else if (auto conn = deepsearch_ground_connection( -// beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN)) { -// ret = get_avoidance(conn, max_bridge_len); -// } - - auto conn = deepsearch_ground_connection( - beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); - - ret = get_avoidance(conn, max_bridge_len); + auto found_it = m_gnd_connections.find(from.id); + if (found_it != m_gnd_connections.end()) { + ret = get_avoidance(found_it->second, max_bridge_len); + } else { + auto conn = deepsearch_ground_connection( + beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); + m_gnd_connections[from.id] = conn; + ret = get_avoidance(conn, max_bridge_len); + } return ret; - -// double glvl = ground_level(m_sm); -// branchingtree::Node dst = from; -// dst.pos.z() = glvl; -// dst.weight += from.pos.z() - glvl; -// bool succ = add_ground_bridge(from, dst); - -// std::optional ret; - -// if (succ) { -// auto it = m_gnd_connections.find(m_pillars.size() - 1); -// if (it != m_gnd_connections.end()) -// ret = get_avoidance(it->second, max_bridge_len); -// } - -// return ret; } inline void build_pillars(SupportTreeBuilder &builder, diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 6c1707953..8f27ab914 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -646,6 +646,7 @@ GroundConnection deepsearch_ground_connection( // Extract and apply the result auto [plr, azm, bridge_l] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); + assert(std::abs(n.norm() - 1.) < EPSILON); // Now the optimizer gave a possible route to ground with a bridge direction // and length. This length can be shortened further by brute-force queries @@ -654,7 +655,7 @@ GroundConnection deepsearch_ground_connection( // constraint, but it would not find quickly enough an accurate solution, // and it would be very hard to define a stop score which is very useful in // terminating the search as soon as the ground is found. - double l = 0., l_max = bridge_l; + double l = 0., l_max = sm.cfg.max_bridge_length_mm; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { From 9a33537b1d3e0f3a1f8d58b2b859d694dd4a80cb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 4 Jan 2023 11:27:24 +0100 Subject: [PATCH 123/206] Slight performance improvement With parallel avoidance search for leaf nodes --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/SLA/BranchingTreeSLA.cpp | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 06ee3ee14..7e59e6f1a 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -106,7 +106,7 @@ public: virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0; virtual std::optional suggest_avoidance(const Node &from, - float max_bridge_len) + float max_bridge_len) const { return {}; } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index e851fd437..b3c7d2944 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -23,7 +23,8 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::vector m_pillars; // to put an index over them // cache succesfull ground connections - std::map m_gnd_connections; + mutable std::map m_gnd_connections; + mutable execution::SpinningMutex m_gnd_connections_mtx; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -144,7 +145,7 @@ public: const branchingtree::Node &to) override; std::optional suggest_avoidance(const branchingtree::Node &from, - float max_bridge_len) override; + float max_bridge_len) const override; void report_unroutable(const branchingtree::Node &j) override { @@ -307,7 +308,7 @@ static std::optional get_avoidance(const GroundConnection &conn, } std::optional BranchingTreeBuilder::suggest_avoidance( - const branchingtree::Node &from, float max_bridge_len) + const branchingtree::Node &from, float max_bridge_len) const { std::optional ret; @@ -317,13 +318,23 @@ std::optional BranchingTreeBuilder::suggest_avoidance( dst.weight += from.pos.z() - glvl; sla::Junction j{from.pos.cast(), get_radius(from)}; - auto found_it = m_gnd_connections.find(from.id); + auto found_it = m_gnd_connections.end(); + { + std::lock_guard lk{m_gnd_connections_mtx}; + found_it = m_gnd_connections.find(from.id); + } + if (found_it != m_gnd_connections.end()) { ret = get_avoidance(found_it->second, max_bridge_len); } else { auto conn = deepsearch_ground_connection( beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); - m_gnd_connections[from.id] = conn; + + { + std::lock_guard lk{m_gnd_connections_mtx}; + m_gnd_connections[from.id] = conn; + } + ret = get_avoidance(conn, max_bridge_len); } @@ -394,6 +405,15 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s std::move(leafs), props}; BranchingTreeBuilder vbuilder{builder, sm, nodes}; + + execution::for_each(ex_tbb, + size_t(0), + nodes.get_leafs().size(), + [&nodes, &vbuilder](size_t leaf_idx) { + vbuilder.suggest_avoidance(nodes.get_leafs()[leaf_idx], + nodes.properties().max_branch_length()); + }); + branchingtree::build_tree(nodes, vbuilder); build_pillars(builder, vbuilder, sm); From 32e323c64ca7e81d8eff0fd3da5198a01b484539 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 4 Jan 2023 12:59:05 +0100 Subject: [PATCH 124/206] Fix supports below ground --- src/libslic3r/SLA/SupportTreeUtils.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 8f27ab914..a7220ce0e 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -517,6 +517,11 @@ Vec3d check_ground_route( const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); + // Intersection of the suggested bridge with ground plane. If the bridge + // spans below ground, stop it at ground level. + double t = (gndlvl - source.pos.z()) / dir.z(); + bridge_len = std::min(t, bridge_len); + Vec3d bridge_end = source.pos + bridge_len * dir; double down_l = bridge_end.z() - gndlvl; @@ -648,6 +653,9 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); assert(std::abs(n.norm() - 1.) < EPSILON); + double t = (gndlvl - source.pos.z()) / n.z(); + bridge_l = std::min(t, bridge_l); + // Now the optimizer gave a possible route to ground with a bridge direction // and length. This length can be shortened further by brute-force queries // of free route straigt down for a possible pillar. @@ -655,7 +663,7 @@ GroundConnection deepsearch_ground_connection( // constraint, but it would not find quickly enough an accurate solution, // and it would be very hard to define a stop score which is very useful in // terminating the search as soon as the ground is found. - double l = 0., l_max = sm.cfg.max_bridge_length_mm; + double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { From aec0c4a0dcc7a0491f88b4cac9a3674212aa9437 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 12:40:09 +0100 Subject: [PATCH 125/206] Fix sidebar combobox behavior for support routing "support_buildplate_only" was toggled only for default supports --- src/libslic3r/PrintConfig.cpp | 17 +++++++++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 7 +++++-- src/slic3r/GUI/Tab.cpp | 7 +++++-- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 926cfd186..cf2753b18 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4802,6 +4802,23 @@ Points get_bed_shape(const PrintConfig &cfg) Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); } +std::string get_sla_suptree_prefix(const DynamicPrintConfig &config) +{ + const auto *suptreetype = config.option>("support_tree_type"); + std::string slatree = ""; + if (suptreetype) { + auto ttype = static_cast(suptreetype->getInt()); + switch (ttype) { + case sla::SupportTreeType::Branching: slatree = "branching"; break; + case sla::SupportTreeType::Organic: slatree = "organic"; break; + default: + ; + } + } + + return slatree; +} + } // namespace Slic3r #include diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5dee704ba..ca6679051 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1167,6 +1167,8 @@ Points get_bed_shape(const DynamicPrintConfig &cfg); Points get_bed_shape(const PrintConfig &cfg); Points get_bed_shape(const SLAPrinterConfig &cfg); +std::string get_sla_suptree_prefix(const DynamicPrintConfig &config); + // ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. // Each change of ModelConfig is tracked by assigning a new timestamp from a global counter. // The counter is used for faster synchronization of the background slicing thread diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1ab23b344..0da9e5b83 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -553,10 +553,13 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : const bool supports_enable = selection == _("None") ? false : true; new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); + std::string treetype = get_sla_suptree_prefix(new_conf); + if (selection == _("Everywhere")) - new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); + new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(false)); else if (selection == _("Support on build plate only")) - new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); + new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(true)); + } tab->load_config(new_conf); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 481b16222..ad7b92e2e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1038,8 +1038,11 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff) { + std::string slatree = is_fff ? "" : get_sla_suptree_prefix(config); + const std::string support = is_fff ? "support_material" : "supports_enable"; - const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only"; + const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : slatree + "support_buildplate_only"; + return ! config.opt_bool(support) ? _("None") : @@ -1082,7 +1085,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (is_fff ? (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : - (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) + (opt_key == "supports_enable" || opt_key == "support_tree_type" || opt_key == get_sla_suptree_prefix(*m_config) + "support_buildplate_only")) og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object")) From add0f89728e35aab50aa5784c2c46bd466260eb3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:20:35 +0100 Subject: [PATCH 126/206] Fix floating point divisions by zero when ground route has no bridge --- src/libslic3r/SLA/SupportTreeUtils.hpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index a7220ce0e..472b9ec40 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -125,8 +125,11 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface Beam_(const Ball &src_ball, const Ball &dst_ball) : src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R} { - r2 = src_ball.R - + (dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); + r2 = src_ball.R; + auto d = distance(src_ball.p, dst_ball.p); + + if (d > EPSILON) + r2 += (dst_ball.R - src_ball.R) / d; } Beam_(const Vec3d &s, const Vec3d &d, double R) @@ -542,7 +545,7 @@ Vec3d check_ground_route( if (brhit_dist < bridge_len) { ret = (source.pos + brhit_dist * dir); - } else { + } else if (down_l > 0.) { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; double end_radius = wideningfn( @@ -565,6 +568,8 @@ Vec3d check_ground_route( } ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d}; + } else { + ret = bridge_end; } return ret; @@ -709,6 +714,9 @@ GroundConnection deepsearch_ground_connection(Ex policy, { double gndlvl = ground_level(sm); auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) { + if (len < EPSILON) + return src.R; + Vec3d dst = src.p + len * dir; double widening = end_radius - src.R; double zlen = dst.z() - gndlvl; From f72984f18ef48a544188b980ff32532caa04bb22 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:21:05 +0100 Subject: [PATCH 127/206] Fix broken caching of pillar routes --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index b3c7d2944..bb9712616 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -231,6 +231,8 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; auto it = m_gnd_connections.find(from.id); + const GroundConnection *connptr = nullptr; + if (it == m_gnd_connections.end()) { sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); @@ -241,15 +243,14 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because // it is unlikely that search_ground_route would find a better solution - m_gnd_connections[from.id] = conn; - - if (conn) { - m_pillars.emplace_back(from); - ret = true; - } + connptr = &(m_gnd_connections[from.id] = conn); + } else { + connptr = &(it->second); } - if (ret) { + if (connptr && *connptr) { + m_pillars.emplace_back(from); + ret = true; build_subtree(from.id); } From 47a824d1310b83ffb7156e98d4698bc4e30eb1a3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:23:55 +0100 Subject: [PATCH 128/206] Remove unused member in DefaultSupportTree Also fix for loop that is copying int vector in each iteration --- src/libslic3r/SLA/DefaultSupportTree.cpp | 3 +-- src/libslic3r/SLA/DefaultSupportTree.hpp | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 53475542a..a0a12d32b 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -372,7 +372,6 @@ void DefaultSupportTree::add_pinheads() PtIndices filtered_indices; filtered_indices.reserve(aliases.size()); m_iheads.reserve(aliases.size()); - m_iheadless.reserve(aliases.size()); for(auto& a : aliases) { // Here we keep only the front point of the cluster. filtered_indices.emplace_back(a.front()); @@ -602,7 +601,7 @@ void DefaultSupportTree::routing_to_ground() // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; - for (auto cl : m_pillar_clusters) { + for (const std::vector &cl : m_pillar_clusters) { m_thr(); auto cidx = cl_centroids[ci++]; diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index ee58e9ded..08990a8df 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -62,7 +62,6 @@ class DefaultSupportTree { PtIndices m_iheads; // support points with pinhead PtIndices m_iheads_onmodel; - PtIndices m_iheadless; // headless support points std::map m_head_to_ground_scans; From 8207433b81e1afe176cca8a7fba8df79590161b4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:24:41 +0100 Subject: [PATCH 129/206] Fix up whitespace for comments in DefaultSupportTree This commit only deals with white space --- src/libslic3r/SLA/DefaultSupportTree.cpp | 143 +++++++++++------------ 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index a0a12d32b..429cd45c6 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -365,8 +365,8 @@ void DefaultSupportTree::add_pinheads() // The minimum distance for two support points to remain valid. const double /*constexpr*/ D_SP = 0.1; - // Get the points that are too close to each other and keep only the - // first one + // Get the points that are too close to each other and keep only the + // first one auto aliases = cluster(m_points, D_SP, 2); PtIndices filtered_indices; @@ -377,7 +377,7 @@ void DefaultSupportTree::add_pinheads() filtered_indices.emplace_back(a.front()); } - // calculate the normals to the triangles for filtered points + // calculate the normals to the triangles for filtered points auto nmls = normals(suptree_ex_policy, m_points, m_sm.emesh, m_sm.cfg.head_front_radius_mm, m_thr, filtered_indices); @@ -406,22 +406,22 @@ void DefaultSupportTree::add_pinheads() Vec3d n = nmls.row(Eigen::Index(i)); - // for all normals we generate the spherical coordinates and - // saturate the polar angle to 45 degrees from the bottom then - // convert back to standard coordinates to get the new normal. - // Then we just create a quaternion from the two normals - // (Quaternion::FromTwoVectors) and apply the rotation to the - // arrow head. + // for all normals we generate the spherical coordinates and + // saturate the polar angle to 45 degrees from the bottom then + // convert back to standard coordinates to get the new normal. + // Then we just create a quaternion from the two normals + // (Quaternion::FromTwoVectors) and apply the rotation to the + // arrow head. auto [polar, azimuth] = dir_to_spheric(n); - // skip if the tilt is not sane + // skip if the tilt is not sane if (polar < PI - m_sm.cfg.normal_cutoff_angle) return; - // We saturate the polar angle to 3pi/4 + // We saturate the polar angle to 3pi/4 polar = std::max(polar, PI - m_sm.cfg.bridge_slope); - // save the head (pinpoint) position + // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); double lmin = m_sm.cfg.head_width_mm, lmax = lmin; @@ -430,18 +430,17 @@ void DefaultSupportTree::add_pinheads() lmin = 0., lmax = m_sm.cfg.head_penetration_mm; } - // The distance needed for a pinhead to not collide with model. + // The distance needed for a pinhead to not collide with model. double w = lmin + 2 * back_r + 2 * m_sm.cfg.head_front_radius_mm - m_sm.cfg.head_penetration_mm; double pin_r = double(m_sm.pts[fidx].head_front_radius); - // Reassemble the now corrected normal + // Reassemble the now corrected normal auto nn = spheric_to_dir(polar, azimuth).normalized(); - // check available distance - AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, - back_r, w); + // check available distance + AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, back_r, w); if (t.distance() < w) { // Let's try to optimize this angle, there might be a @@ -511,10 +510,10 @@ void DefaultSupportTree::classify() ground_head_indices.reserve(m_iheads.size()); m_iheads_onmodel.reserve(m_iheads.size()); - // First we decide which heads reach the ground and can be full - // pillars and which shall be connected to the model surface (or - // search a suitable path around the surface that leads to the - // ground -- TODO) + // First we decide which heads reach the ground and can be full + // pillars and which shall be connected to the model surface (or + // search a suitable path around the surface that leads to the + // ground -- TODO) for(unsigned i : m_iheads) { m_thr(); @@ -532,10 +531,10 @@ void DefaultSupportTree::classify() m_head_to_ground_scans[i] = hit; } - // We want to search for clusters of points that are far enough - // from each other in the XY plane to not cross their pillar bases - // These clusters of support points will join in one pillar, - // possibly in their centroid support point. + // We want to search for clusters of points that are far enough + // from each other in the XY plane to not cross their pillar bases + // These clusters of support points will join in one pillar, + // possibly in their centroid support point. auto pointfn = [this](unsigned i) { return m_builder.head(i).junction_point(); @@ -561,13 +560,13 @@ void DefaultSupportTree::routing_to_ground() for (auto &cl : m_pillar_clusters) { m_thr(); - // place all the centroid head positions into the index. We - // will query for alternative pillar positions. If a sidehead - // cannot connect to the cluster centroid, we have to search - // for another head with a full pillar. Also when there are two - // elements in the cluster, the centroid is arbitrary and the - // sidehead is allowed to connect to a nearby pillar to - // increase structural stability. + // place all the centroid head positions into the index. We + // will query for alternative pillar positions. If a sidehead + // cannot connect to the cluster centroid, we have to search + // for another head with a full pillar. Also when there are two + // elements in the cluster, the centroid is arbitrary and the + // sidehead is allowed to connect to a nearby pillar to + // increase structural stability. if (cl.empty()) continue; @@ -597,9 +596,9 @@ void DefaultSupportTree::routing_to_ground() } } - // now we will go through the clusters ones again and connect the - // sidepoints with the cluster centroid (which is a ground pillar) - // or a nearby pillar if the centroid is unreachable. + // now we will go through the clusters ones again and connect the + // sidepoints with the cluster centroid (which is a ground pillar) + // or a nearby pillar if the centroid is unreachable. size_t ci = 0; for (const std::vector &cl : m_pillar_clusters) { m_thr(); @@ -665,10 +664,10 @@ bool DefaultSupportTree::connect_to_model_body(Head &head) zangle = std::max(zangle, PI/4); double h = std::sin(zangle) * head.fullwidth(); - // The width of the tail head that we would like to have... + // The width of the tail head that we would like to have... h = std::min(hit.distance() - head.r_back_mm, h); - // If this is a mini pillar dont bother with the tail width, can be 0. + // If this is a mini pillar dont bother with the tail width, can be 0. if (head.r_back_mm < m_sm.cfg.head_back_radius_mm) h = std::max(h, 0.); else if (h <= 0.) return false; @@ -774,23 +773,23 @@ void DefaultSupportTree::interconnect_pillars() // Ideally every pillar should be connected with at least one of its // neighbors if that neighbor is within max_pillar_link_distance - // Pillars with height exceeding H1 will require at least one neighbor - // to connect with. Height exceeding H2 require two neighbors. + // Pillars with height exceeding H1 will require at least one neighbor + // to connect with. Height exceeding H2 require two neighbors. double H1 = m_sm.cfg.max_solo_pillar_height_mm; double H2 = m_sm.cfg.max_dual_pillar_height_mm; double d = m_sm.cfg.max_pillar_link_distance_mm; - //A connection between two pillars only counts if the height ratio is - // bigger than 50% + // A connection between two pillars only counts if the height ratio is + // bigger than 50% double min_height_ratio = 0.5; std::set pairs; - // A function to connect one pillar with its neighbors. THe number of - // neighbors is given in the configuration. This function if called - // for every pillar in the pillar index. A pair of pillar will not - // be connected multiple times this is ensured by the 'pairs' set which - // remembers the processed pillar pairs + // A function to connect one pillar with its neighbors. THe number of + // neighbors is given in the configuration. This function if called + // for every pillar in the pillar index. A pair of pillar will not + // be connected multiple times this is ensured by the 'pairs' set which + // remembers the processed pillar pairs auto cascadefn = [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { @@ -798,10 +797,10 @@ void DefaultSupportTree::interconnect_pillars() const Pillar& pillar = m_builder.pillar(el.second); // actual pillar - // Get the max number of neighbors a pillar should connect to + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_sm.cfg.pillar_cascade_neighbors; - // connections are already enough for the pillar + // connections are already enough for the pillar if(pillar.links >= neighbors) return; double max_d = d * pillar.r_start / m_sm.cfg.head_back_radius_mm; @@ -810,7 +809,7 @@ void DefaultSupportTree::interconnect_pillars() return distance(e.first, qp) < max_d; }); - // sort the result by distance (have to check if this is needed) + // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); @@ -822,23 +821,23 @@ void DefaultSupportTree::interconnect_pillars() auto a = el.second, b = re.second; - // Get unique hash for the given pair (order doesn't matter) + // Get unique hash for the given pair (order doesn't matter) auto hashval = pairhash(a, b); - // Search for the pair amongst the remembered pairs + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; const Pillar& neighborpillar = m_builder.pillar(re.second); - // this neighbor is occupied, skip + // this neighbor is occupied, skip if (neighborpillar.links >= neighbors) continue; if (neighborpillar.r_start < pillar.r_start) continue; if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); - // If the interconnection length between the two pillars is - // less than 50% of the longer pillar's height, don't count + // If the interconnection length between the two pillars is + // less than 50% of the longer pillar's height, don't count if(pillar.height < H1 || neighborpillar.height / pillar.height > min_height_ratio) m_builder.increment_links(pillar); @@ -849,30 +848,30 @@ void DefaultSupportTree::interconnect_pillars() } - // connections are enough for one pillar + // connections are enough for one pillar if(pillar.links >= neighbors) break; } }; - // Run the cascade for the pillars in the index + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - // We would be done here if we could allow some pillars to not be - // connected with any neighbors. But this might leave the support tree - // unprintable. - // - // The current solution is to insert additional pillars next to these - // lonely pillars. One or even two additional pillar might get inserted - // depending on the length of the lonely pillar. + // We would be done here if we could allow some pillars to not be + // connected with any neighbors. But this might leave the support tree + // unprintable. + // + // The current solution is to insert additional pillars next to these + // lonely pillars. One or even two additional pillar might get inserted + // depending on the length of the lonely pillar. size_t pillarcount = m_builder.pillarcount(); - // Again, go through all pillars, this time in the whole support tree - // not just the index. + // Again, go through all pillars, this time in the whole support tree + // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_builder.pillar(pid); }; - // Decide how many additional pillars will be needed: + // Decide how many additional pillars will be needed: unsigned needpillars = 0; if (pillar().bridges > m_sm.cfg.max_bridges_on_pillar) @@ -888,17 +887,17 @@ void DefaultSupportTree::interconnect_pillars() needpillars = std::max(pillar().links, needpillars) - pillar().links; if (needpillars == 0) continue; - // Search for new pillar locations: + // Search for new pillar locations: bool found = false; double alpha = 0; // goes to 2Pi double r = 2 * m_sm.cfg.base_radius_mm; Vec3d pillarsp = pillar().startpoint(); - // temp value for starting point detection + // temp value for starting point detection Vec3d sp(pillarsp.x(), pillarsp.y(), pillarsp.z() - r); - // A vector of bool for placement feasbility + // A vector of bool for placement feasbility std::vector canplace(needpillars, false); std::vector spts(needpillars); // vector of starting points @@ -917,12 +916,12 @@ void DefaultSupportTree::interconnect_pillars() s.y() += std::sin(a) * r; spts[n] = s; - // Check the path vertically down + // Check the path vertically down Vec3d check_from = s + Vec3d{0., 0., pillar().r_start}; auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r_start); Vec3d gndsp{s.x(), s.y(), gnd}; - // If the path is clear, check for pillar base collisions + // If the path is clear, check for pillar base collisions canplace[n] = std::isinf(hr.distance()) && std::sqrt(m_sm.emesh.squared_distance(gndsp)) > min_dist; @@ -931,7 +930,7 @@ void DefaultSupportTree::interconnect_pillars() found = std::all_of(canplace.begin(), canplace.end(), [](bool v) { return v; }); - // 20 angles will be tried... + // 20 angles will be tried... alpha += 0.1 * PI; } From 4620dd5a3d37d4f48a85c5ee2a7f52a2305c820e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 16:04:48 +0100 Subject: [PATCH 130/206] WIP on small pillar fixes --- src/libslic3r/SLA/SupportTreeUtils.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 472b9ec40..e4c03da6c 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -469,6 +469,9 @@ inline long build_ground_connection(SupportTreeBuilder &builder, gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); + if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) + h += sm.pad_cfg.wall_thickness_mm; + // TODO: does not work yet // if (conn.path.back().id < 0) { // // this is a head @@ -477,7 +480,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // } else ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); - builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + if (conn.pillar_base->r_top >= sm.cfg.head_back_radius_mm) + builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); return ret; } @@ -555,7 +559,7 @@ Vec3d check_ground_route( auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) { + if (source.r >= sm.cfg.head_back_radius_mm && gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); From d6fe5767e0d59e56c643707102fb8259733664f9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 17 Jan 2023 16:41:27 +0100 Subject: [PATCH 131/206] Small supports now go through the pad to always reach the bed They will not hang in the air if they end up in the gap between the "around object pad" and the object --- src/libslic3r/SLA/SupportTreeUtils.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index e4c03da6c..93f370c32 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -469,8 +469,10 @@ inline long build_ground_connection(SupportTreeBuilder &builder, gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); - if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) + if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) { h += sm.pad_cfg.wall_thickness_mm; + gp.z() -= sm.pad_cfg.wall_thickness_mm; + } // TODO: does not work yet // if (conn.path.back().id < 0) { @@ -478,7 +480,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // long head_id = std::abs(conn.path.back().id); // ret = builder.add_pillar(head_id, h); // } else - ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); + + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); if (conn.pillar_base->r_top >= sm.cfg.head_back_radius_mm) builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); From 8ab0c2cb3d08ad2b14fb5000d9151e2d2c346701 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 18 Jan 2023 10:08:38 +0100 Subject: [PATCH 132/206] Suppressed tree_supports_show_error() in production code. Changed error strings to string_view literals. --- src/libslic3r/TreeModelVolumes.cpp | 16 ++++++++++------ src/libslic3r/TreeSupport.cpp | 26 +++++++++++++------------- src/libslic3r/TreeSupport.hpp | 5 ++--- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index b416db634..e8ab377d7 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -18,6 +18,8 @@ #include "PrintConfig.hpp" #include "Utils.hpp" +#include + #include #include @@ -26,6 +28,8 @@ namespace Slic3r::FFFTreeSupport { +using namespace std::literals; + // or warning // had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() #define error_level_not_in_cache error @@ -336,7 +340,7 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Collision requested.", false); + tree_supports_show_error("Not precalculated Collision requested."sv, false); } const_cast(this)->calculateCollision(radius, layer_idx); return getCollision(orig_radius, layer_idx, min_xy_dist); @@ -351,7 +355,7 @@ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerInde return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Holefree Collision requested.", false); + tree_supports_show_error("Not precalculated Holefree Collision requested."sv, false); } const_cast(this)->calculateCollisionHolefree({ radius, layer_idx }); return getCollisionHolefree(radius, layer_idx); @@ -375,10 +379,10 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI if (m_precalculated) { if (to_model) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Avoidance(to model) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to model) requested."sv, false); } else { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested."sv, false); } } const_cast(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model); @@ -396,7 +400,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Placeable areas requested.", false); + tree_supports_show_error("Not precalculated Placeable areas requested."sv, false); } const_cast(this)->calculatePlaceables(radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx); @@ -422,7 +426,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius, tree_supports_show_error( min_xy_dist ? "Not precalculated Wall restriction of minimum xy distance requested )." : - "Not precalculated Wall restriction requested )." + "Not precalculated Wall restriction requested )."sv , false); } const_cast(this)->calculateWallRestrictions({ radius, layer_idx }); diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1f0aa00b2..8363cd695 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -58,6 +58,7 @@ enum class LineStatus using LineInformation = std::vector>; using LineInformations = std::vector; +using namespace std::literals; static inline void validate_range(const Point &pt) { @@ -191,10 +192,9 @@ static std::vector>> group_me } #endif -//todo Remove! Only relevant for public BETA! static bool inline g_showed_critical_error = false; static bool inline g_showed_performance_warning = false; -void tree_supports_show_error(std::string message, bool critical) +void tree_supports_show_error(std::string_view message, bool critical) { // todo Remove! ONLY FOR PUBLIC BETA!! #if defined(_WIN32) && defined(TREE_SUPPORT_SHOW_ERRORS) @@ -204,7 +204,7 @@ void tree_supports_show_error(std::string message, bool critical) bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); (critical ? g_showed_critical_error : g_showed_performance_warning) = true; if (show) - MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(), + MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); #endif // WIN32 } @@ -572,7 +572,7 @@ static std::optional> polyline_sample_next_point_at_dis // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << ") is smaller than 100"; - tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing.", true); + tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing."sv, true); if (next_distance > 2 * current_distance) // This case should never happen, but better safe than sorry. break; @@ -759,7 +759,7 @@ static std::optional> polyline_sample_next_point_at_dis return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); if (safe_step_size < 0 || last_step_offset_without_check < 0) { BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; - tree_supports_show_error("Negative offset distance... How did you manage this ?", true); + tree_supports_show_error("Negative offset distance... How did you manage this ?"sv, true); return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); } @@ -951,7 +951,7 @@ static void generate_initial_areas( bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; if (!mesh_config.support_rests_on_model && !to_bp) { BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; - tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly.", true); + tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); return; } Polygons circle{ base_circle }; @@ -1517,7 +1517,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (area(check_layer_data) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance); - tree_supports_show_error("Area lost catching up radius. May not cause visible malformation.", true); + tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true); } } } @@ -1783,7 +1783,7 @@ static void increase_areas_one_layer( "Parent " << &parent << ": Radius: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; - tree_supports_show_error("Potentially lost branch!", true); + tree_supports_show_error("Potentially lost branch!"sv, true); } else result = increase_single_area(volumes, config, settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); @@ -2270,7 +2270,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) { if (area(elem.areas.influence_areas) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; - tree_supports_show_error("Insert error of area after bypassing merge.\n", true); + tree_supports_show_error("Insert error of area after bypassing merge.\n"sv, true); } // Move the area to output. this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas)); @@ -2303,7 +2303,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp Polygons new_area = safe_union(elem.areas.influence_areas); if (area(new_area) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; - tree_supports_show_error("Insert error of area after merge.\n", true); + tree_supports_show_error("Insert error of area after merge.\n"sv, true); } this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area)); } @@ -2331,7 +2331,7 @@ static void set_points_on_areas(const SupportElement &elem, SupportElements *lay // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. if (! elem.state.result_on_layer_is_set()) { BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; - tree_supports_show_error("Uninitialized support element. A branch may be missing.\n", true); + tree_supports_show_error("Uninitialized support element. A branch may be missing.\n"sv, true); return; } @@ -2394,7 +2394,7 @@ static void set_to_model_contact_to_model_gracious( // Could not find valid placement, even though it should exist => error handling if (last_successfull_layer == nullptr) { BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << first_elem.state.layer_idx; - tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); + tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches."sv, true); first_elem.state.to_model_gracious = false; set_to_model_contact_simple(first_elem); } else { @@ -2494,7 +2494,7 @@ static void create_nodes_from_area( if (elem.state.to_buildplate) { BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") " "at target_height: " << elem.state.target_height << " layer: " << layer_idx; - tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially.", true); + tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially."sv, true); } // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set elem.state.deleted = true; diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 0d5e967d9..b0ab46891 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -17,7 +17,7 @@ #include "BoundingBox.hpp" #include "Utils.hpp" - #define TREE_SUPPORT_SHOW_ERRORS +// #define TREE_SUPPORT_SHOW_ERRORS #ifdef SLIC3R_TREESUPPORTS_PROGRESS // The various stages of the process can be weighted differently in the progress bar. @@ -576,8 +576,7 @@ public: } }; -// todo Remove! ONLY FOR PUBLIC BETA!! -void tree_supports_show_error(std::string message, bool critical); +void tree_supports_show_error(std::string_view message, bool critical); } // namespace FFFTreeSupport From f656b2e62e439649da8af3f4d8d51af003ddb0af Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 17 Jan 2023 14:09:42 +0100 Subject: [PATCH 133/206] downloader: empty file fix --- src/slic3r/GUI/DownloaderFileGet.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index deac45ad5..2e591a75a 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -163,6 +163,8 @@ void FileGet::priv::get_perform() m_stopped = true; fclose(file); cancel = true; + if (m_written == 0) + std::remove(m_tmp_path.string().c_str()); wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED); evt->SetInt(m_id); m_evt_handler->QueueEvent(evt); From d9c7c675c467d23249d0b46852bb5061c8373caf Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 19 Jan 2022 17:10:37 +0100 Subject: [PATCH 134/206] download missing resources --- src/libslic3r/Preset.cpp | 23 +++++++++++ src/libslic3r/Preset.hpp | 1 + src/slic3r/Utils/PresetUpdater.cpp | 65 +++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9dad49d4c..8e7e53efc 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2099,6 +2099,29 @@ namespace PresetUtils { } return out; } + + bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache) + { + std::string vendor_folder = Slic3r::data_dir() + "/vendor/" + vp.id + "/"; + std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/"; + std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/"; + for (const VendorProfile::PrinterModel& model : vp.models) + { + if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_texture)) + && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_texture)) + && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_texture)))) + return false; + if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_model)) + && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_model)) + && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_model)))) + return false; + if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.id + "_thumbnail.png")) + && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.id + "_thumbnail.png")) + && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.id + "_thumbnail.png")))) + return false; + } + return true; + } } // namespace PresetUtils } // namespace Slic3r diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 74ba2e0f5..9d5280191 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -619,6 +619,7 @@ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); std::string system_printer_bed_model(const Preset& preset); std::string system_printer_bed_texture(const Preset& preset); + bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache); } // namespace PresetUtils diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index f4863ff20..1da8075f8 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -321,6 +321,31 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) const auto bundle_path = cache_path / (vendor.id + ".ini"); if (! get_file(bundle_url, bundle_path)) { continue; } if (cancel) { return; } + + // check the newly downloaded bundle for missing resources + // for that, the ini file must be parsed + auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const std::string& url, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path){ + if (!boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) + && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; + // testing url (to be removed) + //const auto resource_url = format("https://raw.githubusercontent.com/prusa3d/PrusaSlicer/master/resources/profiles/%1%/%2%", vendor, filename); + const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); + const auto resource_path = (cache_path / (vendor + "/" + filename)); + if (!fs::exists(resource_path.parent_path())) + fs::create_directory(resource_path.parent_path()); + self.get_file(resource_url, resource_path); + } + }; + auto vp = VendorProfile::from_ini(bundle_path, true); + for (const auto& model : vp.models) { + check_and_get_resource(vp.id, model.bed_texture, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) { return; } + check_and_get_resource(vp.id, model.bed_model, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) { return; } + check_and_get_resource(vp.id, model.id +"_thumbnail.png", vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) { return; } + } } } @@ -443,14 +468,21 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version fs::path bundle_path_idx_to_install; if (fs::exists(path_in_cache)) { try { - VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, false); + VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, true); if (new_vp.config_version == recommended->config_version) { // The config bundle from the cache directory matches the recommended version of the index from the cache directory. // This is the newest known recommended config. Use it. - new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); - // and install the config index from the cache into vendor's directory. - bundle_path_idx_to_install = idx.path(); - found = true; + if (PresetUtils::vendor_profile_has_all_resources(new_vp, true)) { + // All resources for the profile in cache dir are existing (either in resources or data_dir/vendor or waiting to be copied to vendor from cache) + // This final check is not performed for updates from resources dir below. + new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); + // and install the config index from the cache into vendor's directory. + bundle_path_idx_to_install = idx.path(); + found = true; + } else { + throw std::exception("Some resources are missing."); + } + } } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(info) << format("Failed to load the config bundle `%1%`: %2%", path_in_cache.string(), ex.what()); @@ -601,6 +633,29 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_prints) { obsolete_remover("sla_print", name); } for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } + + auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path) { + if ( !boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) + && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + + if (!boost::filesystem::exists((cache_path / (vendor + "/" + filename)))) { + BOOST_LOG_TRIVIAL(error) << "Resources missing in cache directory: " << vendor << " / " << filename; + return; + } + if (!fs::exists((vendor_path / vendor))) + fs::create_directory((vendor_path / vendor)); + + copy_file_fix((cache_path / (vendor + "/" + filename)), (vendor_path / (vendor + "/" + filename))); + } + }; + // check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/ + auto vp = VendorProfile::from_ini(update.target, true); + for (const auto& model : vp.models) { + copy_missing_resource(vp.id, model.bed_texture, vendor_path, rsrc_path, cache_path); + copy_missing_resource(vp.id, model.bed_model, vendor_path, rsrc_path, cache_path); + copy_missing_resource(vp.id, model.id + "_thumbnail.png", vendor_path, rsrc_path, cache_path); + } + } } From 72540232c8857ce5819417fba9aec21748e86417 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 21 Feb 2022 15:08:55 +0100 Subject: [PATCH 135/206] Template filaments bundle with filament profiles available for all printers Profiles are ment to be adjusted and saved as user profile. Selectable in wizard under (Templates). Installed automatically even when profile with same alias is selected. Special category in combo boxes. no_templates option for disabling this. --- resources/profiles/TemplateFilaments.idx | 2 + resources/profiles/TemplateFilaments.ini | 1410 ++++++++++++++++++++++ src/libslic3r/AppConfig.cpp | 2 + src/libslic3r/Preset.cpp | 35 +- src/libslic3r/Preset.hpp | 1 + src/libslic3r/PresetBundle.cpp | 7 +- src/slic3r/GUI/ConfigWizard.cpp | 333 +++-- src/slic3r/GUI/ConfigWizard_private.hpp | 152 +-- src/slic3r/GUI/Preferences.cpp | 10 + src/slic3r/GUI/PresetComboBoxes.cpp | 57 +- src/slic3r/GUI/SavePresetDialog.cpp | 28 +- src/slic3r/GUI/SavePresetDialog.hpp | 9 +- src/slic3r/GUI/Tab.cpp | 29 +- src/slic3r/Utils/PresetUpdater.cpp | 2 +- 14 files changed, 1881 insertions(+), 196 deletions(-) create mode 100644 resources/profiles/TemplateFilaments.idx create mode 100644 resources/profiles/TemplateFilaments.ini diff --git a/resources/profiles/TemplateFilaments.idx b/resources/profiles/TemplateFilaments.idx new file mode 100644 index 000000000..94223722f --- /dev/null +++ b/resources/profiles/TemplateFilaments.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.4.0-rc +0.0.1 Initial diff --git a/resources/profiles/TemplateFilaments.ini b/resources/profiles/TemplateFilaments.ini new file mode 100644 index 000000000..000e25f34 --- /dev/null +++ b/resources/profiles/TemplateFilaments.ini @@ -0,0 +1,1410 @@ +# Print profiles for the Prusa Research printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Templates Filaments +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the PrusaSlicer configuration to be downgraded. +config_version = 0.0.1 +# Where to get the updates from? +#config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ +# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% +templates_profile = 1 + +## XXX--- Generic filament profiles ---XXX + +[filament:*common*] +cooling = 1 +compatible_printers = +compatible_printers_condition = +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 15 +slowdown_below_layer_time = 15 +start_filament_gcode = "; Filament gcode" + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF8000 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:*PET*] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 50 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_type = PETG +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 0 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #FFF2EC +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*ABSC*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 1 +disable_fan_first_layers = 4 +fan_always_on = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +filament_colour = #FFF2EC +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 15 +min_fan_speed = 15 +min_print_speed = 15 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bed_temperature = 50 +bridge_fan_speed = 80 +cooling = 0 +disable_fan_first_layers = 3 +extrusion_multiplier = 1.15 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #008000 +filament_max_volumetric_speed = 1.5 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +temperature = 240 + +[filament:ColorFabb bronzeFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.2 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #804040 + +[filament:ColorFabb steelFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.2 +filament_density = 3.13 +filament_spool_weight = 236 +filament_colour = #808080 + +[filament:ColorFabb copperFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.2 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #82603E + +[filament:ColorFabb HT] +inherits = *PET* +filament_vendor = ColorFabb +bed_temperature = 100 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_density = 1.18 +filament_spool_weight = 236 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* +filament_vendor = ColorFabb +filament_density = 1.24 +filament_spool_weight = 236 + +[filament:ColorFabb woodFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_density = 1.15 +filament_spool_weight = 236 +filament_colour = #dfc287 +filament_max_volumetric_speed = 9 +first_layer_temperature = 200 +temperature = 200 + +[filament:ColorFabb corkFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_density = 1.18 +filament_spool_weight = 236 +filament_colour = #634d33 +first_layer_temperature = 220 +temperature = 220 + +[filament:ColorFabb XT] +inherits = *PET* +filament_vendor = ColorFabb +filament_density = 1.27 +filament_spool_weight = 236 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +filament_vendor = ColorFabb +extrusion_multiplier = 1.05 +filament_density = 1.35 +filament_spool_weight = 236 +filament_colour = #804040 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +filament_vendor = ColorFabb +filament_density = 1.2 +filament_spool_weight = 236 +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:ColorFabb nGen flex] +inherits = *FLEX* +filament_vendor = ColorFabb +filament_density = 1 +filament_spool_weight = 236 +bed_temperature = 85 +bridge_fan_speed = 40 +cooling = 1 +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +fan_below_layer_time = 10 +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +temperature = 260 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Kimya PETG Carbon] +inherits = *PET* +filament_vendor = Kimya +extrusion_multiplier = 1.05 +filament_density = 1.317 +filament_colour = #804040 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 240 +filament_retract_length = nil + +[filament:Kimya ABS Carbon] +inherits = *ABSC* +filament_vendor = Kimya +filament_density = 1.032 +filament_colour = #804040 +first_layer_temperature = 260 +temperature = 260 + +[filament:Kimya ABS Kevlar] +inherits = Kimya ABS Carbon +filament_vendor = Kimya +filament_density = 1.037 + +[filament:E3D Edge] +inherits = *PET* +filament_vendor = E3D +filament_density = 1.26 +filament_type = EDGE + +[filament:E3D PC-ABS] +inherits = *ABS* +filament_vendor = E3D +filament_type = PC +filament_density = 1.05 +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum PLA] +inherits = *PLA* +filament_vendor = Fillamentum +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Fillamentum ABS] +inherits = *ABSC* +filament_vendor = Fillamentum +filament_density = 1.04 +filament_spool_weight = 230 +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +filament_vendor = Fillamentum +filament_density = 1.07 +filament_spool_weight = 230 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +filament_type = ASA + +[filament:Prusament ASA] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_density = 1.07 +filament_spool_weight = 201 +fan_always_on = 1 +first_layer_temperature = 260 +first_layer_bed_temperature = 105 +temperature = 260 +bed_temperature = 110 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 4 +filament_type = ASA +filament_colour = #FFF2EC + +[filament:Prusament PC Blend] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_density = 1.22 +filament_spool_weight = 201 +fan_always_on = 0 +first_layer_temperature = 275 +first_layer_bed_temperature = 110 +temperature = 275 +bed_temperature = 115 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 20 +disable_fan_first_layers = 4 +fan_below_layer_time = 30 +filament_type = PC +filament_colour = #DEE0E6 + +[filament:Prusament PC Blend Carbon Fiber] +inherits = Prusament PC Blend +filament_density = 1.16 +extrusion_multiplier = 1.04 +first_layer_temperature = 285 +temperature = 285 +disable_fan_first_layers = 4 +fan_below_layer_time = 10 +filament_colour = #BBBBBB + +[filament:Fillamentum CPE] +inherits = *PET* +filament_vendor = Fillamentum +filament_density = 1.25 +filament_spool_weight = 230 +filament_type = CPE +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +min_fan_speed = 30 +max_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +temperature = 275 + +[filament:Fillamentum Timberfill] +inherits = *PLA* +filament_vendor = Fillamentum +extrusion_multiplier = 1.05 +filament_density = 1.15 +filament_spool_weight = 230 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 190 +temperature = 190 + +[filament:Smartfil Wood] +inherits = *PLA* +filament_vendor = Smart Materials 3D +extrusion_multiplier = 1.05 +filament_density = 1.58 +filament_colour = #804040 +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic ABS] +inherits = *ABSC* +filament_vendor = Generic +filament_density = 1.04 + +[filament:Esun ABS] +inherits = *ABSC* +filament_vendor = Esun +filament_density = 1.01 +filament_spool_weight = 265 + +[filament:Hatchbox ABS] +inherits = *ABSC* +filament_vendor = Hatchbox +filament_density = 1.04 +filament_spool_weight = 245 + +[filament:Filament PM ABS] +inherits = *ABSC* +filament_vendor = Filament PM +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Verbatim ABS] +inherits = *ABSC* +filament_vendor = Verbatim +filament_density = 1.05 +filament_spool_weight = 235 + +[filament:Generic PETG] +inherits = *PET* +filament_vendor = Generic +filament_density = 1.27 + +[filament:Extrudr DuraPro ASA] +inherits = Fillamentum ASA +filament_vendor = Extrudr +bed_temperature = 90 +filament_density = 1.05 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" +first_layer_bed_temperature = 90 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 230 + +[filament:Extrudr PETG] +inherits = *PET* +filament_vendor = Extrudr +bed_temperature = 70 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" +first_layer_bed_temperature = 70 +first_layer_temperature = 220 +temperature = 220 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 +full_fan_speed_layer = 0 + +[filament:Extrudr XPETG CF] +inherits = Extrudr PETG +filament_density = 1.41 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" +first_layer_temperature = 235 +temperature = 235 +compatible_printers_condition = nozzle_diameter[0]>=0.4 +filament_spool_weight = 230 + +[filament:Extrudr XPETG Matt] +inherits = Extrudr PETG +filament_density = 1.41 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" +first_layer_temperature = 230 +temperature = 230 + +[filament:Extrudr BioFusion] +inherits = *PLA* +filament_vendor = Extrudr +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_density = 1.25 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" +first_layer_temperature = 220 +temperature = 220 +max_fan_speed = 45 +min_fan_speed = 25 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr Flax] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.45 +filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" +first_layer_temperature = 190 +temperature = 190 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=106" +first_layer_temperature = 208 +temperature = 208 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr GreenTEC Pro] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.35 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" +temperature = 215 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr GreenTEC Pro Carbon] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.2 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" +first_layer_temperature = 225 +max_fan_speed = 80 +min_fan_speed = 30 +temperature = 225 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Extrudr PLA NX1] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.24 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 0 +max_fan_speed = 90 +min_fan_speed = 30 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr PLA NX2] +inherits = Extrudr PLA NX1 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" + +[filament:Extrudr Flex Hard] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +filament_density = 1.2 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex Medium] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +filament_density = 1.19 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex SemiSoft] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +filament_density = 1.18 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:addnorth Adamant S1] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_density = 1.22 +temperature = 250 +bed_temperature = 50 +first_layer_temperature = 245 +first_layer_bed_temperature = 50 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 40 +max_fan_speed = 70 +bridge_fan_speed = 60 +filament_spool_weight = 0 + +[filament:addnorth Adura X] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +filament_type = NYLON +extrusion_multiplier = 1 +bed_temperature = 60 +first_layer_bed_temperature = 60 +first_layer_temperature = 265 +temperature = 270 +fan_always_on = 0 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +min_print_speed = 20 +fan_below_layer_time = 10 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:addnorth E-PLA] +inherits = *PLA* +filament_vendor = addnorth +filament_density = 1.24 +extrusion_multiplier = 1 +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_spool_weight = 0 + +[filament:addnorth ESD-PETG] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 245 +temperature = 265 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 35 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 8 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:addnorth OBC Polyethylene] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_density = 1.22 +temperature = 200 +bed_temperature = 100 +first_layer_temperature = 195 +first_layer_bed_temperature = 100 +slowdown_below_layer_time = 5 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 30 +bridge_fan_speed = 40 +min_print_speed = 20 +filament_spool_weight = 0 +filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." + +[filament:addnorth PETG] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 250 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 40 +bridge_fan_speed = 50 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 15 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:addnorth Rigid X] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 85 +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +temperature = 260 +fan_always_on = 1 +min_fan_speed = 20 +max_fan_speed = 60 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +fan_below_layer_time = 20 +min_print_speed = 20 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 +filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:addnorth Textura] +inherits = *PLA* +filament_vendor = addnorth +filament_density = 1.24 +extrusion_multiplier = 0.95 +temperature = 215 +bed_temperature = 65 +first_layer_temperature = 215 +first_layer_bed_temperature = 65 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 60 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 15 +min_print_speed = 20 +filament_spool_weight = 0 +filament_retract_length = 1 + +[filament:Filamentworld ABS] +inherits = *ABSC* +filament_vendor = Filamentworld +filament_density = 1.04 +temperature = 230 +bed_temperature = 95 +first_layer_temperature = 240 +first_layer_bed_temperature = 100 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 20 +disable_fan_first_layers = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 15 +bridge_fan_speed = 20 + +[filament:Filamentworld PETG] +inherits = *PET* +filament_vendor = Filamentworld +filament_density = 1.27 +bed_temperature = 70 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 235 +fan_always_on = 1 +min_fan_speed = 25 +max_fan_speed = 55 +bridge_fan_speed = 55 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 35 +disable_fan_first_layers = 2 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:Filamentworld PLA] +inherits = *PLA* +filament_vendor = Filamentworld +filament_density = 1.24 +temperature = 205 +bed_temperature = 55 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 10 +filament_spool_weight = 0 +min_print_speed = 20 + +[filament:Filament PM PETG] +inherits = *PET* +filament_vendor = Filament PM +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Generic PLA] +inherits = *PLA* +filament_vendor = Generic +filament_density = 1.24 + +[filament:Devil Design PLA] +inherits = *PLA* +filament_vendor = Devil Design +filament_density = 1.24 +filament_spool_weight = 250 + +[filament:Devil Design PETG] +inherits = *PET* +filament_vendor = Devil Design +filament_density = 1.23 +filament_spool_weight = 250 +first_layer_temperature = 230 +first_layer_bed_temperature = 85 +temperature = 230 +bed_temperature = 90 + +[filament:Spectrum PLA] +inherits = *PLA* +filament_vendor = Spectrum +filament_density = 1.24 + +[filament:Generic FLEX] +inherits = *FLEX* +filament_vendor = Generic +filament_density = 1.22 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil + +[filament:Fillamentum Flexfill 92A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_density = 1.20 +filament_spool_weight = 230 +filament_deretract_speed = 20 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:AmazonBasics TPU] +inherits = *FLEX* +filament_vendor = AmazonBasics +fan_always_on = 1 +extrusion_multiplier = 1.1 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 235 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_density = 1.21 +disable_fan_first_layers = 4 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:SainSmart TPU] +inherits = *FLEX* +filament_vendor = SainSmart +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.1 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_density = 1.21 +disable_fan_first_layers = 4 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:Filatech FilaFlex40] +inherits = *FLEX* +filament_vendor = Filatech +fan_always_on = 1 +extrusion_multiplier = 1.1 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 50 +min_fan_speed = 50 +filament_density = 1.22 +disable_fan_first_layers = 4 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:Filatech FilaFlex30] +inherits = Filatech FilaFlex40 +temperature = 225 +filament_density = 1.15 +extrusion_multiplier = 1.1 + +[filament:Filatech FilaFlex55] +inherits = Filatech FilaFlex40 +temperature = 230 +filament_density = 1.18 +bed_temperature = 60 +fan_always_on = 0 +fan_below_layer_time = 60 +first_layer_temperature = 235 +extrusion_multiplier = 1 + +[filament:Filatech TPE] +inherits = Filatech FilaFlex40 +first_layer_temperature = 230 +temperature = 225 +filament_density = 1.2 +fan_below_layer_time = 60 +max_fan_speed = 80 +min_fan_speed = 80 +fan_always_on = 1 + +[filament:Filatech TPU] +inherits = Filatech FilaFlex40 +first_layer_temperature = 230 +filament_density = 1.2 +fan_below_layer_time = 60 +max_fan_speed = 80 +min_fan_speed = 80 +fan_always_on = 1 +temperature = 235 + +[filament:Filatech ABS] +inherits = *ABSC* +filament_vendor = Filatech +extrusion_multiplier = 0.95 +filament_density = 1.05 + +[filament:Filatech FilaCarbon] +inherits = *ABSC* +filament_vendor = Filatech +extrusion_multiplier = 0.95 +filament_density = 1.1 +first_layer_bed_temperature = 105 +bed_temperature = 100 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Filatech FilaPLA] +inherits = *PLA* +filament_vendor = Filatech +filament_density = 1.3 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 55 + +[filament:Filatech PLA] +inherits = *PLA* +filament_vendor = Filatech +filament_density = 1.25 +first_layer_temperature = 215 +temperature = 210 + +[filament:Filatech PLA+] +inherits = Filatech PLA +filament_density = 1.24 + +[filament:Filatech FilaTough] +inherits = Filatech ABS +extrusion_multiplier = 0.95 +filament_density = 1.29 +first_layer_temperature = 245 +first_layer_bed_temperature = 80 +temperature = 240 +bed_temperature = 90 +cooling = 0 + +[filament:Filatech HIPS] +inherits = Prusa HIPS +filament_vendor = Filatech +filament_density = 1.07 +filament_spool_weight = +first_layer_temperature = 230 +first_layer_bed_temperature = 100 +temperature = 225 +bed_temperature = 110 + +[filament:Filatech PA] +inherits = *ABSC* +filament_vendor = Filatech +filament_density = 1.1 +first_layer_temperature = 275 +first_layer_bed_temperature = 110 +temperature = 275 +bed_temperature = 115 +fan_always_on = 0 +cooling = 0 +bridge_fan_speed = 25 +filament_type = NYLON + +[filament:Filatech PC] +inherits = Filatech PA +first_layer_bed_temperature = 110 +bed_temperature = 115 +filament_density = 1.2 +filament_type = PC + +[filament:Filatech PC-ABS] +inherits = Filatech PC +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_density = 1.08 +filament_type = PC +fan_always_on = 0 +cooling = 1 +extrusion_multiplier = 0.95 +disable_fan_first_layers = 6 + +[filament:Filatech PETG] +inherits = *PET* +filament_vendor = Filatech +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 75 +temperature = 245 +bed_temperature = 80 +extrusion_multiplier = 0.95 +fan_always_on = 0 + +[filament:Filatech Wood-PLA] +inherits = Filatech PLA +filament_density = 1.05 +first_layer_temperature = 210 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Ultrafuse PET] +inherits = *PET* +filament_vendor = BASF +filament_density = 1.33 +first_layer_temperature = 220 +first_layer_bed_temperature = 70 +temperature = 215 +bed_temperature = 70 +fan_below_layer_time = 10 +min_fan_speed = 75 +max_fan_speed = 100 +bridge_fan_speed = 100 +filament_type = PET +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +filament_notes = "BASF Forward AM Ultrafuse PET\nMaterial profile version 1.0\n\nMaterial Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion.\n" + +[filament:Ultrafuse PRO1] +inherits = Prusament PLA +filament_vendor = BASF +filament_density = 1.25 +filament_spool_weight = 0 +filament_colour = #FFFFFF +filament_notes = "BASF Forward AM Ultrafuse PLA PRO1\nMaterial profile version 1.0\n\nMaterial Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate.\n" + +[filament:Ultrafuse ABS] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 1.04 +min_fan_speed = 10 +max_fan_speed = 20 +bed_temperature = 100 +disable_fan_first_layers = 3 +filament_colour = #FFFFFF +filament_notes = "BASF Forward AM Ultrafuse ABS\nMaterial profile version 1.0\n\nMaterial Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion.\n" + +[filament:Ultrafuse Metal] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 4.5 +extrusion_multiplier = 1.08 +first_layer_temperature = 250 +first_layer_bed_temperature = 100 +temperature = 250 +bed_temperature = 100 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +cooling = 0 +fan_always_on = 0 +filament_max_volumetric_speed = 4 +filament_type = METAL +compatible_printers_condition = nozzle_diameter[0]>=0.4 +filament_colour = #FFFFFF + +[filament:Polymaker PC-Max] +inherits = *ABS* +filament_vendor = Polymaker +filament_density = 1.20 +filament_type = PC +bed_temperature = 115 +filament_colour = #FFF2EC +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +bridge_fan_speed = 0 + +[filament:PrimaSelect PVA+] +inherits = *PLA* +filament_vendor = PrimaSelect +filament_density = 1.23 +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +temperature = 195 + +[filament:Prusa ABS] +inherits = *ABSC* +filament_vendor = Made for Prusa +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Prusa HIPS] +inherits = *ABS* +filament_vendor = Made for Prusa +filament_density = 1.04 +filament_spool_weight = 230 +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 220 +max_fan_speed = 20 +min_fan_speed = 20 +temperature = 220 + +[filament:Generic HIPS] +inherits = *ABS* +filament_vendor = Generic +filament_density = 1.04 +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 230 +max_fan_speed = 20 +min_fan_speed = 20 +temperature = 230 + +[filament:Prusa PETG] +inherits = *PET* +filament_vendor = Made for Prusa +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Verbatim PETG] +inherits = *PET* +filament_vendor = Verbatim +filament_density = 1.27 +filament_spool_weight = 235 + +[filament:Fiberlogy PETG] +inherits = *PET* +filament_vendor = Fiberlogy +filament_density = 1.27 + +[filament:Prusament PETG] +inherits = *PET* +filament_vendor = Prusa Polymers +first_layer_temperature = 240 +temperature = 250 +filament_density = 1.27 +filament_spool_weight = 201 +filament_type = PETG + +[filament:Prusa PLA] +inherits = *PLA* +filament_vendor = Made for Prusa +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Fiberlogy PLA] +inherits = *PLA* +filament_vendor = Fiberlogy +filament_density = 1.24 + +[filament:Filament PM PLA] +inherits = *PLA* +filament_vendor = Filament PM +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:AmazonBasics PLA] +inherits = *PLA* +filament_vendor = AmazonBasics +filament_density = 1.24 + +[filament:Overture PLA] +inherits = *PLA* +filament_vendor = Overture +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Hatchbox PLA] +inherits = *PLA* +filament_vendor = Hatchbox +filament_density = 1.27 +filament_spool_weight = 245 + +[filament:Esun PLA] +inherits = *PLA* +filament_vendor = Esun +filament_density = 1.24 +filament_spool_weight = 265 + +[filament:Das Filament PLA] +inherits = *PLA* +filament_vendor = Das Filament +filament_density = 1.24 + +[filament:EUMAKERS PLA] +inherits = *PLA* +filament_vendor = EUMAKERS +filament_density = 1.24 + +[filament:Floreon3D PLA] +inherits = *PLA* +filament_vendor = Floreon3D +filament_density = 1.24 + +[filament:Prusament PLA] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +filament_density = 1.24 +filament_spool_weight = 201 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" +compatible_printers_condition = nozzle_diameter[0]!=0.8 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) + +[filament:Prusament PVB] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +bed_temperature = 75 +first_layer_bed_temperature = 75 +filament_density = 1.09 +filament_spool_weight = 201 +filament_type = PVB +filament_soluble = 1 +filament_colour = #FFFF6F +slowdown_below_layer_time = 20 + +[filament:Fillamentum Flexfill 98A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_density = 1.23 +filament_spool_weight = 230 +extrusion_multiplier = 1.1 +filament_max_volumetric_speed = 1.35 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 + +[filament:Taulman Bridge] +inherits = *common* +filament_vendor = Taulman +filament_density = 1.13 +bed_temperature = 90 +bridge_fan_speed = 40 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = NYLON +first_layer_bed_temperature = 60 +first_layer_temperature = 240 +max_fan_speed = 0 +min_fan_speed = 0 +temperature = 250 + +[filament:Fillamentum Nylon FX256] +inherits = *common* +filament_vendor = Fillamentum +filament_density = 1.01 +filament_spool_weight = 230 +bed_temperature = 90 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 6 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 6 +filament_soluble = 0 +filament_type = NYLON +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +max_fan_speed = 0 +min_fan_speed = 0 +temperature = 250 + +[filament:Fiberthree F3 PA Pure Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_density = 1.2 +bed_temperature = 70 +first_layer_bed_temperature = 75 +first_layer_temperature = 270 +temperature = 270 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = NYLON +max_fan_speed = 20 +min_fan_speed = 20 + +[filament:Fiberthree F3 PA-CF Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_density = 1.25 +bed_temperature = 70 +first_layer_bed_temperature = 75 +first_layer_temperature = 275 +temperature = 275 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = NYLON +max_fan_speed = 0 +min_fan_speed = 0 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Fiberthree F3 PA-GF Pro] +inherits = Fiberthree F3 PA-CF Pro +filament_vendor = Fiberthree +filament_density = 1.27 +fan_always_on = 1 +max_fan_speed = 15 +min_fan_speed = 15 + +[filament:Taulman T-Glase] +inherits = *PET* +filament_vendor = Taulman +filament_density = 1.27 +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 + +[filament:Verbatim PLA] +inherits = *PLA* +filament_vendor = Verbatim +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Verbatim BVOH] +inherits = *common* +filament_vendor = Verbatim +filament_density = 1.14 +filament_spool_weight = 235 +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +filament_vendor = Verbatim +filament_density = 0.89 +filament_spool_weight = 235 +bed_temperature = 100 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_type = PP +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 220 + diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 8c879cde0..4029444e2 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -68,6 +68,8 @@ void AppConfig::set_defaults() // If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. if (get("no_defaults").empty()) set("no_defaults", "1"); + if (get("no_templates").empty()) + set("no_templates", "0"); if (get("show_incompatible_presets").empty()) set("show_incompatible_presets", "0"); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 8e7e53efc..486453bc6 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -146,6 +146,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem res.changelog_url = changelog_url->second.data(); } + const auto templates_profile = vendor_section.find("templates_profile"); + if (templates_profile != vendor_section.not_found()) { + res.templates_profile = templates_profile->second.data() == "1"; + } + if (! load_all) { return res; } @@ -336,7 +341,8 @@ std::string Preset::label() const bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer) { - if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) + // templates_profile vendor profiles should be decided as same vendor profiles + if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile) // The current profile has a vendor assigned and it is different from the active print's vendor. return false; auto &condition = preset.preset.compatible_prints_condition(); @@ -358,7 +364,8 @@ bool is_compatible_with_print(const PresetWithVendorProfile &preset, const Prese bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config) { - if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) + // templates_profile vendor profiles should be decided as same vendor profiles + if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile) // The current profile has a vendor assigned and it is different from the active print's vendor. return false; auto &condition = preset.preset.compatible_printers_condition(); @@ -1185,6 +1192,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil if (opt) config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); bool some_compatible = false; + std::vector indices_of_template_presets; for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) { bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; @@ -1201,7 +1209,30 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil m_idx_selected = size_t(-1); if (selected) preset_selected.is_compatible = preset_edited.is_compatible; + if (preset_edited.vendor && preset_edited.vendor->templates_profile) { + indices_of_template_presets.push_back(idx_preset); + } } + // filter out template profiles where profile with same alias and compability exists + if (!indices_of_template_presets.empty()) { + for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++idx_preset) { + if (m_presets[idx_preset].vendor && !m_presets[idx_preset].vendor->templates_profile && m_presets[idx_preset].is_compatible) { + std::string preset_alias = m_presets[idx_preset].alias; + for (size_t idx_in_templates = 0; idx_in_templates < indices_of_template_presets.size(); ++idx_in_templates) { + size_t idx_of_template_in_presets = indices_of_template_presets[idx_in_templates]; + if (m_presets[idx_of_template_in_presets].alias == preset_alias) { + // unselect selected template filament if there is non-template alias compatible + if (idx_of_template_in_presets == m_idx_selected && (unselect_if_incompatible == PresetSelectCompatibleType::Always || unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible)) { + m_idx_selected = size_t(-1); + } + m_presets[idx_of_template_in_presets].is_compatible = false; + break; + } + } + } + } + } + // Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile. if (m_idx_selected >= m_num_default_presets && m_default_suppressed) for (size_t i = 0; i < m_num_default_presets; ++ i) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 9d5280191..35d4b5e84 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -34,6 +34,7 @@ public: Semver config_version; std::string config_update_url; std::string changelog_url; + bool templates_profile { false }; struct PrinterVariant { PrinterVariant() {} diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index b783c8aeb..f3fd10a40 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1314,10 +1314,10 @@ std::pair PresetBundle::load_configbundle( const VendorProfile *vendor_profile = nullptr; if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { auto vp = VendorProfile::from_ini(tree, path); - if (vp.models.size() == 0) { + if (vp.models.size() == 0 && !vp.templates_profile) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; return std::make_pair(PresetsConfigSubstitutions{}, 0); - } else if (vp.num_variants() == 0) { + } else if (vp.num_variants() == 0 && !vp.templates_profile) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path; return std::make_pair(PresetsConfigSubstitutions{}, 0); } @@ -1359,6 +1359,9 @@ std::pair PresetBundle::load_configbundle( } else if (boost::starts_with(section.first, "filament:")) { presets = &this->filaments; preset_name = section.first.substr(9); + if (vendor_profile->templates_profile) { + preset_name += " @Template"; + } } else if (boost::starts_with(section.first, "sla_print:")) { presets = &this->sla_prints; preset_name = section.first.substr(10); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index ef6f53bf0..38a4f7e36 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -620,6 +620,7 @@ void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) const std::string PageMaterials::EMPTY; +const std::string PageMaterials::TEMPLATES = "templates"; PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) : ConfigWizardPage(parent, std::move(title), std::move(shortname)) @@ -727,6 +728,11 @@ void PageMaterials::reload_presets() clear(); list_printer->append(_L("(All)"), &EMPTY); + + const AppConfig* app_config = wxGetApp().app_config; + if (materials->technology == T_FFF && app_config->get("no_templates") == "0") + list_printer->append(_L("(Templates)"), &TEMPLATES); + //list_printer->SetLabelMarkup("bald"); for (const Preset* printer : materials->printers) { list_printer->append(printer->name, &printer->name); @@ -758,67 +764,74 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector* are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); wxString text; - if (all_printers) { - wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "
" - "
" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line - ); + if (materials->technology == T_FFF && template_shown) { + text = format_wxstr(_L("%1% visible for (\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); } else { - wxString second_line; - if (!printer_names.empty()) - second_line = (materials->technology == T_FFF ? - _L("Only the following installed printers are compatible with the selected filaments") : - _L("Only the following installed printers are compatible with the selected SLA materials")) + ":"; - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line); - for (size_t i = 0; i < printer_names.size(); ++i) - { - text += wxString::Format("", boost::nowide::widen(printer_names[i])); - if (i % 3 == 2) { - text += wxString::Format( - "" - ""); - } + wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); + + if (all_printers) { + wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "
" + "
" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line + ); + } + else { + wxString second_line; + if (!printer_names.empty()) + second_line = (materials->technology == T_FFF ? + _L("Only the following installed printers are compatible with the selected filaments") : + _L("Only the following installed printers are compatible with the selected SLA materials")) + ":"; + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "
%s
" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line); + for (size_t i = 0; i < printer_names.size(); ++i) + { + text += wxString::Format("", boost::nowide::widen(printer_names[i])); + if (i % 3 == 2) { + text += wxString::Format( + "" + ""); + } + } + text += wxString::Format( + "" + "
%s
" + "" + "" + "" + "" + ); } - text += wxString::Format( - "" - "" - "
" - "
" - "" - "" - ); } + wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); const int fs = font.GetPointSize(); @@ -863,6 +876,8 @@ void PageMaterials::on_material_highlighted(int sel_material) std::vector names; for (const Preset* printer : materials->printers) { for (const Preset* material : matching_materials) { + if (material->vendor && material->vendor->templates_profile) + continue; if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { names.push_back(printer->name); break; @@ -880,6 +895,8 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected wxArrayInt sel_printers; int sel_printers_count = list_printer->GetSelections(sel_printers); + bool templates_available = list_printer->size() > 1 && list_printer->get_data(1) == TEMPLATES; + // Does our wxWidgets version support operator== for wxArrayInt ? // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 #if wxCHECK_VERSION(3, 1, 1) @@ -895,13 +912,14 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected }; if (!are_equal(sel_printers, sel_printers_prev)) { #endif - + template_shown = false; // Refresh type list list_type->Clear(); list_type->append(_L("(All)"), &EMPTY); - if (sel_printers_count > 0) { + if (sel_printers_count > 1) { // If all is selected with other printers // unselect "all" or all printers depending on last value + // same with "templates" if (sel_printers[0] == 0 && sel_printers_count > 1) { if (last_selected_printer == 0) { list_printer->SetSelection(wxNOT_FOUND); @@ -911,38 +929,63 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected sel_printers_count = list_printer->GetSelections(sel_printers); } } - if (sel_printers[0] != 0) { - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - } else { - //clear selection except "ALL" - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - sel_printers_count = list_printer->GetSelections(sel_printers); + if (materials->technology == T_FFF && templates_available && (sel_printers[0] == 1 || sel_printers[1] == 1) && sel_printers_count > 1) { + if (last_selected_printer == 1) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + } + else if (last_selected_printer != 0) { + list_printer->SetSelection(1, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + } + if (sel_printers_count > 0 && sel_printers[0] != 0 && ((materials->technology == T_FFF && templates_available && sel_printers[0] != 1) || materials->technology != T_FFF || !templates_available)) { + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, printer_name, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + } + else if (sel_printers_count > 0 && last_selected_printer == 0) { + //clear selection except "ALL" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + sel_printers_count = list_printer->GetSelections(sel_printers); - materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - sort_list_data(list_type, true, true); - } + materials->filter_presets(nullptr, EMPTY, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + else if (materials->technology == T_FFF && templates_available && sel_printers_count > 0 && last_selected_printer == 1) { + //clear selection except "TEMPLATES" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + sel_printers_count = list_printer->GetSelections(sel_printers); + template_shown = true; + materials->filter_presets(nullptr, TEMPLATES, EMPTY, EMPTY, + [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + sort_list_data(list_type, true, true); sel_printers_prev = sel_printers; sel_type = 0; @@ -971,7 +1014,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected break; } } - materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { + materials->filter_presets(printer, printer_name, type, EMPTY, [this](const Preset* p) { const std::string& vendor = this->materials->get_vendor(p); if (list_vendor->find(vendor) == wxNOT_FOUND) { list_vendor->append(vendor, &vendor); @@ -996,7 +1039,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { const std::string& type = list_type->get_data(sel_type); const std::string& vendor = list_vendor->get_data(sel_vendor); - // finst printer preset + // first printer preset std::vector to_list; for (int i = 0; i < sel_printers_count; i++) { const std::string& printer_name = list_printer->get_data(sel_printers[i]); @@ -1007,15 +1050,14 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected break; } } - - materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) { + materials->filter_presets(printer, printer_name, type, vendor, [this, &to_list](const Preset* p) { const std::string& section = materials->appconfig_section(); bool checked = wizard_p()->appconfig_new.has(section, p->name); bool was_checked = false; int cur_i = list_profile->find(p->alias); if (cur_i == wxNOT_FOUND) { - cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); + cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) || template_shown ? "" : " *"), &p->alias); to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); } else { @@ -1053,10 +1095,15 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat std::vector> prusa_profiles; std::vector> other_profiles; + bool add_TEMPLATES_item = false; for (int i = 0 ; i < list->size(); ++i) { const std::string& data = list->get_data(i); - if (data == EMPTY) // do not sort item + if (data == EMPTY) // do not sort item continue; + if (data == TEMPLATES) {// do not sort item + add_TEMPLATES_item = true; + continue; + } if (!material_type_ordering && data.find("Prusa") != std::string::npos) prusa_profiles.push_back(data); else @@ -1095,10 +1142,13 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat list->Clear(); if (add_All_item) list->append(_L("(All)"), &EMPTY); + if (materials->technology == T_FFF && add_TEMPLATES_item) + list->append(_L("(Templates)"), &TEMPLATES); for (const auto& item : prusa_profiles) list->append(item, &const_cast(item.get())); for (const auto& item : other_profiles) list->append(item, &const_cast(item.get())); + } void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) @@ -1125,11 +1175,11 @@ void PageMaterials::sort_list_data(PresetList* list, const std::vectorClear(); for (size_t i = 0; i < prusa_profiles.size(); ++i) { - list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); + list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); list->Check(i, prusa_profiles[i].checked); } for (size_t i = 0; i < other_profiles.size(); ++i) { - list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get())); + list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(other_profiles[i].name.get())); list->Check(i + prusa_profiles.size(), other_profiles[i].checked); } } @@ -1139,6 +1189,15 @@ void PageMaterials::select_material(int i) const bool checked = list_profile->IsChecked(i); const std::string& alias_key = list_profile->get_data(i); + if (checked && template_shown && !notification_shown) { + notification_shown = true; + wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); + MessageDialog msg(this, message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_NO) { + list_profile->Check(i, false); + return; + } + } wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); } @@ -2197,13 +2256,19 @@ void ConfigWizard::priv::load_pages() index->add_page(page_temps); } - // Filaments & Materials + // Filaments & Materials if (any_fff_selected) { index->add_page(page_filaments); } + // Filaments page if only custom printer is selected + const AppConfig* app_config = wxGetApp().app_config; + if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) { + update_materials(T_ANY); + index->add_page(page_filaments); + } } if (any_sla_selected) { index->add_page(page_sla_materials); } // there should to be selected at least one printer - btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected); + btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle); index->add_page(page_update); index->add_page(page_downloader); @@ -2265,6 +2330,13 @@ void ConfigWizard::priv::load_vendors() } } + for (const auto& printer : wxGetApp().preset_bundle->printers) { + if (!printer.is_default && !printer.is_system && printer.is_visible) { + custom_printer_in_bundle = true; + break; + } + } + // Initialize the is_visible flag in printer Presets for (auto &pair : bundles) { pair.second.preset_bundle->load_installed_printers(appconfig_new); @@ -2386,7 +2458,7 @@ void ConfigWizard::priv::set_run_reason(RunReason run_reason) void ConfigWizard::priv::update_materials(Technology technology) { - if (any_fff_selected && (technology & T_FFF)) { + if ((any_fff_selected || custom_printer_in_bundle || custom_printer_selected) && (technology & T_FFF)) { filaments.clear(); aliases_fff.clear(); // Iterate filaments in all bundles @@ -2409,11 +2481,22 @@ void ConfigWizard::priv::update_materials(Technology technology) filaments.add_printer(&printer); } } - + // template filament bundle has no printers - filament would be never added + if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) + { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + } } } // count compatible printers for (const auto& preset : filaments.presets) { + // skip template filaments + if (preset->vendor && preset->vendor->templates_profile) + continue; const auto filter = [preset](const std::pair element) { return preset->alias == element.first; @@ -2421,17 +2504,19 @@ void ConfigWizard::priv::update_materials(Technology technology) if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { continue; } + // find all aliases (except templates) std::vector idx_with_same_alias; for (size_t i = 0; i < filaments.presets.size(); ++i) { - if (preset->alias == filaments.presets[i]->alias) + if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor)) idx_with_same_alias.push_back(i); } + // check compatibility with each printer size_t counter = 0; for (const auto& printer : filaments.printers) { if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) continue; bool compatible = false; - // Test otrher materials with same alias + // Test other materials with same alias for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); const Preset& prntr = *printer; @@ -2694,6 +2779,21 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo has_material = true; break; } + + // find if preset.first is part of the templates profile (up is searching if preset.first is part of printer vendor preset) + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material && is_compatible_with_printer(PresetWithVendorProfile(*template_material, &bp.second.preset_bundle->vendors.begin()->second), PresetWithVendorProfile(printer, nullptr))) { + has_material = true; + break; + } + } + } + if (has_material) + break; + } } if (! has_material) @@ -2702,6 +2802,23 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo } } } + // template_profile_selected check + template_profile_selected = false; + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + for (const auto& preset : appconfig_presets) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material){ + template_profile_selected = true; + break; + } + } + if (template_profile_selected) { + break; + } + } + } assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); return printer_models_without_material; }; @@ -2865,7 +2982,13 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end()) { continue; } + if (vendor == enabled_vendors.end() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } + + if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { + // Templates vendor needs to be installed + install_bundles.emplace_back(pair.first); + continue; + } size_t size_sum = 0; for (const auto &model : vendor->second) { size_sum += model.second.size(); } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index a8ac09d9b..0d63de95d 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -84,74 +84,8 @@ struct BundleMap : std::map const Bundle& prusa_bundle() const; }; -struct Materials -{ - Technology technology; - // use vector for the presets to purpose of save of presets sorting in the bundle - std::vector presets; - // String is alias of material, size_t number of compatible counters - std::vector> compatibility_counter; - std::set types; - std::set printers; +struct Materials; - Materials(Technology technology) : technology(technology) {} - - void push(const Preset *preset); - void add_printer(const Preset* preset); - void clear(); - bool containts(const Preset *preset) const { - //return std::find(presets.begin(), presets.end(), preset) != presets.end(); - return std::find_if(presets.begin(), presets.end(), - [preset](const Preset* element) { return element == preset; }) != presets.end(); - - } - - bool get_omnipresent(const Preset* preset) { - return get_printer_counter(preset) == printers.size(); - } - - const std::vector get_presets_by_alias(const std::string name) { - std::vector ret_vec; - for (auto it = presets.begin(); it != presets.end(); ++it) { - if ((*it)->alias == name) - ret_vec.push_back((*it)); - } - return ret_vec; - } - - - - size_t get_printer_counter(const Preset* preset) { - for (auto it : compatibility_counter) { - if (it.first == preset->alias) - return it.second; - } - return 0; - } - - const std::string& appconfig_section() const; - const std::string& get_type(const Preset *preset) const; - const std::string& get_vendor(const Preset *preset) const; - - template void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) { - for (auto preset : presets) { - const Preset& prst = *(preset); - const Preset& prntr = *printer; - if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) && - (type.empty() || get_type(preset) == type) && - (vendor.empty() || get_vendor(preset) == vendor)) { - - cb(preset); - } - } - } - - static const std::string UNKNOWN; - static const std::string& get_filament_type(const Preset *preset); - static const std::string& get_filament_vendor(const Preset *preset); - static const std::string& get_material_type(const Preset *preset); - static const std::string& get_material_vendor(const Preset *preset); -}; struct PrinterPickerEvent; @@ -344,6 +278,10 @@ struct PageMaterials: ConfigWizardPage std::string empty_printers_label; bool first_paint = { false }; static const std::string EMPTY; + static const std::string TEMPLATES; + // notify user first time they choose template profile + bool template_shown = { false }; + bool notification_shown = { false }; int last_hovered_item = { -1 } ; PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name); @@ -368,6 +306,82 @@ struct PageMaterials: ConfigWizardPage virtual void on_activate() override; }; +struct Materials +{ + Technology technology; + // use vector for the presets to purpose of save of presets sorting in the bundle + std::vector presets; + // String is alias of material, size_t number of compatible counters + std::vector> compatibility_counter; + std::set types; + std::set printers; + + Materials(Technology technology) : technology(technology) {} + + void push(const Preset* preset); + void add_printer(const Preset* preset); + void clear(); + bool containts(const Preset* preset) const { + //return std::find(presets.begin(), presets.end(), preset) != presets.end(); + return std::find_if(presets.begin(), presets.end(), + [preset](const Preset* element) { return element == preset; }) != presets.end(); + + } + + bool get_omnipresent(const Preset* preset) { + return get_printer_counter(preset) == printers.size(); + } + + const std::vector get_presets_by_alias(const std::string name) { + std::vector ret_vec; + for (auto it = presets.begin(); it != presets.end(); ++it) { + if ((*it)->alias == name) + ret_vec.push_back((*it)); + } + return ret_vec; + } + + + + size_t get_printer_counter(const Preset* preset) { + for (auto it : compatibility_counter) { + if (it.first == preset->alias) + return it.second; + } + return 0; + } + + const std::string& appconfig_section() const; + const std::string& get_type(const Preset* preset) const; + const std::string& get_vendor(const Preset* preset) const; + + template void filter_presets(const Preset* printer, const std::string& printer_name, const std::string& type, const std::string& vendor, F cb) { + for (auto preset : presets) { + const Preset& prst = *(preset); + const Preset& prntr = *printer; + if (((printer == nullptr && printer_name == PageMaterials::EMPTY) || (printer != nullptr && is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor)))) && + (type.empty() || get_type(preset) == type) && + (vendor.empty() || get_vendor(preset) == vendor) && + !prst.vendor->templates_profile) { + + cb(preset); + } + else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor->templates_profile && + (type.empty() || get_type(preset) == type) && + (vendor.empty() || get_vendor(preset) == vendor)) { + cb(preset); + } + } + } + + static const std::string UNKNOWN; + static const std::string& get_filament_type(const Preset* preset); + static const std::string& get_filament_vendor(const Preset* preset); + static const std::string& get_material_type(const Preset* preset); + static const std::string& get_material_vendor(const Preset* preset); +}; + + struct PageCustom: ConfigWizardPage { PageCustom(ConfigWizard *parent); @@ -608,9 +622,11 @@ struct ConfigWizard::priv std::unique_ptr custom_config; // Backing for custom printer definition bool any_fff_selected; // Used to decide whether to display Filaments page bool any_sla_selected; // Used to decide whether to display SLA Materials page - bool custom_printer_selected { false }; + bool custom_printer_selected { false }; // New custom printer is requested + bool custom_printer_in_bundle { false }; // Older custom printer already exists when wizard starts // Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers) bool only_sla_mode { false }; + bool template_profile_selected { false }; // This bool has one purpose - to tell that template profile should be installed if its not (because it cannot be added to appconfig) wxScrolledWindow *hscroll = nullptr; wxBoxSizer *hscroll_sizer = nullptr; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 106937c46..cc57bcffd 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -301,6 +301,11 @@ void PreferencesDialog::build() L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."), app_config->get("no_defaults") == "1"); + append_bool_option(m_optgroup_general, "no_templates", + L("Suppress \" Template \" filament presets"), + L("Suppress \" Template \" filament presets in configuration wizard and sidebar visibility."), + app_config->get("no_templates") == "1"); + append_bool_option(m_optgroup_general, "show_incompatible_presets", L("Show incompatible print and filament presets"), L("When checked, the print and filament presets are shown in the preset editor " @@ -692,6 +697,8 @@ void PreferencesDialog::accept(wxEvent&) DesktopIntegrationDialog::perform_desktop_integration(true); #endif // __linux__ + bool update_filament_sidebar = (m_values.find("no_templates") != m_values.end()); + std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" }; for (const std::string& option : options_to_recreate_GUI) { @@ -761,6 +768,9 @@ void PreferencesDialog::accept(wxEvent&) wxGetApp().update_ui_from_settings(); clear_cache(); + + if (update_filament_sidebar) + wxGetApp().plater()->sidebar().update_presets(Preset::Type::TYPE_FILAMENT); } void PreferencesDialog::revert(wxEvent&) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index c4d0fc670..be34f6c5d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -836,6 +836,7 @@ void PlaterPresetComboBox::update() null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width; std::map nonsys_presets; + std::map template_presets; wxString selected_user_preset; wxString tooltip; @@ -883,10 +884,18 @@ void PlaterPresetComboBox::update() const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { - Append(get_preset_name(preset), *bmp); - validate_selection(is_selected); - if (is_selected) - tooltip = from_u8(preset.name); + if (preset.vendor && preset.vendor->templates_profile) { + template_presets.emplace(get_preset_name(preset), bmp); + if (is_selected) { + selected_user_preset = get_preset_name(preset); + tooltip = from_u8(preset.name); + } + } else { + Append(get_preset_name(preset), *bmp); + validate_selection(is_selected); + if (is_selected) + tooltip = from_u8(preset.name); + } } else { @@ -899,6 +908,15 @@ void PlaterPresetComboBox::update() if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } + + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + Append(it->first, *it->second); + validate_selection(it->first == selected_user_preset); + } + } if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); @@ -1046,6 +1064,8 @@ void TabPresetComboBox::update() const std::deque& presets = m_collection->get_presets(); std::map> nonsys_presets; + std::map> template_presets; + wxString selected = ""; if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); @@ -1078,11 +1098,19 @@ void TabPresetComboBox::update() auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); - if (preset.is_default || preset.is_system) { - int item_id = Append(get_preset_name(preset), *bmp); - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(i == idx_selected); + if (preset.is_default || preset.is_system) { + if (preset.vendor && preset.vendor->templates_profile) { + template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + if (i == idx_selected) + selected = get_preset_name(preset); + } else { + int item_id = Append(get_preset_name(preset), *bmp); + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(i == idx_selected); + } + + } else { @@ -1094,6 +1122,17 @@ void TabPresetComboBox::update() if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } + } if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index d9ee5f58c..d4c2ba58c 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -285,17 +285,17 @@ void SavePresetDialog::Item::Enable(bool enable /*= true*/) // SavePresetDialog //----------------------------------------------- -SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix) +SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix, bool template_filament) : DPIDialog(parent, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) { - build(std::vector{type}, suffix); + build(std::vector{type}, suffix, template_filament); } -SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix, PresetBundle* preset_bundle/* = nullptr*/) +SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix, bool template_filament/* =false*/, PresetBundle* preset_bundle/* = nullptr*/) : DPIDialog(parent, wxID_ANY, _L("Save presets"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), m_preset_bundle(preset_bundle) { - build(types, suffix); + build(types, suffix, template_filament); } SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention) @@ -312,7 +312,7 @@ SavePresetDialog::~SavePresetDialog() } } -void SavePresetDialog::build(std::vector types, std::string suffix) +void SavePresetDialog::build(std::vector types, std::string suffix, bool template_filament) { #if defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling @@ -341,6 +341,15 @@ void SavePresetDialog::build(std::vector types, std::string suffix btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); }); topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxALL, BORDER_W); + + // Add checkbox for Template filament saving + if (template_filament && types.size() == 1 && *types.begin() == Preset::Type::TYPE_FILAMENT) { + m_template_filament_checkbox = new wxCheckBox(this, wxID_ANY, _L("Save as profile derived from current printer only.")); + wxBoxSizer* check_sizer = new wxBoxSizer(wxVERTICAL); + check_sizer->Add(m_template_filament_checkbox); + topSizer->Add(check_sizer, 0, wxEXPAND | wxALL, BORDER_W); + } + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); SetSizer(topSizer); @@ -371,6 +380,15 @@ std::string SavePresetDialog::get_name(Preset::Type type) return ""; } +bool SavePresetDialog::get_template_filament_checkbox() +{ + if (m_template_filament_checkbox) + { + return m_template_filament_checkbox->GetValue(); + } + return false; +} + bool SavePresetDialog::enable_ok_btn() const { for (const Item* item : m_items) diff --git a/src/slic3r/GUI/SavePresetDialog.hpp b/src/slic3r/GUI/SavePresetDialog.hpp index e34ed9e5d..0199e27de 100644 --- a/src/slic3r/GUI/SavePresetDialog.hpp +++ b/src/slic3r/GUI/SavePresetDialog.hpp @@ -76,6 +76,7 @@ private: wxStaticText* m_label {nullptr}; wxBoxSizer* m_radio_sizer {nullptr}; ActionType m_action {UndefAction}; + wxCheckBox* m_template_filament_checkbox {nullptr}; std::string m_ph_printer_name; std::string m_old_preset_name; @@ -86,10 +87,11 @@ private: public: +<<<<<<< master const wxString& get_info_line_extention() { return m_info_line_extention; } - SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = ""); - SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix = "", PresetBundle* preset_bundle = nullptr); + SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = "", bool template_filament = false); + SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix = "", bool template_filament = false, PresetBundle* preset_bundle = nullptr); SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention); ~SavePresetDialog() override; @@ -105,12 +107,13 @@ public: bool Layout() override; bool is_for_rename() { return m_use_for_rename; } + bool get_template_filament_checkbox(); protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {} private: - void build(std::vector types, std::string suffix = ""); + void build(std::vector types, std::string suffix = "", bool template_filament = false); void update_physical_printers(const std::string& preset_name); void accept(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ad7b92e2e..a90287689 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3715,11 +3715,25 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // focus currently.is there anything better than this ? //! m_treectrl->OnSetFocus(); + auto& old_preset = m_presets->get_edited_preset(); + bool from_template = false; + std::string edited_printer; + if (m_type == Preset::TYPE_FILAMENT && old_preset.vendor && old_preset.vendor->templates_profile) + { + //TODO: is this really the best way to get "printer_model" option of currently edited printer? + edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt("printer_model")->serialize(); + if (!edited_printer.empty()) + from_template = true; + + } + if (name.empty()) { - SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : ""); + SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : "", from_template); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); + if (from_template) + from_template = dlg.get_template_filament_checkbox(); } if (detach && m_type == Preset::TYPE_PRINTER) @@ -3731,6 +3745,19 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) if (detach && m_type == Preset::TYPE_PRINTER) wxGetApp().mainframe->on_config_changed(m_config); + // Update compatible printers + if (from_template && !edited_printer.empty()) { + auto& new_preset = m_presets->get_edited_preset(); + std::string cond = new_preset.compatible_printers_condition(); + if (!cond.empty()) + cond += " and "; + cond += "printer_model == \""+edited_printer+"\""; + new_preset.config.set("compatible_printers_condition", cond); + new_preset.save(); + m_presets->save_current_preset(name, detach); + load_current_preset(); + } + // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 1da8075f8..cee611bdb 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -480,7 +480,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version bundle_path_idx_to_install = idx.path(); found = true; } else { - throw std::exception("Some resources are missing."); + throw Slic3r::CriticalException("Some resources are missing."); } } From a15ad698d7b722e36c0d826967bff06b0ce9b192 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 27 Feb 2022 18:30:36 +0100 Subject: [PATCH 136/206] download profile bundles in zip archive --- src/libslic3r/AppConfig.cpp | 12 + src/libslic3r/AppConfig.hpp | 2 + src/libslic3r/PresetBundle.cpp | 1 + src/slic3r/GUI/ConfigWizard.cpp | 6972 ++++++++++++----------- src/slic3r/GUI/ConfigWizard_private.hpp | 11 +- src/slic3r/Utils/PresetUpdater.cpp | 140 +- 6 files changed, 3614 insertions(+), 3524 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 4029444e2..44658e852 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -36,6 +36,10 @@ static const std::string MODEL_PREFIX = "model:"; // are phased out, then we will revert to the original name. //static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; +// url to folder with profile archive zip +// TODO: Uncomment and delete 2nd when we have archive online +//static const std::string PROFILE_ARCHIVE_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Archive/Archive.zip"; +static const std::string PROFILE_ARCHIVE_URL = "https://raw.githubusercontent.com/kocikdav/PrusaSlicer-settings/master/live/Bundle/Archive.zip"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; @@ -667,6 +671,14 @@ std::string AppConfig::version_check_url() const return from_settings.empty() ? VERSION_CHECK_URL : from_settings; } +std::string AppConfig::profile_archive_url() const +{ + // Do we want to have settable url? + //auto from_settings = get("profile_archive_url"); + //return from_settings.empty() ? PROFILE_ARCHIVE_URL : from_settings; + return PROFILE_ARCHIVE_URL; +} + bool AppConfig::exists() { return boost::filesystem::exists(config_path()); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 5658f142d..219a5ff28 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -139,6 +139,8 @@ public: // Get the Slic3r version check url. // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. std::string version_check_url() const; + // Get the Slic3r url to vendor profile archive zip. + std::string profile_archive_url() const; // Returns the original Slic3r version found in the ini file before it was overwritten // by the current version diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f3fd10a40..ed063415f 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -159,6 +159,7 @@ void PresetBundle::setup_directories() data_dir, data_dir / "vendor", data_dir / "cache", + data_dir / "cache" / "vendor", data_dir / "shapes", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 38a4f7e36..4d0269a8e 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,3483 +1,3489 @@ -// FIXME: extract absolute units -> em - -#include "ConfigWizard_private.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef WIN32 -#include -#include -#include -#endif // WIN32 - -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE - -#include "libslic3r/Platform.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Config.hpp" -#include "libslic3r/libslic3r.h" -#include "libslic3r/Model.hpp" -#include "libslic3r/Color.hpp" -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "GUI_Utils.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "Field.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "slic3r/Utils/PresetUpdater.hpp" -#include "format.hpp" -#include "MsgDialog.hpp" -#include "UnsavedChangesDialog.hpp" -#include "slic3r/Utils/AppUpdater.hpp" - -#if defined(__linux__) && defined(__WXGTK3__) -#define wxLinux_gtk3 true -#else -#define wxLinux_gtk3 false -#endif //defined(__linux__) && defined(__WXGTK3__) - -namespace Slic3r { -namespace GUI { - - -using Config::Snapshot; -using Config::SnapshotDB; - - -// Configuration data structures extensions needed for the wizard - -bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle) -{ - this->preset_bundle = std::make_unique(); - this->is_in_resources = ais_in_resources; - this->is_prusa_bundle = ais_prusa_bundle; - - std::string path_string = source_path.string(); - // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. - auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( - path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); - UNUSED(config_substitutions); - // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. - assert(config_substitutions.empty()); - auto first_vendor = preset_bundle->vendors.begin(); - if (first_vendor == preset_bundle->vendors.end()) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; - return false; - } - if (presets_loaded == 0) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string; - return false; - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded; - this->vendor_profile = &first_vendor->second; - return true; -} - -Bundle::Bundle(Bundle &&other) - : preset_bundle(std::move(other.preset_bundle)) - , vendor_profile(other.vendor_profile) - , is_in_resources(other.is_in_resources) - , is_prusa_bundle(other.is_prusa_bundle) -{ - other.vendor_profile = nullptr; -} - -BundleMap BundleMap::load() -{ - BundleMap res; - - const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); - const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); - - auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - auto prusa_bundle_rsrc = false; - if (! boost::filesystem::exists(prusa_bundle_path)) { - prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - prusa_bundle_rsrc = true; - } - { - Bundle prusa_bundle; - if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true)) - res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); - } - - // Load the other bundles in the datadir/vendor directory - // and then additionally from resources/profiles. - bool is_in_resources = false; - for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { - for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { - if (Slic3r::is_ini_file(dir_entry)) { - std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part - - // Don't load this bundle if we've already loaded it. - if (res.find(id) != res.end()) { continue; } - - Bundle bundle; - if (bundle.load(dir_entry.path(), is_in_resources)) - res.emplace(std::move(id), std::move(bundle)); - } - } - - is_in_resources = true; - } - - return res; -} - -Bundle& BundleMap::prusa_bundle() -{ - auto it = find(PresetBundle::PRUSA_BUNDLE); - if (it == end()) { - throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); - } - - return it->second; -} - -const Bundle& BundleMap::prusa_bundle() const -{ - return const_cast(this)->prusa_bundle(); -} - - -// Printer model picker GUI control - -struct PrinterPickerEvent : public wxEvent -{ - std::string vendor_id; - std::string model_id; - std::string variant_name; - bool enable; - - PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) - : wxEvent(winid, eventType) - , vendor_id(std::move(vendor_id)) - , model_id(std::move(model_id)) - , variant_name(std::move(variant_name)) - , enable(enable) - {} - - virtual wxEvent *Clone() const - { - return new PrinterPickerEvent(*this); - } -}; - -wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); - -const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) - : wxPanel(parent) - , vendor_id(vendor.id) - , width(0) -{ - wxGetApp().UpdateDarkUI(this); - const auto &models = vendor.models; - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - const auto font_title = GetFont().MakeBold().Scaled(1.3f); - const auto font_name = GetFont().MakeBold(); - const auto font_alt_nozzle = GetFont().Scaled(0.9f); - - // wxGrid appends widgets by rows, but we need to construct them in columns. - // These vectors are used to hold the elements so that they can be appended in the right order. - std::vector titles; - std::vector bitmaps; - std::vector variants_panels; - - int max_row_width = 0; - int current_row_width = 0; - - bool is_variants = false; - - for (const auto &model : models) { - if (! filter(model)) { continue; } - - wxBitmap bitmap; - int bitmap_width = 0; - auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { - if (wxFileExists(bitmap_file)) { - bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); - bitmap_width = bitmap.GetWidth(); - return true; - } - return false; - }; - if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") - % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") - % vendor.id - % model.id; - load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); - } - } - auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - title->SetFont(font_name); - const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); - title->Wrap(wrap_width); - - current_row_width += wrap_width; - if (titles.size() % max_cols == max_cols - 1) { - max_row_width = std::max(max_row_width, current_row_width); - current_row_width = 0; - } - - titles.push_back(title); - - auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); - bitmaps.push_back(bitmap_widget); - - auto *variants_panel = new wxPanel(this); - wxGetApp().UpdateDarkUI(variants_panel); - auto *variants_sizer = new wxBoxSizer(wxVERTICAL); - variants_panel->SetSizer(variants_sizer); - const auto model_id = model.id; - - for (size_t i = 0; i < model.variants.size(); i++) { - const auto &variant = model.variants[i]; - - const auto label = model.technology == ptFFF - ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str()) - : from_u8(model.name); - - if (i == 1) { - auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:")); - alt_label->SetFont(font_alt_nozzle); - variants_sizer->Add(alt_label, 0, wxBOTTOM, 3); - is_variants = true; - } - - auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); - i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); - - const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); - cbox->SetValue(enabled); - - variants_sizer->Add(cbox, 0, wxBOTTOM, 3); - - cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) { - on_checkbox(cbox, event.IsChecked()); - }); - } - - variants_panels.push_back(variants_panel); - } - - width = std::max(max_row_width, current_row_width); - - const size_t cols = std::min(max_cols, titles.size()); - - auto *printer_grid = new wxFlexGridSizer(cols, 0, 20); - printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL); - - if (titles.size() > 0) { - const size_t odd_items = titles.size() % cols; - - for (size_t i = 0; i < titles.size() - odd_items; i += cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); } - - // Add separator space to multiliners - if (titles.size() > cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); } - } - } - if (odd_items > 0) { - const size_t rem = titles.size() - odd_items; - - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); } - } - } - - auto *title_sizer = new wxBoxSizer(wxHORIZONTAL); - if (! title.IsEmpty()) { - auto *title_widget = new wxStaticText(this, wxID_ANY, title); - title_widget->SetFont(font_title); - title_sizer->Add(title_widget); - } - title_sizer->AddStretchSpacer(); - - if (titles.size() > 1 || is_variants) { - // It only makes sense to add the All / None buttons if there's multiple printers - // All Standard button is added when there are more variants for at least one printer - auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard")); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - if (is_variants) - sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); }); - sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); }); - sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); - if (is_variants) - title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(sel_all_std); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - // fill button indexes used later for buttons rescaling - if (is_variants) - m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() }; - else { - sel_all_std->Destroy(); - m_button_indexes = { sel_all->GetId(), sel_none->GetId() }; - } - } - - sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING); - sizer->Add(printer_grid); - - SetSizer(sizer); -} - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) - : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) -{} - -void PrinterPicker::select_all(bool select, bool alternates) -{ - for (const auto &cb : cboxes) { - if (cb->GetValue() != select) { - cb->SetValue(select); - on_checkbox(cb, select); - } - } - - if (! select) { alternates = false; } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue() != alternates) { - cb->SetValue(alternates); - on_checkbox(cb, alternates); - } - } -} - -void PrinterPicker::select_one(size_t i, bool select) -{ - if (i < cboxes.size() && cboxes[i]->GetValue() != select) { - cboxes[i]->SetValue(select); - on_checkbox(cboxes[i], select); - } -} - -bool PrinterPicker::any_selected() const -{ - for (const auto &cb : cboxes) { - if (cb->GetValue()) { return true; } - } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue()) { return true; } - } - - return false; -} - -std::set PrinterPicker::get_selected_models() const -{ - std::set ret_set; - - for (const auto& cb : cboxes) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - for (const auto& cb : cboxes_alt) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - return ret_set; -} - -void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) -{ - PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); - AddPendingEvent(evt); -} - - -// Wizard page base - -ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent) - : wxPanel(parent->p->hscroll) - , parent(parent) - , shortname(std::move(shortname)) - , indent(indent) -{ - wxGetApp().UpdateDarkUI(this); - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - const auto font = GetFont().MakeBold().Scaled(1.5); - text->SetFont(font); - sizer->Add(text, 0, wxALIGN_LEFT, 0); - sizer->AddSpacer(10); - - content = new wxBoxSizer(wxVERTICAL); - sizer->Add(content, 1, wxEXPAND); - - SetSizer(sizer); - - // There is strange layout on Linux with GTK3, - // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861 - // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages - if (!wxLinux_gtk3) - this->Hide(); - - Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { - this->Layout(); - event.Skip(); - }); -} - -ConfigWizardPage::~ConfigWizardPage() {} - -wxStaticText* ConfigWizardPage::append_text(wxString text) -{ - auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - widget->Wrap(WRAP_WIDTH); - widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); - append(widget); - return widget; -} - -void ConfigWizardPage::append_spacer(int space) -{ - // FIXME: scaling - content->AddSpacer(space); -} - -// Wizard pages - -PageWelcome::PageWelcome(ConfigWizard *parent) - : ConfigWizardPage(parent, from_u8((boost::format( -#ifdef __APPLE__ - _utf8(L("Welcome to the %s Configuration Assistant")) -#else - _utf8(L("Welcome to the %s Configuration Wizard")) -#endif - ) % SLIC3R_APP_NAME).str()), _L("Welcome")) - , welcome_text(append_text(from_u8((boost::format( - _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print."))) - % SLIC3R_APP_NAME - % _utf8(ConfigWizard::name())).str()) - )) - , cbox_reset(append( - new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) - )) - , cbox_integrate(append( - new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) - )) -{ - welcome_text->Hide(); - cbox_reset->Hide(); - cbox_integrate->Hide(); -} - -void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) -{ - const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; - welcome_text->Show(data_empty); - cbox_reset->Show(!data_empty); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - if (!DesktopIntegrationDialog::is_integrated()) - cbox_integrate->Show(true); - else - cbox_integrate->Hide(); -#else - cbox_integrate->Hide(); -#endif -} - - -PagePrinters::PagePrinters(ConfigWizard *parent, - wxString title, - wxString shortname, - const VendorProfile &vendor, - unsigned indent, - Technology technology) - : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) - , technology(technology) - , install(false) // only used for 3rd party vendors -{ - enum { - COL_SIZE = 200, - }; - - AppConfig *appconfig = &this->wizard_p()->appconfig_new; - - const auto families = vendor.families(); - for (const auto &family : families) { - const auto filter = [&](const VendorProfile::PrinterModel &model) { - return ((model.technology == ptFFF && technology & T_FFF) - || (model.technology == ptSLA && technology & T_SLA)) - && model.family == family; - }; - - if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) { - continue; - } - - const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str()); - auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); - - picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { - appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); - wizard_p()->on_printer_pick(this, evt); - }); - - append(new StaticLine(this)); - - append(picker); - printer_pickers.push_back(picker); - has_printers = true; - } - -} - -void PagePrinters::select_all(bool select, bool alternates) -{ - for (auto picker : printer_pickers) { - picker->select_all(select, alternates); - } -} - -int PagePrinters::get_width() const -{ - return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, - [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); -} - -bool PagePrinters::any_selected() const -{ - for (const auto *picker : printer_pickers) { - if (picker->any_selected()) { return true; } - } - - return false; -} - -std::set PagePrinters::get_selected_models() -{ - std::set ret_set; - - for (const auto *picker : printer_pickers) - { - std::set tmp_models = picker->get_selected_models(); - ret_set.insert(tmp_models.begin(), tmp_models.end()); - } - - return ret_set; -} - -void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) -{ - if (is_primary_printer_page - && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY) - && printer_pickers.size() > 0 - && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) { - printer_pickers[0]->select_one(0, true); - } -} - - -const std::string PageMaterials::EMPTY; -const std::string PageMaterials::TEMPLATES = "templates"; - -PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) - : ConfigWizardPage(parent, std::move(title), std::move(shortname)) - , materials(materials) - , list_printer(new StringList(this, wxLB_MULTIPLE)) - , list_type(new StringList(this)) - , list_vendor(new StringList(this)) - , list_profile(new PresetList(this)) -{ - append_spacer(VERTICAL_SPACING); - - const int em = parent->em_unit(); - const int list_h = 30*em; - - - list_printer->SetMinSize(wxSize(23*em, list_h)); - list_type->SetMinSize(wxSize(13*em, list_h)); - list_vendor->SetMinSize(wxSize(13*em, list_h)); - list_profile->SetMinSize(wxSize(23*em, list_h)); - - - - grid = new wxFlexGridSizer(4, em/2, em); - grid->AddGrowableCol(3, 1); - grid->AddGrowableRow(1, 1); - - grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:"))); - grid->Add(new wxStaticText(this, wxID_ANY, list1name)); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:"))); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:"))); - - grid->Add(list_printer, 0, wxEXPAND); - grid->Add(list_type, 0, wxEXPAND); - grid->Add(list_vendor, 0, wxEXPAND); - grid->Add(list_profile, 1, wxEXPAND); - - auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); - btn_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(list_printer); - wxGetApp().UpdateDarkUI(list_type); - wxGetApp().UpdateDarkUI(list_vendor); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(btn_sizer, 0, wxALIGN_RIGHT); - - append(grid, 1, wxEXPAND); - - append_spacer(VERTICAL_SPACING); - - html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, - wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); - append(html_window, 0, wxEXPAND); - - list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt()); - }); - list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - - list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); - list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); - - sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); - sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); - /* - Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); - - list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); - list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); - list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); - */ - reload_presets(); - set_compatible_printers_html_window(std::vector(), false); -} -void PageMaterials::on_paint() -{ -} -void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) -{ - const wxClientDC dc(list_profile); - const wxPoint pos = evt.GetLogicalPosition(dc); - int item = list_profile->HitTest(pos); - on_material_hovered(item); -} -void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) -{} -void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) -{ - on_material_hovered(-1); -} -void PageMaterials::reload_presets() -{ - clear(); - - list_printer->append(_L("(All)"), &EMPTY); - - const AppConfig* app_config = wxGetApp().app_config; - if (materials->technology == T_FFF && app_config->get("no_templates") == "0") - list_printer->append(_L("(Templates)"), &TEMPLATES); - - //list_printer->SetLabelMarkup("bald"); - for (const Preset* printer : materials->printers) { - list_printer->append(printer->name, &printer->name); - } - sort_list_data(list_printer, true, false); - if (list_printer->GetCount() > 0) { - list_printer->SetSelection(0); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - update_lists(0, 0, 0); - } - - presets_loaded = true; -} - -void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) -{ - const auto bgr_clr = -#if defined(__APPLE__) - html_window->GetParent()->GetBackgroundColour(); -#else -#if defined(_WIN32) - wxGetApp().get_window_default_clr(); -#else - wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); -#endif -#endif - const auto text_clr = wxGetApp().get_label_clr_default(); - const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); - const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); - wxString text; - if (materials->technology == T_FFF && template_shown) { - text = format_wxstr(_L("%1% visible for (\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - } else { - wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - - if (all_printers) { - wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "
" - "
" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line - ); - } - else { - wxString second_line; - if (!printer_names.empty()) - second_line = (materials->technology == T_FFF ? - _L("Only the following installed printers are compatible with the selected filaments") : - _L("Only the following installed printers are compatible with the selected SLA materials")) + ":"; - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line); - for (size_t i = 0; i < printer_names.size(); ++i) - { - text += wxString::Format("", boost::nowide::widen(printer_names[i])); - if (i % 3 == 2) { - text += wxString::Format( - "" - ""); - } - } - text += wxString::Format( - "" - "
%s
" - "
" - "
" - "" - "" - ); - } - } - - - wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); - const int fs = font.GetPointSize(); - int size[] = { fs,fs,fs,fs,fs,fs,fs }; - html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); - html_window->SetPage(text); -} - -void PageMaterials::clear_compatible_printers_label() -{ - set_compatible_printers_html_window(std::vector(), false); -} - -void PageMaterials::on_material_hovered(int sel_material) -{ - -} - -void PageMaterials::on_material_highlighted(int sel_material) -{ - if (sel_material == last_hovered_item) - return; - if (sel_material == -1) { - clear_compatible_printers_label(); - return; - } - last_hovered_item = sel_material; - std::vector tabs; - tabs.push_back(std::string()); - tabs.push_back(std::string()); - tabs.push_back(std::string()); - //selected material string - std::string material_name = list_profile->get_data(sel_material); - // get material preset - const std::vector matching_materials = materials->get_presets_by_alias(material_name); - if (matching_materials.empty()) - { - clear_compatible_printers_label(); - return; - } - //find matching printers - std::vector names; - for (const Preset* printer : materials->printers) { - for (const Preset* material : matching_materials) { - if (material->vendor && material->vendor->templates_profile) - continue; - if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { - names.push_back(printer->name); - break; - } - } - } - set_compatible_printers_html_window(names, names.size() == materials->printers.size()); -} - -void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - wxArrayInt sel_printers; - int sel_printers_count = list_printer->GetSelections(sel_printers); - - bool templates_available = list_printer->size() > 1 && list_printer->get_data(1) == TEMPLATES; - - // Does our wxWidgets version support operator== for wxArrayInt ? - // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 -#if wxCHECK_VERSION(3, 1, 1) - if (sel_printers != sel_printers_prev) { -#else - auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { - if (arr_first.GetCount() != arr_second.GetCount()) - return false; - for (size_t i = 0; i < arr_first.GetCount(); i++) - if (arr_first[i] != arr_second[i]) - return false; - return true; - }; - if (!are_equal(sel_printers, sel_printers_prev)) { -#endif - template_shown = false; - // Refresh type list - list_type->Clear(); - list_type->append(_L("(All)"), &EMPTY); - if (sel_printers_count > 1) { - // If all is selected with other printers - // unselect "all" or all printers depending on last value - // same with "templates" - if (sel_printers[0] == 0 && sel_printers_count > 1) { - if (last_selected_printer == 0) { - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - } else { - list_printer->SetSelection(0, false); - sel_printers_count = list_printer->GetSelections(sel_printers); - } - } - if (materials->technology == T_FFF && templates_available && (sel_printers[0] == 1 || sel_printers[1] == 1) && sel_printers_count > 1) { - if (last_selected_printer == 1) { - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(1); - } - else if (last_selected_printer != 0) { - list_printer->SetSelection(1, false); - sel_printers_count = list_printer->GetSelections(sel_printers); - } - } - } - if (sel_printers_count > 0 && sel_printers[0] != 0 && ((materials->technology == T_FFF && templates_available && sel_printers[0] != 1) || materials->technology != T_FFF || !templates_available)) { - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, printer_name, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - } - else if (sel_printers_count > 0 && last_selected_printer == 0) { - //clear selection except "ALL" - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - sel_printers_count = list_printer->GetSelections(sel_printers); - - materials->filter_presets(nullptr, EMPTY, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - else if (materials->technology == T_FFF && templates_available && sel_printers_count > 0 && last_selected_printer == 1) { - //clear selection except "TEMPLATES" - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(1); - sel_printers_count = list_printer->GetSelections(sel_printers); - template_shown = true; - materials->filter_presets(nullptr, TEMPLATES, EMPTY, EMPTY, - [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - sort_list_data(list_type, true, true); - - sel_printers_prev = sel_printers; - sel_type = 0; - sel_type_prev = wxNOT_FOUND; - list_type->SetSelection(sel_type); - list_profile->Clear(); - } - - if (sel_type != sel_type_prev) { - // Refresh vendor list - - // XXX: The vendor list is created with quadratic complexity here, - // but the number of vendors is going to be very small this shouldn't be a problem. - - list_vendor->Clear(); - list_vendor->append(_L("(All)"), &EMPTY); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - // find printer preset - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, printer_name, type, EMPTY, [this](const Preset* p) { - const std::string& vendor = this->materials->get_vendor(p); - if (list_vendor->find(vendor) == wxNOT_FOUND) { - list_vendor->append(vendor, &vendor); - } - }); - } - sort_list_data(list_vendor, true, false); - } - - sel_type_prev = sel_type; - sel_vendor = 0; - sel_vendor_prev = wxNOT_FOUND; - list_vendor->SetSelection(sel_vendor); - list_profile->Clear(); - } - - if (sel_vendor != sel_vendor_prev) { - // Refresh material list - - list_profile->Clear(); - clear_compatible_printers_label(); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - const std::string& vendor = list_vendor->get_data(sel_vendor); - // first printer preset - std::vector to_list; - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, printer_name, type, vendor, [this, &to_list](const Preset* p) { - const std::string& section = materials->appconfig_section(); - bool checked = wizard_p()->appconfig_new.has(section, p->name); - bool was_checked = false; - - int cur_i = list_profile->find(p->alias); - if (cur_i == wxNOT_FOUND) { - cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) || template_shown ? "" : " *"), &p->alias); - to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); - } - else { - was_checked = list_profile->IsChecked(cur_i); - to_list[cur_i].checked = checked || was_checked; - } - list_profile->Check(cur_i, checked || was_checked); - - /* Update preset selection in config. - * If one preset from aliases bundle is selected, - * than mark all presets with this aliases as selected - * */ - if (checked && !was_checked) - wizard_p()->update_presets_in_config(section, p->alias, true); - else if (!checked && was_checked) - wizard_p()->appconfig_new.set(section, p->name, "1"); - }); - } - sort_list_data(list_profile, to_list); - } - - sel_vendor_prev = sel_vendor; - } - wxGetApp().UpdateDarkUI(list_profile); -} - -void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) -{ -// get data from list -// sort data -// first should be -// then prusa profiles -// then the rest -// in alphabetical order - - std::vector> prusa_profiles; - std::vector> other_profiles; - bool add_TEMPLATES_item = false; - for (int i = 0 ; i < list->size(); ++i) { - const std::string& data = list->get_data(i); - if (data == EMPTY) // do not sort item - continue; - if (data == TEMPLATES) {// do not sort item - add_TEMPLATES_item = true; - continue; - } - if (!material_type_ordering && data.find("Prusa") != std::string::npos) - prusa_profiles.push_back(data); - else - other_profiles.push_back(data); - } - if(material_type_ordering) { - - const ConfigOptionDef* def = print_config_def.get("filament_type"); - std::vectorenum_values = def->enum_values; - size_t end_of_sorted = 0; - for (size_t vals = 0; vals < enum_values.size(); vals++) { - for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) - { - // find instead compare because PET vs PETG - if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { - //swap - if(profs != end_of_sorted) { - std::reference_wrapper aux = other_profiles[end_of_sorted]; - other_profiles[end_of_sorted] = other_profiles[profs]; - other_profiles[profs] = aux; - } - end_of_sorted++; - break; - } - } - } - } else { - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - } - - list->Clear(); - if (add_All_item) - list->append(_L("(All)"), &EMPTY); - if (materials->technology == T_FFF && add_TEMPLATES_item) - list->append(_L("(Templates)"), &TEMPLATES); - for (const auto& item : prusa_profiles) - list->append(item, &const_cast(item.get())); - for (const auto& item : other_profiles) - list->append(item, &const_cast(item.get())); - -} - -void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) -{ - // sort data - // then prusa profiles - // then the rest - // in alphabetical order - std::vector prusa_profiles; - std::vector other_profiles; - //for (int i = 0; i < data.size(); ++i) { - for (const auto& item : data) { - const std::string& name = item.name; - if (name.find("Prusa") != std::string::npos) - prusa_profiles.emplace_back(item); - else - other_profiles.emplace_back(item); - } - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - list->Clear(); - for (size_t i = 0; i < prusa_profiles.size(); ++i) { - list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); - list->Check(i, prusa_profiles[i].checked); - } - for (size_t i = 0; i < other_profiles.size(); ++i) { - list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(other_profiles[i].name.get())); - list->Check(i + prusa_profiles.size(), other_profiles[i].checked); - } -} - -void PageMaterials::select_material(int i) -{ - const bool checked = list_profile->IsChecked(i); - - const std::string& alias_key = list_profile->get_data(i); - if (checked && template_shown && !notification_shown) { - notification_shown = true; - wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); - MessageDialog msg(this, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_NO) { - list_profile->Check(i, false); - return; - } - } - wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); -} - -void PageMaterials::select_all(bool select) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - for (unsigned i = 0; i < list_profile->GetCount(); i++) { - const bool current = list_profile->IsChecked(i); - if (current != select) { - list_profile->Check(i, select); - select_material(i); - } - } -} - -void PageMaterials::clear() -{ - list_printer->Clear(); - list_type->Clear(); - list_vendor->Clear(); - list_profile->Clear(); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - presets_loaded = false; -} - -void PageMaterials::on_activate() -{ - if (! presets_loaded) { - wizard_p()->update_materials(materials->technology); - reload_presets(); - } - first_paint = true; -} - - -const char *PageCustom::default_profile_name = "My Settings"; - -PageCustom::PageCustom(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer")) -{ - cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile")); - auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); - - wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL); - profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name }; - profile_name_editor->Enable(false); - - cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { - profile_name_editor->Enable(custom_wanted()); - wizard_p()->on_custom_setup(custom_wanted()); - }); - - append(cb_custom); - append(label); - append(profile_name_sizer); -} - -PageUpdate::PageUpdate(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates")) - , version_check(true) - , preset_update(true) -{ - const AppConfig *app_config = wxGetApp().app_config; - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates")); - box_slic3r->SetValue(app_config->get("notify_release") != "none"); - append(box_slic3r); - append_text(wxString::Format(_L( - "If enabled, %s checks for new application versions online. When a new version becomes available, " - "a notification is displayed at the next application startup (never during program usage). " - "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME)); - - append_spacer(VERTICAL_SPACING); - - auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically")); - box_presets->SetValue(app_config->get("preset_update") == "1"); - append(box_presets); - append_text(wxString::Format(_L( - "If enabled, %s downloads updates of built-in system presets in the background." - "These updates are downloaded into a separate temporary location." - "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME)); - const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings."); - auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); - label_bold->SetFont(boldfont); - label_bold->Wrap(WRAP_WIDTH); - append(label_bold); - append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")); - - box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); - box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); -} - -namespace DownloaderUtils -{ -#ifdef _WIN32 - - wxString get_downloads_path() - { - wxString ret; - PWSTR path = NULL; - HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path); - if (SUCCEEDED(hr)) { - ret = wxString(path); - } - CoTaskMemFree(path); - return ret; - } - -#elif __APPLE__ - wxString get_downloads_path() - { - // call objective-c implementation - return wxString::FromUTF8(get_downloads_path_mac()); - } -#else - wxString get_downloads_path() - { - wxString command = "xdg-user-dir DOWNLOAD"; - wxArrayString output; - GUI::desktop_execute_get_result(command, output); - if (output.GetCount() > 0) { - return output[0]; - } - return wxString(); - } - -#endif - -Worker::Worker(wxWindow* parent) -: wxBoxSizer(wxHORIZONTAL) -, m_parent(parent) -{ - m_input_path = new wxTextCtrl(m_parent, wxID_ANY); - set_path_name(get_app_config()->get("url_downloader_dest")); - - auto* path_label = new wxStaticText(m_parent, wxID_ANY, _L("Download path") + ":"); - - this->Add(path_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - this->Add(m_input_path, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); - - auto* button_path = new wxButton(m_parent, wxID_ANY, _L("Browse")); - this->Add(button_path, 0, wxEXPAND | wxTOP | wxLEFT, 5); - button_path->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { - boost::filesystem::path chosen_dest(boost::nowide::narrow(m_input_path->GetValue())); - - wxDirDialog dialog(m_parent, L("Choose folder:"), chosen_dest.string() ); - if (dialog.ShowModal() == wxID_OK) - this->m_input_path->SetValue(dialog.GetPath()); - }); - - for (wxSizerItem* item : this->GetChildren()) - if (item->IsWindow()) { - wxWindow* win = item->GetWindow(); - wxGetApp().UpdateDarkUI(win); - } -} - -void Worker::set_path_name(wxString path) -{ - if (path.empty()) - path = boost::nowide::widen(get_app_config()->get("url_downloader_dest")); - - if (path.empty()) { - // What should be default path? Each system has Downloads folder, that could be good one. - // Other would be program location folder - not so good: access rights, apple bin is inside bundle... - // default_path = boost::dll::program_location().parent_path().string(); - path = get_downloads_path(); - } - - m_input_path->SetValue(path); -} - -void Worker::set_path_name(const std::string& name) -{ - if (!m_input_path) - return; - - set_path_name(boost::nowide::widen(name)); -} - -} // DownLoader - -PageDownloader::PageDownloader(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Downloads from URL"), _L("Downloads")) -{ - const AppConfig* app_config = wxGetApp().app_config; - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - append_spacer(VERTICAL_SPACING); - - auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow build-in downloader")); - // TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run. - bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get("downloader_url_registered") == "1" : true); - box_allow_downloads->SetValue(box_allow_value); - append(box_allow_downloads); - - append_text(wxString::Format(_L( - "If enabled, %s registers to start on custom URL on www.printables.com." - " You will be able to use button with %s logo to open models in this %s." - " The model will be downloaded into folder you choose bellow." - ), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME)); - -#ifdef __linux__ - append_text(wxString::Format(_L( - "On Linux systems the process of registration also creates desktop integration files for this version of application." - ))); -#endif - - box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); }); - - downloader = new DownloaderUtils::Worker(this); - append(downloader); - downloader->allow(box_allow_value); -} - -bool PageDownloader::on_finish_downloader() const -{ - return downloader->on_finish(); -} - -bool DownloaderUtils::Worker::perform_register() -{ - //boost::filesystem::path chosen_dest/*(path_text_ctrl->GetValue());*/(boost::nowide::narrow(path_text_ctrl->GetValue())); - boost::filesystem::path chosen_dest (GUI::format(path_name())); - boost::system::error_code ec; - if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) { - std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not Exists.") ,chosen_dest.string()); - BOOST_LOG_TRIVIAL(error) << err_msg; - show_error(m_parent, err_msg); - return false; - } - BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string(); - wxGetApp().app_config->set("url_downloader_dest", chosen_dest.string()); -#ifdef _WIN32 - // Registry key creation for "prusaslicer://" URL - - boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location())); - // the path to binary needs to be correctly saved in string with respect to localized characters - wxString wbinary = wxString::FromUTF8(binary_path.string()); - std::string binary_string = (boost::format("%1%") % wbinary).str(); - BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string; - - //std::string key_string = "\"" + binary_string + "\" \"-u\" \"%1\""; - //std::string key_string = "\"" + binary_string + "\" \"%1\""; - std::string key_string = "\"" + binary_string + "\" \"--single-instance\" \"%1\""; - - wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\prusaslicer"); - wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); - if (!key_first.Exists()) { - key_first.Create(false); - } - key_first.SetValue("URL Protocol", ""); - - if (!key_full.Exists()) { - key_full.Create(false); - } - //key_full = "\"C:\\Program Files\\Prusa3D\\PrusaSlicer\\prusa-slicer-console.exe\" \"%1\""; - key_full = key_string; -#elif __APPLE__ - // Apple registers for custom url in info.plist thus it has to be already registered since build. - // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) -#else - // the performation should be called later during desktop integration - perform_registration_linux = true; -#endif - return true; -} - -void DownloaderUtils::Worker::deregister() -{ -#ifdef _WIN32 - std::string key_string = ""; - wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); - if (!key_full.Exists()) { - return; - } - key_full = key_string; -#elif __APPLE__ - // TODO -#else - BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration"; - DesktopIntegrationDialog::undo_downloader_registration(); - perform_registration_linux = false; -#endif -} - -bool DownloaderUtils::Worker::on_finish() { - AppConfig* app_config = wxGetApp().app_config; - bool ac_value = app_config->get("downloader_url_registered") == "1"; - BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked; - if (ac_value && downloader_checked) { - // already registered but we need to do it again - if (!perform_register()) - return false; - app_config->set("downloader_url_registered", "1"); - } else if (!ac_value && downloader_checked) { - // register - if (!perform_register()) - return false; - app_config->set("downloader_url_registered", "1"); - } else if (ac_value && !downloader_checked) { - // deregister, downloads are banned now - deregister(); - app_config->set("downloader_url_registered", "0"); - } /*else if (!ac_value && !downloader_checked) { - // not registered and we dont want to do it - // do not deregister as other instance might be registered - } */ - return true; -} - - -PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) - , full_pathnames(false) -{ - auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files")); - box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); - append(box_pathnames); - append_text(_L( - "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" - "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." - )); - - box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); -} - -#ifdef _WIN32 -PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) -{ - cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer")); - cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer")); -// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer")); - - append(cb_3mf); - append(cb_stl); -// append(cb_gcode); -} -#endif // _WIN32 - -PageMode::PageMode(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) -{ - append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" - "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " - "The other two offer progressively more sophisticated fine-tuning, " - "they are suitable for advanced and expert users, respectively.")); - - radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode")); - radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); - radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); - - std::string mode { "simple" }; - wxGetApp().app_config->get("", "view_mode", mode); - - if (mode == "advanced") { radio_advanced->SetValue(true); } - else if (mode == "expert") { radio_expert->SetValue(true); } - else { radio_simple->SetValue(true); } - - append(radio_simple); - append(radio_advanced); - append(radio_expert); - - append_text("\n" + _L("The size of the object can be specified in inches")); - check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); - check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); - append(check_inch); - - on_activate(); -} - -void PageMode::serialize_mode(AppConfig *app_config) const -{ - std::string mode = ""; - - if (radio_simple->GetValue()) { mode = "simple"; } - if (radio_advanced->GetValue()) { mode = "advanced"; } - if (radio_expert->GetValue()) { mode = "expert"; } - - app_config->set("view_mode", mode); - app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); -} - -PageVendors::PageVendors(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) -{ - const AppConfig &appconfig = this->wizard_p()->appconfig_new; - - append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":"); - - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - for (const auto &pair : wizard_p()->bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); - cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { - wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); - }); - - const auto &vendors = appconfig.vendors(); - const bool enabled = vendors.find(pair.first) != vendors.end(); - if (enabled) { - cbox->SetValue(true); - - auto pages = wizard_p()->pages_3rdparty.find(vendor->id); - wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); - - for (PagePrinters* page : { pages->second.first, pages->second.second }) - if (page) page->install = true; - } - - append(cbox); - } -} - -PageFirmware::PageFirmware(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1) - , gcode_opt(*print_config_def.get("gcode_flavor")) - , gcode_picker(nullptr) -{ - append_text(_L("Choose the type of firmware used by your printer.")); - append_text(_(gcode_opt.tooltip)); - - wxArrayString choices; - choices.Alloc(gcode_opt.enum_labels.size()); - for (const auto &label : gcode_opt.enum_labels) { - choices.Add(label); - } - - gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); - wxGetApp().UpdateDarkUI(gcode_picker); - const auto &enum_values = gcode_opt.enum_values; - auto needle = enum_values.cend(); - if (gcode_opt.default_value) { - needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); - } - if (needle != enum_values.cend()) { - gcode_picker->SetSelection(needle - enum_values.cbegin()); - } else { - gcode_picker->SetSelection(0); - } - - append(gcode_picker); -} - -void PageFirmware::apply_custom_config(DynamicPrintConfig &config) -{ - auto sel = gcode_picker->GetSelection(); - if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) { - auto *opt = new ConfigOptionEnum(static_cast(sel)); - config.set_key_value("gcode_flavor", opt); - } -} - -static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) -{ - e.Skip(); - wxString str = ctrl->GetValue(); - - const char dec_sep = is_decimal_separator_point() ? '.' : ','; - const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; - // Replace the first incorrect separator in decimal number. - bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; - - double val = 0.0; - if (!str.ToDouble(&val)) { - if (val == 0.0) - val = def_value; - ctrl->SetValue(double_to_string(val)); - show_error(nullptr, _L("Invalid numeric input.")); - // On Windows, this SetFocus creates an invisible marker. - //ctrl->SetFocus(); - } - else if (was_replaced) - ctrl->SetValue(double_to_string(val)); -} - -class DiamTextCtrl : public wxTextCtrl -{ -public: - DiamTextCtrl(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxBORDER_SIMPLE; -#else - long style = 0; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style); - wxGetApp().UpdateDarkUI(this); - } - ~DiamTextCtrl() {} -}; - -PageBedShape::PageBedShape(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) - , shape_panel(new BedShapePanel(this)) -{ - append_text(_L("Set the shape of your printer's bed.")); - - shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), - *wizard_p()->custom_config->option("bed_custom_texture"), - *wizard_p()->custom_config->option("bed_custom_model")); - - append(shape_panel); -} - -void PageBedShape::apply_custom_config(DynamicPrintConfig &config) -{ - const std::vector& points = shape_panel->get_shape(); - const std::string& custom_texture = shape_panel->get_custom_texture(); - const std::string& custom_model = shape_panel->get_custom_model(); - config.set_key_value("bed_shape", new ConfigOptionPoints(points)); - config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); - config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); -} - -PageBuildVolume::PageBuildVolume(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1) - , build_volume(new DiamTextCtrl(this)) -{ - append_text(_L("Set verctical size of your printer.")); - - wxString value = "200"; - build_volume->SetValue(value); - - build_volume->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { - double def_value = 200.0; - double max_value = 1200.0; - e.Skip(); - wxString str = build_volume->GetValue(); - - const char dec_sep = is_decimal_separator_point() ? '.' : ','; - const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; - // Replace the first incorrect separator in decimal number. - bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; - - double val = 0.0; - if (!str.ToDouble(&val)) { - val = def_value; - build_volume->SetValue(double_to_string(val)); - show_error(nullptr, _L("Invalid numeric input.")); - //build_volume->SetFocus(); - } else if (val < 0.0) { - val = def_value; - build_volume->SetValue(double_to_string(val)); - show_error(nullptr, _L("Invalid numeric input.")); - //build_volume->SetFocus(); - } else if (val > max_value) { - val = max_value; - build_volume->SetValue(double_to_string(val)); - show_error(nullptr, _L("Invalid numeric input.")); - //build_volume->SetFocus(); - } else if (was_replaced) - build_volume->SetValue(double_to_string(val)); - }, build_volume->GetId()); - - auto* sizer_volume = new wxFlexGridSizer(3, 5, 5); - auto* text_volume = new wxStaticText(this, wxID_ANY, _L("Max print height:")); - auto* unit_volume = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_volume->AddGrowableCol(0, 1); - sizer_volume->Add(text_volume, 0, wxALIGN_CENTRE_VERTICAL); - sizer_volume->Add(build_volume); - sizer_volume->Add(unit_volume, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_volume); -} - -void PageBuildVolume::apply_custom_config(DynamicPrintConfig& config) -{ - double val = 0.0; - build_volume->GetValue().ToDouble(&val); - auto* opt_volume = new ConfigOptionFloat(val); - config.set_key_value("max_print_height", opt_volume); -} - -PageDiameters::PageDiameters(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) - , diam_nozzle(new DiamTextCtrl(this)) - , diam_filam (new DiamTextCtrl(this)) -{ - auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); - wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); - diam_nozzle->SetValue(value); - - auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); - value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); - diam_filam->SetValue(value); - - diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); - diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); - - append_text(_L("Enter the diameter of your printer's hot end nozzle.")); - - auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); - auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:")); - auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_nozzle->AddGrowableCol(0, 1); - sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - sizer_nozzle->Add(diam_nozzle); - sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_nozzle); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the diameter of your filament.")); - append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")); - - auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); - auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:")); - auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_filam->AddGrowableCol(0, 1); - sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_filam); -} - -void PageDiameters::apply_custom_config(DynamicPrintConfig &config) -{ - double val = 0.0; - diam_nozzle->GetValue().ToDouble(&val); - auto *opt_nozzle = new ConfigOptionFloats(1, val); - config.set_key_value("nozzle_diameter", opt_nozzle); - - val = 0.0; - diam_filam->GetValue().ToDouble(&val); - auto * opt_filam = new ConfigOptionFloats(1, val); - config.set_key_value("filament_diameter", opt_filam); - - auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { - char buf[64]; // locales don't matter here (sprintf/atof) - sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); - config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); - }; - - set_extrusion_width("support_material_extrusion_width", 0.35); - set_extrusion_width("top_infill_extrusion_width", 0.40); - set_extrusion_width("first_layer_extrusion_width", 0.42); - - set_extrusion_width("extrusion_width", 0.45); - set_extrusion_width("perimeter_extrusion_width", 0.45); - set_extrusion_width("external_perimeter_extrusion_width", 0.45); - set_extrusion_width("infill_extrusion_width", 0.45); - set_extrusion_width("solid_infill_extrusion_width", 0.45); -} - -class SpinCtrlDouble: public wxSpinCtrlDouble -{ -public: - SpinCtrlDouble(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; -#else - long style = wxSP_ARROW_KEYS; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style); -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(this->GetText()); -#endif - this->Refresh(); - } - ~SpinCtrlDouble() {} -}; - -PageTemperatures::PageTemperatures(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1) - , spin_extr(new SpinCtrlDouble(this)) - , spin_bed (new SpinCtrlDouble(this)) -{ - spin_extr->SetIncrement(5.0); - const auto &def_extr = *print_config_def.get("temperature"); - spin_extr->SetRange(def_extr.min, def_extr.max); - auto *default_extr = def_extr.get_default_value(); - spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); - - spin_bed->SetIncrement(5.0); - const auto &def_bed = *print_config_def.get("bed_temperature"); - spin_bed->SetRange(def_bed.min, def_bed.max); - auto *default_bed = def_bed.get_default_value(); - spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); - - append_text(_L("Enter the temperature needed for extruding your filament.")); - append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")); - - auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); - auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:")); - auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_extr->AddGrowableCol(0, 1); - sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); - sizer_extr->Add(spin_extr); - sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_extr); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")); - append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")); - - auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); - auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:")); - auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_bed->AddGrowableCol(0, 1); - sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); - sizer_bed->Add(spin_bed); - sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_bed); -} - -void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) -{ - auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("temperature", opt_extr); - auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("first_layer_temperature", opt_extr1st); - auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("bed_temperature", opt_bed); - auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("first_layer_bed_temperature", opt_bed1st); -} - - -// Index - -ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) - : wxPanel(parent) - , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192)) - , bullet_black(ScalableBitmap(parent, "bullet_black.png")) - , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) - , bullet_white(ScalableBitmap(parent, "bullet_white.png")) - , item_active(NO_ITEM) - , item_hover(NO_ITEM) - , last_page((size_t)-1) -{ -#ifndef __WXOSX__ - SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX -#endif //__WXOSX__ - SetMinSize(bg.GetSize()); - - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); - Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); }); - Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this); - - Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) { - if (item_hover != -1) { - item_hover = -1; - Refresh(); - } - evt.Skip(); - }); - - Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) { - if (item_hover >= 0) { go_to(item_hover); } - }); -} - -wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); - -void ConfigWizardIndex::add_page(ConfigWizardPage *page) -{ - last_page = items.size(); - items.emplace_back(Item { page->shortname, page->indent, page }); - Refresh(); -} - -void ConfigWizardIndex::add_label(wxString label, unsigned indent) -{ - items.emplace_back(Item { std::move(label), indent, nullptr }); - Refresh(); -} - -ConfigWizardPage* ConfigWizardIndex::active_page() const -{ - if (item_active >= items.size()) { return nullptr; } - - return items[item_active].page; -} - -void ConfigWizardIndex::go_prev() -{ - // Search for a preceiding item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active; i > 0; i--) { - if (items[i - 1].page != nullptr) { - go_to(i - 1); - return; - } - } -} - -void ConfigWizardIndex::go_next() -{ - // Search for a next item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active + 1; i < items.size(); i++) { - if (items[i].page != nullptr) { - go_to(i); - return; - } - } -} - -// This one actually performs the go-to op -void ConfigWizardIndex::go_to(size_t i) -{ - if (i != item_active - && i < items.size() - && items[i].page != nullptr) { - auto *new_active = items[i].page; - auto *former_active = active_page(); - if (former_active != nullptr) { - former_active->Hide(); - } - - item_active = i; - new_active->Show(); - - wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); - AddPendingEvent(evt); - - Refresh(); - - new_active->on_activate(); - } -} - -void ConfigWizardIndex::go_to(const ConfigWizardPage *page) -{ - if (page == nullptr) { return; } - - for (size_t i = 0; i < items.size(); i++) { - if (items[i].page == page) { - go_to(i); - return; - } - } -} - -void ConfigWizardIndex::clear() -{ - auto *former_active = active_page(); - if (former_active != nullptr) { former_active->Hide(); } - - items.clear(); - item_active = NO_ITEM; -} - -void ConfigWizardIndex::on_paint(wxPaintEvent & evt) -{ - const auto size = GetClientSize(); - if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } - - wxPaintDC dc(this); - - const auto bullet_w = bullet_black.GetWidth(); - const auto bullet_h = bullet_black.GetHeight(); - const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; - const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; - const int yinc = item_height(); - - int index_width = 0; - - unsigned y = 0; - for (size_t i = 0; i < items.size(); i++) { - const Item& item = items[i]; - unsigned x = em_w/2 + item.indent * em_w; - - if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { - dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false); - } - else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); } - else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); } - - x += + bullet_w + em_w/2; - const auto text_size = dc.GetTextExtent(item.label); - dc.SetTextForeground(wxGetApp().get_label_clr_default()); - dc.DrawText(item.label, x, y + yoff_text); - - y += yinc; - index_width = std::max(index_width, (int)x + text_size.x); - } - - //draw logo - if (int y = size.y - bg.GetHeight(); y>=0) { - dc.DrawBitmap(bg.get_bitmap(), 0, y, false); - index_width = std::max(index_width, bg.GetWidth() + em_w / 2); - } - - if (GetMinSize().x < index_width) { - CallAfter([this, index_width]() { - SetMinSize(wxSize(index_width, GetMinSize().y)); - Refresh(); - }); - } -} - -void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) -{ - const wxClientDC dc(this); - const wxPoint pos = evt.GetLogicalPosition(dc); - - const ssize_t item_hover_new = pos.y / item_height(); - - if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { - item_hover = item_hover_new; - Refresh(); - } - - evt.Skip(); -} - -void ConfigWizardIndex::msw_rescale() -{ - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - SetMinSize(bg.GetSize()); - - Refresh(); -} - - -// Materials - -const std::string Materials::UNKNOWN = "(Unknown)"; - -void Materials::push(const Preset *preset) -{ - presets.emplace_back(preset); - types.insert(technology & T_FFF - ? Materials::get_filament_type(preset) - : Materials::get_material_type(preset)); -} - -void Materials::add_printer(const Preset* preset) -{ - printers.insert(preset); -} - -void Materials::clear() -{ - presets.clear(); - types.clear(); - printers.clear(); - compatibility_counter.clear(); -} - -const std::string& Materials::appconfig_section() const -{ - return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; -} - -const std::string& Materials::get_type(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); -} - -const std::string& Materials::get_vendor(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); -} - -const std::string& Materials::get_filament_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_type"); - if (opt != nullptr && opt->values.size() > 0) { - return opt->values[0]; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_filament_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -const std::string& Materials::get_material_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_type"); - if (opt != nullptr) { - return opt->value; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_material_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -// priv - -static const std::unordered_map> legacy_preset_map {{ - { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, - { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, - { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, -}}; - -void ConfigWizard::priv::load_pages() -{ - wxWindowUpdateLocker freeze_guard(q); - (void)freeze_guard; - - const ConfigWizardPage *former_active = index->active_page(); - - index->clear(); - - index->add_page(page_welcome); - - // Printers - if (!only_sla_mode) - index->add_page(page_fff); - index->add_page(page_msla); - if (!only_sla_mode) { - index->add_page(page_vendors); - for (const auto &pages : pages_3rdparty) { - for ( PagePrinters* page : { pages.second.first, pages.second.second }) - if (page && page->install) - index->add_page(page); - } - - index->add_page(page_custom); - if (page_custom->custom_wanted()) { - index->add_page(page_firmware); - index->add_page(page_bed); - index->add_page(page_bvolume); - index->add_page(page_diams); - index->add_page(page_temps); - } - - // Filaments & Materials - if (any_fff_selected) { index->add_page(page_filaments); } - // Filaments page if only custom printer is selected - const AppConfig* app_config = wxGetApp().app_config; - if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) { - update_materials(T_ANY); - index->add_page(page_filaments); - } - } - if (any_sla_selected) { index->add_page(page_sla_materials); } - - // there should to be selected at least one printer - btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle); - - index->add_page(page_update); - index->add_page(page_downloader); - index->add_page(page_reload_from_disk); -#ifdef _WIN32 - index->add_page(page_files_association); -#endif // _WIN32 - index->add_page(page_mode); - - index->go_to(former_active); // Will restore the active item/page if possible - - q->Layout(); -// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig - q->Refresh(); -} - -void ConfigWizard::priv::init_dialog_size() -{ - // Clamp the Wizard size based on screen dimensions - - const auto idx = wxDisplay::GetFromWindow(q); - wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); - - const auto disp_rect = display.GetClientArea(); - wxRect window_rect( - disp_rect.x + disp_rect.width / 20, - disp_rect.y + disp_rect.height / 20, - 9*disp_rect.width / 10, - 9*disp_rect.height / 10); - - const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution - if (width_hint < window_rect.width) { - window_rect.x += (window_rect.width - width_hint) / 2; - window_rect.width = width_hint; - } - - q->SetSize(window_rect); -} - -void ConfigWizard::priv::load_vendors() -{ - bundles = BundleMap::load(); - - // Load up the set of vendors / models / variants the user has had enabled up till now - AppConfig *app_config = wxGetApp().app_config; - if (! app_config->legacy_datadir()) { - appconfig_new.set_vendors(*app_config); - } else { - // In case of legacy datadir, try to guess the preference based on the printer preset files that are present - const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; - for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) - if (Slic3r::is_ini_file(dir_entry)) { - auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); - if (needle == legacy_preset_map.end()) { continue; } - - const auto &model = needle->second.first; - const auto &variant = needle->second.second; - appconfig_new.set_variant("PrusaResearch", model, variant, true); - } - } - - for (const auto& printer : wxGetApp().preset_bundle->printers) { - if (!printer.is_default && !printer.is_system && printer.is_visible) { - custom_printer_in_bundle = true; - break; - } - } - - // Initialize the is_visible flag in printer Presets - for (auto &pair : bundles) { - pair.second.preset_bundle->load_installed_printers(appconfig_new); - } - - // Copy installed filaments and SLA material names from app_config to appconfig_new - // while resolving current names of profiles, which were renamed in the meantime. - for (PrinterTechnology technology : { ptFFF, ptSLA }) { - const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; - std::map section_new; - if (app_config->has_section(section_name)) { - const std::map §ion_old = app_config->get_section(section_name); - for (const auto& material_name_and_installed : section_old) - if (material_name_and_installed.second == "1") { - // Material is installed. Resolve it in bundles. - size_t num_found = 0; - const std::string &material_name = material_name_and_installed.first; - for (auto &bundle : bundles) { - const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); - const Preset *preset = materials.find_preset(material_name); - if (preset == nullptr) { - // Not found. Maybe the material preset is there, bu it was was renamed? - const std::string *new_name = materials.get_preset_name_renamed(material_name); - if (new_name != nullptr) - preset = materials.find_preset(*new_name); - } - if (preset != nullptr) { - // Materal preset was found, mark it as installed. - section_new[preset->name] = "1"; - ++ num_found; - } - } - if (num_found == 0) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name; - else if (num_found > 1) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found; - } - } - appconfig_new.set_section(section_name, section_new); - }; -} - -void ConfigWizard::priv::add_page(ConfigWizardPage *page) -{ - const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; - hscroll_sizer->Add(page, proportion, wxEXPAND); - all_pages.push_back(page); -} - -void ConfigWizard::priv::enable_next(bool enable) -{ - btn_next->Enable(enable); - btn_finish->Enable(enable); -} - -void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) -{ - switch (start_page) { - case ConfigWizard::SP_PRINTERS: - index->go_to(page_fff); - btn_next->SetFocus(); - break; - case ConfigWizard::SP_FILAMENTS: - index->go_to(page_filaments); - btn_finish->SetFocus(); - break; - case ConfigWizard::SP_MATERIALS: - index->go_to(page_sla_materials); - btn_finish->SetFocus(); - break; - default: - index->go_to(page_welcome); - btn_next->SetFocus(); - break; - } -} - -void ConfigWizard::priv::create_3rdparty_pages() -{ - for (const auto &pair : bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - bool is_fff_technology = false; - bool is_sla_technology = false; - - for (auto& model: vendor->models) - { - if (!is_fff_technology && model.technology == ptFFF) - is_fff_technology = true; - if (!is_sla_technology && model.technology == ptSLA) - is_sla_technology = true; - } - - PagePrinters* pageFFF = nullptr; - PagePrinters* pageSLA = nullptr; - - if (is_fff_technology) { - pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF); - add_page(pageFFF); - } - - if (is_sla_technology) { - pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA); - add_page(pageSLA); - } - - pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}}); - } -} - -void ConfigWizard::priv::set_run_reason(RunReason run_reason) -{ - this->run_reason = run_reason; - for (auto &page : all_pages) { - page->set_run_reason(run_reason); - } -} - -void ConfigWizard::priv::update_materials(Technology technology) -{ - if ((any_fff_selected || custom_printer_in_bundle || custom_printer_selected) && (technology & T_FFF)) { - filaments.clear(); - aliases_fff.clear(); - // Iterate filaments in all bundles - for (const auto &pair : bundles) { - for (const auto &filament : pair.second.preset_bundle->filaments) { - // Check if filament is already added - if (filaments.containts(&filament)) - continue; - // Iterate printers in all bundles - for (const auto &printer : pair.second.preset_bundle->printers) { - if (!printer.is_visible || printer.printer_technology() != ptFFF) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { - if (!filaments.containts(&filament)) { - filaments.push(&filament); - if (!filament.alias.empty()) - aliases_fff[filament.alias].insert(filament.name); - } - filaments.add_printer(&printer); - } - } - // template filament bundle has no printers - filament would be never added - if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) - { - if (!filaments.containts(&filament)) { - filaments.push(&filament); - if (!filament.alias.empty()) - aliases_fff[filament.alias].insert(filament.name); - } - } - } - } - // count compatible printers - for (const auto& preset : filaments.presets) { - // skip template filaments - if (preset->vendor && preset->vendor->templates_profile) - continue; - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { - continue; - } - // find all aliases (except templates) - std::vector idx_with_same_alias; - for (size_t i = 0; i < filaments.presets.size(); ++i) { - if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor)) - idx_with_same_alias.push_back(i); - } - // check compatibility with each printer - size_t counter = 0; - for (const auto& printer : filaments.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) - continue; - bool compatible = false; - // Test other materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - filaments.compatibility_counter.emplace_back(preset->alias, counter); - } - } - - if (any_sla_selected && (technology & T_SLA)) { - sla_materials.clear(); - aliases_sla.clear(); - - // Iterate SLA materials in all bundles - for (const auto &pair : bundles) { - for (const auto &material : pair.second.preset_bundle->sla_materials) { - // Check if material is already added - if (sla_materials.containts(&material)) - continue; - // Iterate printers in all bundles - // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. - for (const auto& printer : pair.second.preset_bundle->printers) { - if(!printer.is_visible || printer.printer_technology() != ptSLA) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - // Check if material is already added - if(!sla_materials.containts(&material)) { - sla_materials.push(&material); - if (!material.alias.empty()) - aliases_sla[material.alias].insert(material.name); - } - sla_materials.add_printer(&printer); - } - } - } - } - // count compatible printers - for (const auto& preset : sla_materials.presets) { - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { - continue; - } - std::vector idx_with_same_alias; - for (size_t i = 0; i < sla_materials.presets.size(); ++i) { - if(preset->alias == sla_materials.presets[i]->alias) - idx_with_same_alias.push_back(i); - } - size_t counter = 0; - for (const auto& printer : sla_materials.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) - continue; - bool compatible = false; - // Test otrher materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - sla_materials.compatibility_counter.emplace_back(preset->alias, counter); - } - } -} - -void ConfigWizard::priv::on_custom_setup(const bool custom_wanted) -{ - custom_printer_selected = custom_wanted; - load_pages(); -} - -void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) -{ - if (check_sla_selected() != any_sla_selected || - check_fff_selected() != any_fff_selected) { - any_fff_selected = check_fff_selected(); - any_sla_selected = check_sla_selected(); - - load_pages(); - } - - // Update the is_visible flag on relevant printer profiles - for (auto &pair : bundles) { - if (pair.first != evt.vendor_id) { continue; } - - for (auto &preset : pair.second.preset_bundle->printers) { - if (preset.config.opt_string("printer_model") == evt.model_id - && preset.config.opt_string("printer_variant") == evt.variant_name) { - preset.is_visible = evt.enable; - } - } - - // When a printer model is picked, but there is no material installed compatible with this printer model, - // install default materials for selected printer model silently. - check_and_install_missing_materials(page->technology, evt.model_id); - } - - if (page->technology & T_FFF) { - page_filaments->clear(); - } else if (page->technology & T_SLA) { - page_sla_materials->clear(); - } -} - -void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology) -{ - PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - for (const std::string& material : printer_model.default_materials) - appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); -} - -void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models) -{ - PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - const std::string &appconfig_section = page_materials->materials->appconfig_section(); - - // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. - // Filament is selected on same page for all printers of same technology. - /* - auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology) - { - const std::string vendor_id = page_printers->get_vendor_id(); - for (auto& pair : bundles) - if (pair.first == vendor_id) - for (const VendorProfile::PrinterModel *printer_model : printer_models) - for (const std::string &material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - }; - - PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; - select_default_materials_for_printer_page(page_printers, technology); - - for (const auto& printer : pages_3rdparty) - { - page_printers = technology & T_FFF ? printer.second.first : printer.second.second; - if (page_printers) - select_default_materials_for_printer_page(page_printers, technology); - } - */ - - // Iterate printer_models and select default materials. If none available -> msg to user. - std::vector models_without_default; - for (const VendorProfile::PrinterModel* printer_model : printer_models) { - if (printer_model->default_materials.empty()) { - models_without_default.emplace_back(printer_model); - } else { - for (const std::string& material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - } - } - - if (!models_without_default.empty()) { - std::string printer_names = "\n\n"; - for (const VendorProfile::PrinterModel* printer_model : models_without_default) { - printer_names += printer_model->name + "\n"; - } - printer_names += "\n\n"; - std::string message = (technology & T_FFF ? - GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) : - GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names)); - MessageDialog msg(q, message, _L("Notice"), wxOK); - msg.ShowModal(); - } - - update_materials(technology); - ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets(); -} - -void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) -{ - auto it = pages_3rdparty.find(vendor->id); - wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); - - for (PagePrinters* page : { it->second.first, it->second.second }) - if (page) { - if (page->install && !install) - page->select_all(false); - page->install = install; - // if some 3rd vendor is selected, select first printer for them - if (install) - page->printer_pickers[0]->select_one(0, true); - page->Layout(); - } - - load_pages(); -} - -bool ConfigWizard::priv::on_bnt_finish() -{ - wxBusyCursor wait; - - if (!page_downloader->on_finish_downloader()) { - index->go_to(page_downloader); - return false; - } - /* When Filaments or Sla Materials pages are activated, - * materials for this pages are automaticaly updated and presets are reloaded. - * - * But, if _Finish_ button was clicked without activation of those pages - * (for example, just some printers were added/deleted), - * than last changes wouldn't be updated for filaments/materials. - * SO, do that before close of Wizard - */ - update_materials(T_ANY); - if (any_fff_selected) - page_filaments->reload_presets(); - if (any_sla_selected) - page_sla_materials->reload_presets(); - - // theres no need to check that filament is selected if we have only custom printer - if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; - // check, that there is selected at least one filament/material - return check_and_install_missing_materials(T_ANY); -} - -// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed -// for each Printer preset of each Printer Model installed. -// -// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently. -// Otherwise the user is quieried whether to install the missing default materials or not. -// -// Return true if the tested Printer Models already had materials installed. -// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these -// respective Printer Models or not. -bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id) -{ - // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle, - // which is compatible with it. - const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion) - { - const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map(); - std::set printer_models_without_material; - for (const auto &pair : bundles) { - const PresetCollection &materials = pair.second.preset_bundle->materials(technology); - for (const auto &printer : pair.second.preset_bundle->printers) { - if (printer.is_visible && printer.printer_technology() == technology) { - const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer); - assert(printer_model != nullptr); - if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) && - printer_models_without_material.find(printer_model) == printer_models_without_material.end()) { - bool has_material = false; - for (const auto& preset : appconfig_presets) { - if (preset.second == "1") { - const Preset *material = materials.find_preset(preset.first, false); - if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - has_material = true; - break; - } - - // find if preset.first is part of the templates profile (up is searching if preset.first is part of printer vendor preset) - for (const auto& bp : bundles) { - if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { - const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); - const Preset* template_material = template_materials.find_preset(preset.first, false); - if (template_material && is_compatible_with_printer(PresetWithVendorProfile(*template_material, &bp.second.preset_bundle->vendors.begin()->second), PresetWithVendorProfile(printer, nullptr))) { - has_material = true; - break; - } - } - } - if (has_material) - break; - - } - } - if (! has_material) - printer_models_without_material.insert(printer_model); - } - } - } - } - // template_profile_selected check - template_profile_selected = false; - for (const auto& bp : bundles) { - if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { - for (const auto& preset : appconfig_presets) { - const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); - const Preset* template_material = template_materials.find_preset(preset.first, false); - if (template_material){ - template_profile_selected = true; - break; - } - } - if (template_profile_selected) { - break; - } - } - } - assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); - return printer_models_without_material; - }; - - const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) - { - //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); - MessageDialog msg(q, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_YES) - select_default_materials_for_printer_models(technology, printer_models); - }; - - const auto printer_model_list = [](const std::set &printer_models) -> wxString { - wxString out; - for (const VendorProfile::PrinterModel *printer_model : printer_models) { - wxString name = from_u8(printer_model->name); - out += "\t\t"; - out += name; - out += "\n"; - } - return out; - }; - - if (any_fff_selected && (technology & T_FFF)) { - std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following FFF printer models have no filament selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default filaments for these FFF printer models?"), - printer_models_without_material, - T_FFF); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF); - return false; - } - } - - if (any_sla_selected && (technology & T_SLA)) { - std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following SLA printer models have no materials selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default SLA materials for these printer models?"), - printer_models_without_material, - T_SLA); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA); - return false; - } - } - - return true; -} - -static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data) -{ - auto get_aliases = [](const std::map& data) { - std::set old_aliases; - for (auto item : data) { - const std::string& name = item.first; - size_t pos = name.find("@"); - old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1)); - } - return old_aliases; - }; - - std::set old_aliases = get_aliases(old_data); - std::set new_aliases = get_aliases(new_data); - std::set diff; - std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin())); - - return diff; -} - -static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data) -{ - std::set diff = get_new_added_presets(old_data, new_data); - if (diff.empty()) - return std::string(); - return *diff.begin(); -} - -bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) -{ - wxString header, caption = _L("Configuration is edited in ConfigWizard"); - const auto enabled_vendors = appconfig_new.vendors(); - const auto enabled_vendors_old = app_config->vendors(); - - bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); - PrinterTechnology preferred_pt = ptAny; - auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { - const auto config = enabled_vendors.find(bundle_name); - PrinterTechnology pt = ptAny; - if (config != enabled_vendors.end()) { - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0) { - pt = model.technology; - const auto config_old = enabled_vendors_old.find(bundle_name); - if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { - // if preferred printer model has SLA printer technology it's important to check the model for multi-part state - if (pt == ptSLA && suppress_sla_printer) - continue; - return pt; - } - - if (const auto model_it_old = config_old->second.find(model.id); - model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { - // if preferred printer model has SLA printer technology it's important to check the model for multi-part state - if (pt == ptSLA && suppress_sla_printer) - continue; - return pt; - } - } - } - } - return pt; - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); - preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) - continue; - else if (preferred_pt == ptAny) - preferred_pt = pt; - if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) - break; - } - } - - if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) - return false; - - bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); - if (check_unsaved_preset_changes) - header = _L("All user presets will be deleted."); - int act_btns = ActionButtons::KEEP; - if (!check_unsaved_preset_changes) - act_btns |= ActionButtons::SAVE; - - // Install bundles from resources if needed: - std::vector install_bundles; - for (const auto &pair : bundles) { - if (! pair.second.is_in_resources) { continue; } - - if (pair.second.is_prusa_bundle) { - // Always install Prusa bundle, because it has a lot of filaments/materials - // likely to be referenced by other profiles. - install_bundles.emplace_back(pair.first); - continue; - } - - const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } - - if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { - // Templates vendor needs to be installed - install_bundles.emplace_back(pair.first); - continue; - } - - size_t size_sum = 0; - for (const auto &model : vendor->second) { size_sum += model.second.size(); } - - if (size_sum > 0) { - // This vendor needs to be installed - install_bundles.emplace_back(pair.first); - } - } - if (!check_unsaved_preset_changes) - if ((check_unsaved_preset_changes = install_bundles.size() > 0)) - header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size()); - -#ifdef __linux__ - // Desktop integration on Linux - BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux(); - if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux()) - DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux()); -#endif - - // Decide whether to create snapshot based on run_reason and the reset profile checkbox - bool snapshot = true; - Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; - switch (run_reason) { - case ConfigWizard::RR_DATA_EMPTY: - snapshot = false; - break; - case ConfigWizard::RR_DATA_LEGACY: - snapshot = true; - break; - case ConfigWizard::RR_DATA_INCOMPAT: - // In this case snapshot has already been taken by - // PresetUpdater with the appropriate reason - snapshot = false; - break; - case ConfigWizard::RR_USER: - snapshot = page_welcome->reset_user_profile(); - snapshot_reason = Snapshot::SNAPSHOT_USER; - break; - } - - if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?"))) - return false; - - if (check_unsaved_preset_changes && - !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - - if (install_bundles.size() > 0) { - // Install bundles from resources. - // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) - return false; - } else { - BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; - } - - if (page_welcome->reset_user_profile()) { - BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; - preset_bundle->reset(true); - } - - std::string preferred_model; - std::string preferred_variant; - auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { - const auto config = enabled_vendors.find(bundle_name); - if (config == enabled_vendors.end()) - return std::string(); - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0 && - preferred_pt == model.technology) { - variant = *model_it->second.begin(); - const auto config_old = enabled_vendors_old.find(bundle_name); - if (config_old == enabled_vendors_old.end()) - return model.id; - const auto model_it_old = config_old->second.find(model.id); - if (model_it_old == config_old->second.end()) - return model.id; - else if (model_it_old->second != model_it->second) { - for (const auto& var : model_it->second) - if (model_it_old->second.find(var) == model_it_old->second.end()) { - variant = var; - return model.id; - } - } - } - } - if (!variant.empty()) - variant.clear(); - return std::string(); - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant); - preferred_model.empty()) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant); - !preferred_model.empty()) - break; - } - } - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !preferred_model.empty())) { - header = _L("A new Printer was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { - header = _L("Some Printers were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - - std::string first_added_filament, first_added_sla_material; - auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { - if (appconfig_new.has_section(section_name)) { - // get first of new added preset names - const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map(); - first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); - } - }; - get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); - get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { - header = !first_added_filament.empty() ? - _L("A new filament was installed and it will be activated.") : - _L("A new SLA material was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else { - auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { - return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name); - }; - bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); - bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); - if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { - header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - } - - // apply materials in app_config - for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) - app_config->set_section(section_name, appconfig_new.get_section(section_name)); - - app_config->set_vendors(appconfig_new); - - app_config->set("notify_release", page_update->version_check ? "all" : "none"); - app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); - -#ifdef _WIN32 - app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); - app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); -// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); - - if (wxGetApp().is_editor()) { - if (page_files_association->associate_3mf()) - wxGetApp().associate_3mf_files(); - if (page_files_association->associate_stl()) - wxGetApp().associate_stl_files(); - } -// else { -// if (page_files_association->associate_gcode()) -// wxGetApp().associate_gcode_files(); -// } -#endif // _WIN32 - - page_mode->serialize_mode(app_config); - - if (check_unsaved_preset_changes) - preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, - {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); - - if (!only_sla_mode && page_custom->custom_wanted() && page_custom->is_valid_profile_name()) { - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes && - !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes)) - return false; - - page_firmware->apply_custom_config(*custom_config); - page_bed->apply_custom_config(*custom_config); - page_bvolume->apply_custom_config(*custom_config); - page_diams->apply_custom_config(*custom_config); - page_temps->apply_custom_config(*custom_config); - - copy_bed_model_and_texture_if_needed(*custom_config); - - const std::string profile_name = page_custom->profile_name(); - preset_bundle->load_config_from_wizard(profile_name, *custom_config); - } - - // Update the selections from the compatibilty. - preset_bundle->export_selections(*app_config); - - return true; -} -void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) -{ - const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; - - auto update = [this, add](const std::string& s, const std::string& key) { - assert(! s.empty()); - if (add) - appconfig_new.set(s, key, "1"); - else - appconfig_new.erase(s, key); - }; - - // add or delete presets had a same alias - auto it = aliases.find(alias_key); - if (it != aliases.end()) - for (const std::string& name : it->second) - update(section, name); -} - -bool ConfigWizard::priv::check_fff_selected() -{ - bool ret = page_fff->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.first) // FFF page - ret |= printer.second.first->any_selected(); - return ret; -} - -bool ConfigWizard::priv::check_sla_selected() -{ - bool ret = page_msla->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.second) // SLA page - ret |= printer.second.second->any_selected(); - return ret; -} - - -// Public - -ConfigWizard::ConfigWizard(wxWindow *parent) - : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) - , p(new priv(this)) -{ - this->SetFont(wxGetApp().normal_font()); - - p->load_vendors(); - p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", - })); - - p->index = new ConfigWizardIndex(this); - - auto *vsizer = new wxBoxSizer(wxVERTICAL); - auto *topsizer = new wxBoxSizer(wxHORIZONTAL); - auto* hline = new StaticLine(this); - p->btnsizer = new wxBoxSizer(wxHORIZONTAL); - - // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. - // Later, we compare that to the size of the current screen and set minimum width based on that (see below). - p->hscroll = new wxScrolledWindow(this); - p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL); - p->hscroll->SetSizer(p->hscroll_sizer); - - topsizer->Add(p->index, 0, wxEXPAND); - topsizer->AddSpacer(INDEX_MARGIN); - topsizer->Add(p->hscroll, 1, wxEXPAND); - - p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers")); - p->btnsizer->Add(p->btn_sel_all); - - p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back")); - p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >")); - p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish")); - p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac - p->btnsizer->AddStretchSpacer(); - p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); - - wxGetApp().UpdateDarkUI(p->btn_sel_all); - wxGetApp().UpdateDarkUI(p->btn_prev); - wxGetApp().UpdateDarkUI(p->btn_next); - wxGetApp().UpdateDarkUI(p->btn_finish); - wxGetApp().UpdateDarkUI(p->btn_cancel); - - const auto prusa_it = p->bundles.find("PrusaResearch"); - wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); - const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; - - p->add_page(p->page_welcome = new PageWelcome(this)); - - - p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF); - p->only_sla_mode = !p->page_fff->has_printers; - if (!p->only_sla_mode) { - p->add_page(p->page_fff); - p->page_fff->is_primary_printer_page = true; - } - - - p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA); - p->add_page(p->page_msla); - if (p->only_sla_mode) { - p->page_msla->is_primary_printer_page = true; - } - - if (!p->only_sla_mode) { - // Pages for 3rd party vendors - p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors - p->add_page(p->page_vendors = new PageVendors(this)); - p->add_page(p->page_custom = new PageCustom(this)); - p->custom_printer_selected = p->page_custom->custom_wanted(); - } - - p->any_sla_selected = p->check_sla_selected(); - p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected(); - - p->update_materials(T_ANY); - if (!p->only_sla_mode) - p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, - _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") )); - - p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, - _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); - - - p->add_page(p->page_update = new PageUpdate(this)); - p->add_page(p->page_downloader = new PageDownloader(this)); - p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); -#ifdef _WIN32 - p->add_page(p->page_files_association = new PageFilesAssociation(this)); -#endif // _WIN32 - p->add_page(p->page_mode = new PageMode(this)); - p->add_page(p->page_firmware = new PageFirmware(this)); - p->add_page(p->page_bed = new PageBedShape(this)); - p->add_page(p->page_bvolume = new PageBuildVolume(this)); - p->add_page(p->page_diams = new PageDiameters(this)); - p->add_page(p->page_temps = new PageTemperatures(this)); - - p->load_pages(); - p->index->go_to(size_t{0}); - - vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); - vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); - vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); - - SetSizer(vsizer); - SetSizerAndFit(vsizer); - - // We can now enable scrolling on hscroll - p->hscroll->SetScrollRate(30, 30); - - on_window_geometry(this, [this]() { - p->init_dialog_size(); - }); - - p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); - - p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - // check, that there is selected at least one filament/material - ConfigWizardPage* active_page = this->p->index->active_page(); - if (// Leaving the filaments or SLA materials page and - (active_page == p->page_filaments || active_page == p->page_sla_materials) && - // some Printer models had no filament or SLA material selected. - ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) - // In that case don't leave the page and the function above queried the user whether to install default materials. - return; - this->p->index->go_next(); - }); - - p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - if (p->on_bnt_finish()) - this->EndModal(wxID_OK); - }); - - p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { - p->any_sla_selected = true; - p->load_pages(); - p->page_fff->select_all(true, false); - p->page_msla->select_all(true, false); - p->index->go_to(p->page_mode); - }); - - p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { - const bool is_last = p->index->active_is_last(); - p->btn_next->Show(! is_last); - if (is_last) - p->btn_finish->SetFocus(); - - Layout(); - }); - - if (wxLinux_gtk3) - this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { - ConfigWizardPage* active_page = p->index->active_page(); - if (!active_page) - return; - for (auto page : p->all_pages) - if (page != active_page) - page->Hide(); - // update best size for the dialog after hiding of the non-active pages - vsizer->SetSizeHints(this); - // set initial dialog size - p->init_dialog_size(); - }); -} - -ConfigWizard::~ConfigWizard() {} - -bool ConfigWizard::run(RunReason reason, StartPage start_page) -{ - BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; - - GUI_App &app = wxGetApp(); - - p->set_run_reason(reason); - p->set_start_page(start_page); - - if (ShowModal() == wxID_OK) { - bool apply_keeped_changes = false; - if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) - return false; - - if (apply_keeped_changes) - app.apply_keeped_preset_modifications(); - - app.app_config->set_legacy_datadir(false); - app.update_mode(); - app.obj_manipul()->update_ui_from_settings(); - BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; - return true; - } else { - BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; - return false; - } -} - -const wxString& ConfigWizard::name(const bool from_menu/* = false*/) -{ - // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. - // Note: Don't call _() macro here. - // This function just return the current name according to the OS. - // Translation is implemented inside GUI_App::add_config_menu() -#if __APPLE__ - static const wxString config_wizard_name = L("Configuration Assistant"); - static const wxString config_wizard_name_menu = L("Configuration &Assistant"); -#else - static const wxString config_wizard_name = L("Configuration Wizard"); - static const wxString config_wizard_name_menu = L("Configuration &Wizard"); -#endif - return from_menu ? config_wizard_name_menu : config_wizard_name; -} - -void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) -{ - p->index->msw_rescale(); - - const int em = em_unit(); - - msw_buttons_rescale(this, em, { wxID_APPLY, - wxID_CANCEL, - p->btn_sel_all->GetId(), - p->btn_next->GetId(), - p->btn_prev->GetId() }); - - for (auto printer_picker: p->page_fff->printer_pickers) - msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); - - p->init_dialog_size(); - - Refresh(); -} - -void ConfigWizard::on_sys_color_changed() -{ - wxGetApp().UpdateDlgDarkUI(this); - Refresh(); -} - -} -} +// FIXME: extract absolute units -> em + +#include "ConfigWizard_private.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#include +#endif // WIN32 + +#ifdef _MSW_DARK_MODE +#include +#endif // _MSW_DARK_MODE + +#include "libslic3r/Platform.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Model.hpp" +#include "libslic3r/Color.hpp" +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "GUI_Utils.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "Field.hpp" +#include "DesktopIntegrationDialog.hpp" +#include "slic3r/Config/Snapshot.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" +#include "format.hpp" +#include "MsgDialog.hpp" +#include "UnsavedChangesDialog.hpp" +#include "slic3r/Utils/AppUpdater.hpp" + +#if defined(__linux__) && defined(__WXGTK3__) +#define wxLinux_gtk3 true +#else +#define wxLinux_gtk3 false +#endif //defined(__linux__) && defined(__WXGTK3__) + +namespace Slic3r { +namespace GUI { + + +using Config::Snapshot; +using Config::SnapshotDB; + + +// Configuration data structures extensions needed for the wizard + +bool Bundle::load(fs::path source_path, BundleLocation location, bool ais_prusa_bundle) +{ + this->preset_bundle = std::make_unique(); + this->location = location; + this->is_prusa_bundle = ais_prusa_bundle; + + std::string path_string = source_path.string(); + // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. + auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( + path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); + UNUSED(config_substitutions); + // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. + assert(config_substitutions.empty()); + auto first_vendor = preset_bundle->vendors.begin(); + if (first_vendor == preset_bundle->vendors.end()) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; + return false; + } + if (presets_loaded == 0) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string; + return false; + } + + BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded; + this->vendor_profile = &first_vendor->second; + return true; +} + +Bundle::Bundle(Bundle &&other) + : preset_bundle(std::move(other.preset_bundle)) + , vendor_profile(other.vendor_profile) + , location(other.location) + , is_prusa_bundle(other.is_prusa_bundle) +{ + other.vendor_profile = nullptr; +} + +BundleMap BundleMap::load() +{ + BundleMap res; + + const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); + const auto archive_dir = (boost::filesystem::path(Slic3r::data_dir()) / "cache" / "vendor").make_preferred(); + const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + // Load Prusa bundle from the datadir/vendor directory or from datadir/cache/vendor (archive) or from resources/profiles. + auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + BundleLocation prusa_bundle_loc = BundleLocation::IN_VENDOR; + if (! boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (archive_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_loc = BundleLocation::IN_ARCHIVE; + } + if (!boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_loc = BundleLocation::IN_RESOURCES; + } + { + Bundle prusa_bundle; + if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_loc, true)) + res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); + } + + // Load the other bundles in the datadir/vendor directory + // and then additionally from datadir/cache/vendor (archive) and resources/profiles. + // Should we concider case where archive has older profiles than resources (shouldnt happen)? + typedef std::pair DirData; + std::vector dir_list { {vendor_dir, BundleLocation::IN_VENDOR}, {archive_dir, BundleLocation::IN_ARCHIVE}, {rsrc_vendor_dir, BundleLocation::IN_RESOURCES} }; + for ( auto dir : dir_list) { + for (const auto &dir_entry : boost::filesystem::directory_iterator(dir.first)) { + if (Slic3r::is_ini_file(dir_entry)) { + std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part + + // Don't load this bundle if we've already loaded it. + if (res.find(id) != res.end()) { continue; } + + Bundle bundle; + if (bundle.load(dir_entry.path(), dir.second)) + res.emplace(std::move(id), std::move(bundle)); + } + } + } + + return res; +} + +Bundle& BundleMap::prusa_bundle() +{ + auto it = find(PresetBundle::PRUSA_BUNDLE); + if (it == end()) { + throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); + } + + return it->second; +} + +const Bundle& BundleMap::prusa_bundle() const +{ + return const_cast(this)->prusa_bundle(); +} + + +// Printer model picker GUI control + +struct PrinterPickerEvent : public wxEvent +{ + std::string vendor_id; + std::string model_id; + std::string variant_name; + bool enable; + + PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) + : wxEvent(winid, eventType) + , vendor_id(std::move(vendor_id)) + , model_id(std::move(model_id)) + , variant_name(std::move(variant_name)) + , enable(enable) + {} + + virtual wxEvent *Clone() const + { + return new PrinterPickerEvent(*this); + } +}; + +wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); + +const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) + : wxPanel(parent) + , vendor_id(vendor.id) + , width(0) +{ + wxGetApp().UpdateDarkUI(this); + const auto &models = vendor.models; + + auto *sizer = new wxBoxSizer(wxVERTICAL); + + const auto font_title = GetFont().MakeBold().Scaled(1.3f); + const auto font_name = GetFont().MakeBold(); + const auto font_alt_nozzle = GetFont().Scaled(0.9f); + + // wxGrid appends widgets by rows, but we need to construct them in columns. + // These vectors are used to hold the elements so that they can be appended in the right order. + std::vector titles; + std::vector bitmaps; + std::vector variants_panels; + + int max_row_width = 0; + int current_row_width = 0; + + bool is_variants = false; + + for (const auto &model : models) { + if (! filter(model)) { continue; } + + wxBitmap bitmap; + int bitmap_width = 0; + auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { + if (wxFileExists(bitmap_file)) { + bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); + bitmap_width = bitmap.GetWidth(); + return true; + } + return false; + }; + if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { + if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") + % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") + % vendor.id + % model.id; + load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); + } + } + auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + title->SetFont(font_name); + const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); + title->Wrap(wrap_width); + + current_row_width += wrap_width; + if (titles.size() % max_cols == max_cols - 1) { + max_row_width = std::max(max_row_width, current_row_width); + current_row_width = 0; + } + + titles.push_back(title); + + auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); + bitmaps.push_back(bitmap_widget); + + auto *variants_panel = new wxPanel(this); + wxGetApp().UpdateDarkUI(variants_panel); + auto *variants_sizer = new wxBoxSizer(wxVERTICAL); + variants_panel->SetSizer(variants_sizer); + const auto model_id = model.id; + + for (size_t i = 0; i < model.variants.size(); i++) { + const auto &variant = model.variants[i]; + + const auto label = model.technology == ptFFF + ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str()) + : from_u8(model.name); + + if (i == 1) { + auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:")); + alt_label->SetFont(font_alt_nozzle); + variants_sizer->Add(alt_label, 0, wxBOTTOM, 3); + is_variants = true; + } + + auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); + i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); + + const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); + cbox->SetValue(enabled); + + variants_sizer->Add(cbox, 0, wxBOTTOM, 3); + + cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) { + on_checkbox(cbox, event.IsChecked()); + }); + } + + variants_panels.push_back(variants_panel); + } + + width = std::max(max_row_width, current_row_width); + + const size_t cols = std::min(max_cols, titles.size()); + + auto *printer_grid = new wxFlexGridSizer(cols, 0, 20); + printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL); + + if (titles.size() > 0) { + const size_t odd_items = titles.size() % cols; + + for (size_t i = 0; i < titles.size() - odd_items; i += cols) { + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); } + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); } + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); } + + // Add separator space to multiliners + if (titles.size() > cols) { + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); } + } + } + if (odd_items > 0) { + const size_t rem = titles.size() - odd_items; + + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); } + for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); } + for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); } + } + } + + auto *title_sizer = new wxBoxSizer(wxHORIZONTAL); + if (! title.IsEmpty()) { + auto *title_widget = new wxStaticText(this, wxID_ANY, title); + title_widget->SetFont(font_title); + title_sizer->Add(title_widget); + } + title_sizer->AddStretchSpacer(); + + if (titles.size() > 1 || is_variants) { + // It only makes sense to add the All / None buttons if there's multiple printers + // All Standard button is added when there are more variants for at least one printer + auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard")); + auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); + auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); + if (is_variants) + sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); }); + sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); }); + sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); + if (is_variants) + title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING); + title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING); + title_sizer->Add(sel_none); + + wxGetApp().UpdateDarkUI(sel_all_std); + wxGetApp().UpdateDarkUI(sel_all); + wxGetApp().UpdateDarkUI(sel_none); + + // fill button indexes used later for buttons rescaling + if (is_variants) + m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() }; + else { + sel_all_std->Destroy(); + m_button_indexes = { sel_all->GetId(), sel_none->GetId() }; + } + } + + sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING); + sizer->Add(printer_grid); + + SetSizer(sizer); +} + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) + : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) +{} + +void PrinterPicker::select_all(bool select, bool alternates) +{ + for (const auto &cb : cboxes) { + if (cb->GetValue() != select) { + cb->SetValue(select); + on_checkbox(cb, select); + } + } + + if (! select) { alternates = false; } + + for (const auto &cb : cboxes_alt) { + if (cb->GetValue() != alternates) { + cb->SetValue(alternates); + on_checkbox(cb, alternates); + } + } +} + +void PrinterPicker::select_one(size_t i, bool select) +{ + if (i < cboxes.size() && cboxes[i]->GetValue() != select) { + cboxes[i]->SetValue(select); + on_checkbox(cboxes[i], select); + } +} + +bool PrinterPicker::any_selected() const +{ + for (const auto &cb : cboxes) { + if (cb->GetValue()) { return true; } + } + + for (const auto &cb : cboxes_alt) { + if (cb->GetValue()) { return true; } + } + + return false; +} + +std::set PrinterPicker::get_selected_models() const +{ + std::set ret_set; + + for (const auto& cb : cboxes) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + for (const auto& cb : cboxes_alt) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + return ret_set; +} + +void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) +{ + PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); + AddPendingEvent(evt); +} + + +// Wizard page base + +ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent) + : wxPanel(parent->p->hscroll) + , parent(parent) + , shortname(std::move(shortname)) + , indent(indent) +{ + wxGetApp().UpdateDarkUI(this); + + auto *sizer = new wxBoxSizer(wxVERTICAL); + + auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + const auto font = GetFont().MakeBold().Scaled(1.5); + text->SetFont(font); + sizer->Add(text, 0, wxALIGN_LEFT, 0); + sizer->AddSpacer(10); + + content = new wxBoxSizer(wxVERTICAL); + sizer->Add(content, 1, wxEXPAND); + + SetSizer(sizer); + + // There is strange layout on Linux with GTK3, + // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861 + // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages + if (!wxLinux_gtk3) + this->Hide(); + + Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { + this->Layout(); + event.Skip(); + }); +} + +ConfigWizardPage::~ConfigWizardPage() {} + +wxStaticText* ConfigWizardPage::append_text(wxString text) +{ + auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + widget->Wrap(WRAP_WIDTH); + widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); + append(widget); + return widget; +} + +void ConfigWizardPage::append_spacer(int space) +{ + // FIXME: scaling + content->AddSpacer(space); +} + +// Wizard pages + +PageWelcome::PageWelcome(ConfigWizard *parent) + : ConfigWizardPage(parent, from_u8((boost::format( +#ifdef __APPLE__ + _utf8(L("Welcome to the %s Configuration Assistant")) +#else + _utf8(L("Welcome to the %s Configuration Wizard")) +#endif + ) % SLIC3R_APP_NAME).str()), _L("Welcome")) + , welcome_text(append_text(from_u8((boost::format( + _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print."))) + % SLIC3R_APP_NAME + % _utf8(ConfigWizard::name())).str()) + )) + , cbox_reset(append( + new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) + )) + , cbox_integrate(append( + new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) + )) +{ + welcome_text->Hide(); + cbox_reset->Hide(); + cbox_integrate->Hide(); +} + +void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) +{ + const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; + welcome_text->Show(data_empty); + cbox_reset->Show(!data_empty); +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + if (!DesktopIntegrationDialog::is_integrated()) + cbox_integrate->Show(true); + else + cbox_integrate->Hide(); +#else + cbox_integrate->Hide(); +#endif +} + + +PagePrinters::PagePrinters(ConfigWizard *parent, + wxString title, + wxString shortname, + const VendorProfile &vendor, + unsigned indent, + Technology technology) + : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) + , technology(technology) + , install(false) // only used for 3rd party vendors +{ + enum { + COL_SIZE = 200, + }; + + AppConfig *appconfig = &this->wizard_p()->appconfig_new; + + const auto families = vendor.families(); + for (const auto &family : families) { + const auto filter = [&](const VendorProfile::PrinterModel &model) { + return ((model.technology == ptFFF && technology & T_FFF) + || (model.technology == ptSLA && technology & T_SLA)) + && model.family == family; + }; + + if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) { + continue; + } + + const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str()); + auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); + + picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { + appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + wizard_p()->on_printer_pick(this, evt); + }); + + append(new StaticLine(this)); + + append(picker); + printer_pickers.push_back(picker); + has_printers = true; + } + +} + +void PagePrinters::select_all(bool select, bool alternates) +{ + for (auto picker : printer_pickers) { + picker->select_all(select, alternates); + } +} + +int PagePrinters::get_width() const +{ + return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, + [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); +} + +bool PagePrinters::any_selected() const +{ + for (const auto *picker : printer_pickers) { + if (picker->any_selected()) { return true; } + } + + return false; +} + +std::set PagePrinters::get_selected_models() +{ + std::set ret_set; + + for (const auto *picker : printer_pickers) + { + std::set tmp_models = picker->get_selected_models(); + ret_set.insert(tmp_models.begin(), tmp_models.end()); + } + + return ret_set; +} + +void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) +{ + if (is_primary_printer_page + && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY) + && printer_pickers.size() > 0 + && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) { + printer_pickers[0]->select_one(0, true); + } +} + + +const std::string PageMaterials::EMPTY; +const std::string PageMaterials::TEMPLATES = "templates"; + +PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) + : ConfigWizardPage(parent, std::move(title), std::move(shortname)) + , materials(materials) + , list_printer(new StringList(this, wxLB_MULTIPLE)) + , list_type(new StringList(this)) + , list_vendor(new StringList(this)) + , list_profile(new PresetList(this)) +{ + append_spacer(VERTICAL_SPACING); + + const int em = parent->em_unit(); + const int list_h = 30*em; + + + list_printer->SetMinSize(wxSize(23*em, list_h)); + list_type->SetMinSize(wxSize(13*em, list_h)); + list_vendor->SetMinSize(wxSize(13*em, list_h)); + list_profile->SetMinSize(wxSize(23*em, list_h)); + + + + grid = new wxFlexGridSizer(4, em/2, em); + grid->AddGrowableCol(3, 1); + grid->AddGrowableRow(1, 1); + + grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:"))); + grid->Add(new wxStaticText(this, wxID_ANY, list1name)); + grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:"))); + grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:"))); + + grid->Add(list_printer, 0, wxEXPAND); + grid->Add(list_type, 0, wxEXPAND); + grid->Add(list_vendor, 0, wxEXPAND); + grid->Add(list_profile, 1, wxEXPAND); + + auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); + auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); + auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); + btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); + btn_sizer->Add(sel_none); + + wxGetApp().UpdateDarkUI(list_printer); + wxGetApp().UpdateDarkUI(list_type); + wxGetApp().UpdateDarkUI(list_vendor); + wxGetApp().UpdateDarkUI(sel_all); + wxGetApp().UpdateDarkUI(sel_none); + + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(btn_sizer, 0, wxALIGN_RIGHT); + + append(grid, 1, wxEXPAND); + + append_spacer(VERTICAL_SPACING); + + html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, + wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); + append(html_window, 0, wxEXPAND); + + list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt()); + }); + list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection()); + }); + list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection()); + }); + + list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); + list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); + + sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); + sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); + /* + Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); + + list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); + list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); + list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); + */ + reload_presets(); + set_compatible_printers_html_window(std::vector(), false); +} +void PageMaterials::on_paint() +{ +} +void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) +{ + const wxClientDC dc(list_profile); + const wxPoint pos = evt.GetLogicalPosition(dc); + int item = list_profile->HitTest(pos); + on_material_hovered(item); +} +void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) +{} +void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) +{ + on_material_hovered(-1); +} +void PageMaterials::reload_presets() +{ + clear(); + + list_printer->append(_L("(All)"), &EMPTY); + + const AppConfig* app_config = wxGetApp().app_config; + if (materials->technology == T_FFF && app_config->get("no_templates") == "0") + list_printer->append(_L("(Templates)"), &TEMPLATES); + + //list_printer->SetLabelMarkup("bald"); + for (const Preset* printer : materials->printers) { + list_printer->append(printer->name, &printer->name); + } + sort_list_data(list_printer, true, false); + if (list_printer->GetCount() > 0) { + list_printer->SetSelection(0); + sel_printers_prev.Clear(); + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; + update_lists(0, 0, 0); + } + + presets_loaded = true; +} + +void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) +{ + const auto bgr_clr = +#if defined(__APPLE__) + html_window->GetParent()->GetBackgroundColour(); +#else +#if defined(_WIN32) + wxGetApp().get_window_default_clr(); +#else + wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); +#endif +#endif + const auto text_clr = wxGetApp().get_label_clr_default(); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + wxString text; + if (materials->technology == T_FFF && template_shown) { + text = format_wxstr(_L("%1% visible for (\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); + } else { + wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); + + if (all_printers) { + wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "
" + "
" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line + ); + } + else { + wxString second_line; + if (!printer_names.empty()) + second_line = (materials->technology == T_FFF ? + _L("Only the following installed printers are compatible with the selected filaments") : + _L("Only the following installed printers are compatible with the selected SLA materials")) + ":"; + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line); + for (size_t i = 0; i < printer_names.size(); ++i) + { + text += wxString::Format("", boost::nowide::widen(printer_names[i])); + if (i % 3 == 2) { + text += wxString::Format( + "" + ""); + } + } + text += wxString::Format( + "" + "
%s
" + "
" + "
" + "" + "" + ); + } + } + + + wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); + const int fs = font.GetPointSize(); + int size[] = { fs,fs,fs,fs,fs,fs,fs }; + html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html_window->SetPage(text); +} + +void PageMaterials::clear_compatible_printers_label() +{ + set_compatible_printers_html_window(std::vector(), false); +} + +void PageMaterials::on_material_hovered(int sel_material) +{ + +} + +void PageMaterials::on_material_highlighted(int sel_material) +{ + if (sel_material == last_hovered_item) + return; + if (sel_material == -1) { + clear_compatible_printers_label(); + return; + } + last_hovered_item = sel_material; + std::vector tabs; + tabs.push_back(std::string()); + tabs.push_back(std::string()); + tabs.push_back(std::string()); + //selected material string + std::string material_name = list_profile->get_data(sel_material); + // get material preset + const std::vector matching_materials = materials->get_presets_by_alias(material_name); + if (matching_materials.empty()) + { + clear_compatible_printers_label(); + return; + } + //find matching printers + std::vector names; + for (const Preset* printer : materials->printers) { + for (const Preset* material : matching_materials) { + if (material->vendor && material->vendor->templates_profile) + continue; + if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { + names.push_back(printer->name); + break; + } + } + } + set_compatible_printers_html_window(names, names.size() == materials->printers.size()); +} + +void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + wxArrayInt sel_printers; + int sel_printers_count = list_printer->GetSelections(sel_printers); + + bool templates_available = list_printer->size() > 1 && list_printer->get_data(1) == TEMPLATES; + + // Does our wxWidgets version support operator== for wxArrayInt ? + // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 +#if wxCHECK_VERSION(3, 1, 1) + if (sel_printers != sel_printers_prev) { +#else + auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { + if (arr_first.GetCount() != arr_second.GetCount()) + return false; + for (size_t i = 0; i < arr_first.GetCount(); i++) + if (arr_first[i] != arr_second[i]) + return false; + return true; + }; + if (!are_equal(sel_printers, sel_printers_prev)) { +#endif + template_shown = false; + // Refresh type list + list_type->Clear(); + list_type->append(_L("(All)"), &EMPTY); + if (sel_printers_count > 1) { + // If all is selected with other printers + // unselect "all" or all printers depending on last value + // same with "templates" + if (sel_printers[0] == 0 && sel_printers_count > 1) { + if (last_selected_printer == 0) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + } else { + list_printer->SetSelection(0, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + if (materials->technology == T_FFF && templates_available && (sel_printers[0] == 1 || sel_printers[1] == 1) && sel_printers_count > 1) { + if (last_selected_printer == 1) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + } + else if (last_selected_printer != 0) { + list_printer->SetSelection(1, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + } + if (sel_printers_count > 0 && sel_printers[0] != 0 && ((materials->technology == T_FFF && templates_available && sel_printers[0] != 1) || materials->technology != T_FFF || !templates_available)) { + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, printer_name, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + } + else if (sel_printers_count > 0 && last_selected_printer == 0) { + //clear selection except "ALL" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + sel_printers_count = list_printer->GetSelections(sel_printers); + + materials->filter_presets(nullptr, EMPTY, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + else if (materials->technology == T_FFF && templates_available && sel_printers_count > 0 && last_selected_printer == 1) { + //clear selection except "TEMPLATES" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + sel_printers_count = list_printer->GetSelections(sel_printers); + template_shown = true; + materials->filter_presets(nullptr, TEMPLATES, EMPTY, EMPTY, + [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + sort_list_data(list_type, true, true); + + sel_printers_prev = sel_printers; + sel_type = 0; + sel_type_prev = wxNOT_FOUND; + list_type->SetSelection(sel_type); + list_profile->Clear(); + } + + if (sel_type != sel_type_prev) { + // Refresh vendor list + + // XXX: The vendor list is created with quadratic complexity here, + // but the number of vendors is going to be very small this shouldn't be a problem. + + list_vendor->Clear(); + list_vendor->append(_L("(All)"), &EMPTY); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + // find printer preset + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, printer_name, type, EMPTY, [this](const Preset* p) { + const std::string& vendor = this->materials->get_vendor(p); + if (list_vendor->find(vendor) == wxNOT_FOUND) { + list_vendor->append(vendor, &vendor); + } + }); + } + sort_list_data(list_vendor, true, false); + } + + sel_type_prev = sel_type; + sel_vendor = 0; + sel_vendor_prev = wxNOT_FOUND; + list_vendor->SetSelection(sel_vendor); + list_profile->Clear(); + } + + if (sel_vendor != sel_vendor_prev) { + // Refresh material list + + list_profile->Clear(); + clear_compatible_printers_label(); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + const std::string& vendor = list_vendor->get_data(sel_vendor); + // first printer preset + std::vector to_list; + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, printer_name, type, vendor, [this, &to_list](const Preset* p) { + const std::string& section = materials->appconfig_section(); + bool checked = wizard_p()->appconfig_new.has(section, p->name); + bool was_checked = false; + + int cur_i = list_profile->find(p->alias); + if (cur_i == wxNOT_FOUND) { + cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) || template_shown ? "" : " *"), &p->alias); + to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); + } + else { + was_checked = list_profile->IsChecked(cur_i); + to_list[cur_i].checked = checked || was_checked; + } + list_profile->Check(cur_i, checked || was_checked); + + /* Update preset selection in config. + * If one preset from aliases bundle is selected, + * than mark all presets with this aliases as selected + * */ + if (checked && !was_checked) + wizard_p()->update_presets_in_config(section, p->alias, true); + else if (!checked && was_checked) + wizard_p()->appconfig_new.set(section, p->name, "1"); + }); + } + sort_list_data(list_profile, to_list); + } + + sel_vendor_prev = sel_vendor; + } + wxGetApp().UpdateDarkUI(list_profile); +} + +void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) +{ +// get data from list +// sort data +// first should be +// then prusa profiles +// then the rest +// in alphabetical order + + std::vector> prusa_profiles; + std::vector> other_profiles; + bool add_TEMPLATES_item = false; + for (int i = 0 ; i < list->size(); ++i) { + const std::string& data = list->get_data(i); + if (data == EMPTY) // do not sort item + continue; + if (data == TEMPLATES) {// do not sort item + add_TEMPLATES_item = true; + continue; + } + if (!material_type_ordering && data.find("Prusa") != std::string::npos) + prusa_profiles.push_back(data); + else + other_profiles.push_back(data); + } + if(material_type_ordering) { + + const ConfigOptionDef* def = print_config_def.get("filament_type"); + std::vectorenum_values = def->enum_values; + size_t end_of_sorted = 0; + for (size_t vals = 0; vals < enum_values.size(); vals++) { + for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) + { + // find instead compare because PET vs PETG + if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { + //swap + if(profs != end_of_sorted) { + std::reference_wrapper aux = other_profiles[end_of_sorted]; + other_profiles[end_of_sorted] = other_profiles[profs]; + other_profiles[profs] = aux; + } + end_of_sorted++; + break; + } + } + } + } else { + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + } + + list->Clear(); + if (add_All_item) + list->append(_L("(All)"), &EMPTY); + if (materials->technology == T_FFF && add_TEMPLATES_item) + list->append(_L("(Templates)"), &TEMPLATES); + for (const auto& item : prusa_profiles) + list->append(item, &const_cast(item.get())); + for (const auto& item : other_profiles) + list->append(item, &const_cast(item.get())); + +} + +void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) +{ + // sort data + // then prusa profiles + // then the rest + // in alphabetical order + std::vector prusa_profiles; + std::vector other_profiles; + //for (int i = 0; i < data.size(); ++i) { + for (const auto& item : data) { + const std::string& name = item.name; + if (name.find("Prusa") != std::string::npos) + prusa_profiles.emplace_back(item); + else + other_profiles.emplace_back(item); + } + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { + return a.name.get() < b.name.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { + return a.name.get() < b.name.get(); + }); + list->Clear(); + for (size_t i = 0; i < prusa_profiles.size(); ++i) { + list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); + list->Check(i, prusa_profiles[i].checked); + } + for (size_t i = 0; i < other_profiles.size(); ++i) { + list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(other_profiles[i].name.get())); + list->Check(i + prusa_profiles.size(), other_profiles[i].checked); + } +} + +void PageMaterials::select_material(int i) +{ + const bool checked = list_profile->IsChecked(i); + + const std::string& alias_key = list_profile->get_data(i); + if (checked && template_shown && !notification_shown) { + notification_shown = true; + wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); + MessageDialog msg(this, message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_NO) { + list_profile->Check(i, false); + return; + } + } + wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); +} + +void PageMaterials::select_all(bool select) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + for (unsigned i = 0; i < list_profile->GetCount(); i++) { + const bool current = list_profile->IsChecked(i); + if (current != select) { + list_profile->Check(i, select); + select_material(i); + } + } +} + +void PageMaterials::clear() +{ + list_printer->Clear(); + list_type->Clear(); + list_vendor->Clear(); + list_profile->Clear(); + sel_printers_prev.Clear(); + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; + presets_loaded = false; +} + +void PageMaterials::on_activate() +{ + if (! presets_loaded) { + wizard_p()->update_materials(materials->technology); + reload_presets(); + } + first_paint = true; +} + + +const char *PageCustom::default_profile_name = "My Settings"; + +PageCustom::PageCustom(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer")) +{ + cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile")); + auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); + + wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL); + profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name }; + profile_name_editor->Enable(false); + + cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { + profile_name_editor->Enable(custom_wanted()); + wizard_p()->on_custom_setup(custom_wanted()); + }); + + append(cb_custom); + append(label); + append(profile_name_sizer); +} + +PageUpdate::PageUpdate(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates")) + , version_check(true) + , preset_update(true) +{ + const AppConfig *app_config = wxGetApp().app_config; + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates")); + box_slic3r->SetValue(app_config->get("notify_release") != "none"); + append(box_slic3r); + append_text(wxString::Format(_L( + "If enabled, %s checks for new application versions online. When a new version becomes available, " + "a notification is displayed at the next application startup (never during program usage). " + "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME)); + + append_spacer(VERTICAL_SPACING); + + auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically")); + box_presets->SetValue(app_config->get("preset_update") == "1"); + append(box_presets); + append_text(wxString::Format(_L( + "If enabled, %s downloads updates of built-in system presets in the background." + "These updates are downloaded into a separate temporary location." + "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME)); + const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings."); + auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); + label_bold->SetFont(boldfont); + label_bold->Wrap(WRAP_WIDTH); + append(label_bold); + append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")); + + box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); + box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); +} + +namespace DownloaderUtils +{ +#ifdef _WIN32 + + wxString get_downloads_path() + { + wxString ret; + PWSTR path = NULL; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path); + if (SUCCEEDED(hr)) { + ret = wxString(path); + } + CoTaskMemFree(path); + return ret; + } + +#elif __APPLE__ + wxString get_downloads_path() + { + // call objective-c implementation + return wxString::FromUTF8(get_downloads_path_mac()); + } +#else + wxString get_downloads_path() + { + wxString command = "xdg-user-dir DOWNLOAD"; + wxArrayString output; + GUI::desktop_execute_get_result(command, output); + if (output.GetCount() > 0) { + return output[0]; + } + return wxString(); + } + +#endif + +Worker::Worker(wxWindow* parent) +: wxBoxSizer(wxHORIZONTAL) +, m_parent(parent) +{ + m_input_path = new wxTextCtrl(m_parent, wxID_ANY); + set_path_name(get_app_config()->get("url_downloader_dest")); + + auto* path_label = new wxStaticText(m_parent, wxID_ANY, _L("Download path") + ":"); + + this->Add(path_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + this->Add(m_input_path, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + + auto* button_path = new wxButton(m_parent, wxID_ANY, _L("Browse")); + this->Add(button_path, 0, wxEXPAND | wxTOP | wxLEFT, 5); + button_path->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + boost::filesystem::path chosen_dest(boost::nowide::narrow(m_input_path->GetValue())); + + wxDirDialog dialog(m_parent, L("Choose folder:"), chosen_dest.string() ); + if (dialog.ShowModal() == wxID_OK) + this->m_input_path->SetValue(dialog.GetPath()); + }); + + for (wxSizerItem* item : this->GetChildren()) + if (item->IsWindow()) { + wxWindow* win = item->GetWindow(); + wxGetApp().UpdateDarkUI(win); + } +} + +void Worker::set_path_name(wxString path) +{ + if (path.empty()) + path = boost::nowide::widen(get_app_config()->get("url_downloader_dest")); + + if (path.empty()) { + // What should be default path? Each system has Downloads folder, that could be good one. + // Other would be program location folder - not so good: access rights, apple bin is inside bundle... + // default_path = boost::dll::program_location().parent_path().string(); + path = get_downloads_path(); + } + + m_input_path->SetValue(path); +} + +void Worker::set_path_name(const std::string& name) +{ + if (!m_input_path) + return; + + set_path_name(boost::nowide::widen(name)); +} + +} // DownLoader + +PageDownloader::PageDownloader(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Downloads from URL"), _L("Downloads")) +{ + const AppConfig* app_config = wxGetApp().app_config; + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + append_spacer(VERTICAL_SPACING); + + auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow build-in downloader")); + // TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run. + bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get("downloader_url_registered") == "1" : true); + box_allow_downloads->SetValue(box_allow_value); + append(box_allow_downloads); + + append_text(wxString::Format(_L( + "If enabled, %s registers to start on custom URL on www.printables.com." + " You will be able to use button with %s logo to open models in this %s." + " The model will be downloaded into folder you choose bellow." + ), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME)); + +#ifdef __linux__ + append_text(wxString::Format(_L( + "On Linux systems the process of registration also creates desktop integration files for this version of application." + ))); +#endif + + box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); }); + + downloader = new DownloaderUtils::Worker(this); + append(downloader); + downloader->allow(box_allow_value); +} + +bool PageDownloader::on_finish_downloader() const +{ + return downloader->on_finish(); +} + +bool DownloaderUtils::Worker::perform_register() +{ + //boost::filesystem::path chosen_dest/*(path_text_ctrl->GetValue());*/(boost::nowide::narrow(path_text_ctrl->GetValue())); + boost::filesystem::path chosen_dest (GUI::format(path_name())); + boost::system::error_code ec; + if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) { + std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not Exists.") ,chosen_dest.string()); + BOOST_LOG_TRIVIAL(error) << err_msg; + show_error(m_parent, err_msg); + return false; + } + BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string(); + wxGetApp().app_config->set("url_downloader_dest", chosen_dest.string()); +#ifdef _WIN32 + // Registry key creation for "prusaslicer://" URL + + boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location())); + // the path to binary needs to be correctly saved in string with respect to localized characters + wxString wbinary = wxString::FromUTF8(binary_path.string()); + std::string binary_string = (boost::format("%1%") % wbinary).str(); + BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string; + + //std::string key_string = "\"" + binary_string + "\" \"-u\" \"%1\""; + //std::string key_string = "\"" + binary_string + "\" \"%1\""; + std::string key_string = "\"" + binary_string + "\" \"--single-instance\" \"%1\""; + + wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\prusaslicer"); + wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); + if (!key_first.Exists()) { + key_first.Create(false); + } + key_first.SetValue("URL Protocol", ""); + + if (!key_full.Exists()) { + key_full.Create(false); + } + //key_full = "\"C:\\Program Files\\Prusa3D\\PrusaSlicer\\prusa-slicer-console.exe\" \"%1\""; + key_full = key_string; +#elif __APPLE__ + // Apple registers for custom url in info.plist thus it has to be already registered since build. + // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) +#else + // the performation should be called later during desktop integration + perform_registration_linux = true; +#endif + return true; +} + +void DownloaderUtils::Worker::deregister() +{ +#ifdef _WIN32 + std::string key_string = ""; + wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); + if (!key_full.Exists()) { + return; + } + key_full = key_string; +#elif __APPLE__ + // TODO +#else + BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration"; + DesktopIntegrationDialog::undo_downloader_registration(); + perform_registration_linux = false; +#endif +} + +bool DownloaderUtils::Worker::on_finish() { + AppConfig* app_config = wxGetApp().app_config; + bool ac_value = app_config->get("downloader_url_registered") == "1"; + BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked; + if (ac_value && downloader_checked) { + // already registered but we need to do it again + if (!perform_register()) + return false; + app_config->set("downloader_url_registered", "1"); + } else if (!ac_value && downloader_checked) { + // register + if (!perform_register()) + return false; + app_config->set("downloader_url_registered", "1"); + } else if (ac_value && !downloader_checked) { + // deregister, downloads are banned now + deregister(); + app_config->set("downloader_url_registered", "0"); + } /*else if (!ac_value && !downloader_checked) { + // not registered and we dont want to do it + // do not deregister as other instance might be registered + } */ + return true; +} + + +PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) + , full_pathnames(false) +{ + auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files")); + box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); + append(box_pathnames); + append_text(_L( + "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" + "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." + )); + + box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); +} + +#ifdef _WIN32 +PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) +{ + cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer")); + cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer")); +// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer")); + + append(cb_3mf); + append(cb_stl); +// append(cb_gcode); +} +#endif // _WIN32 + +PageMode::PageMode(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) +{ + append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" + "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " + "The other two offer progressively more sophisticated fine-tuning, " + "they are suitable for advanced and expert users, respectively.")); + + radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode")); + radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); + radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); + + std::string mode { "simple" }; + wxGetApp().app_config->get("", "view_mode", mode); + + if (mode == "advanced") { radio_advanced->SetValue(true); } + else if (mode == "expert") { radio_expert->SetValue(true); } + else { radio_simple->SetValue(true); } + + append(radio_simple); + append(radio_advanced); + append(radio_expert); + + append_text("\n" + _L("The size of the object can be specified in inches")); + check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); + check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); + append(check_inch); + + on_activate(); +} + +void PageMode::serialize_mode(AppConfig *app_config) const +{ + std::string mode = ""; + + if (radio_simple->GetValue()) { mode = "simple"; } + if (radio_advanced->GetValue()) { mode = "advanced"; } + if (radio_expert->GetValue()) { mode = "expert"; } + + app_config->set("view_mode", mode); + app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); +} + +PageVendors::PageVendors(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) +{ + const AppConfig &appconfig = this->wizard_p()->appconfig_new; + + append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":"); + + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + for (const auto &pair : wizard_p()->bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + + auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); + cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { + wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); + }); + + const auto &vendors = appconfig.vendors(); + const bool enabled = vendors.find(pair.first) != vendors.end(); + if (enabled) { + cbox->SetValue(true); + + auto pages = wizard_p()->pages_3rdparty.find(vendor->id); + wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); + + for (PagePrinters* page : { pages->second.first, pages->second.second }) + if (page) page->install = true; + } + + append(cbox); + } +} + +PageFirmware::PageFirmware(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1) + , gcode_opt(*print_config_def.get("gcode_flavor")) + , gcode_picker(nullptr) +{ + append_text(_L("Choose the type of firmware used by your printer.")); + append_text(_(gcode_opt.tooltip)); + + wxArrayString choices; + choices.Alloc(gcode_opt.enum_labels.size()); + for (const auto &label : gcode_opt.enum_labels) { + choices.Add(label); + } + + gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); + wxGetApp().UpdateDarkUI(gcode_picker); + const auto &enum_values = gcode_opt.enum_values; + auto needle = enum_values.cend(); + if (gcode_opt.default_value) { + needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); + } + if (needle != enum_values.cend()) { + gcode_picker->SetSelection(needle - enum_values.cbegin()); + } else { + gcode_picker->SetSelection(0); + } + + append(gcode_picker); +} + +void PageFirmware::apply_custom_config(DynamicPrintConfig &config) +{ + auto sel = gcode_picker->GetSelection(); + if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) { + auto *opt = new ConfigOptionEnum(static_cast(sel)); + config.set_key_value("gcode_flavor", opt); + } +} + +static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) +{ + e.Skip(); + wxString str = ctrl->GetValue(); + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + + double val = 0.0; + if (!str.ToDouble(&val)) { + if (val == 0.0) + val = def_value; + ctrl->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + // On Windows, this SetFocus creates an invisible marker. + //ctrl->SetFocus(); + } + else if (was_replaced) + ctrl->SetValue(double_to_string(val)); +} + +class DiamTextCtrl : public wxTextCtrl +{ +public: + DiamTextCtrl(wxWindow* parent) + { +#ifdef _WIN32 + long style = wxBORDER_SIMPLE; +#else + long style = 0; +#endif + Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style); + wxGetApp().UpdateDarkUI(this); + } + ~DiamTextCtrl() {} +}; + +PageBedShape::PageBedShape(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) + , shape_panel(new BedShapePanel(this)) +{ + append_text(_L("Set the shape of your printer's bed.")); + + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), + *wizard_p()->custom_config->option("bed_custom_texture"), + *wizard_p()->custom_config->option("bed_custom_model")); + + append(shape_panel); +} + +void PageBedShape::apply_custom_config(DynamicPrintConfig &config) +{ + const std::vector& points = shape_panel->get_shape(); + const std::string& custom_texture = shape_panel->get_custom_texture(); + const std::string& custom_model = shape_panel->get_custom_model(); + config.set_key_value("bed_shape", new ConfigOptionPoints(points)); + config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); + config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); +} + +PageBuildVolume::PageBuildVolume(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1) + , build_volume(new DiamTextCtrl(this)) +{ + append_text(_L("Set verctical size of your printer.")); + + wxString value = "200"; + build_volume->SetValue(value); + + build_volume->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { + double def_value = 200.0; + double max_value = 1200.0; + e.Skip(); + wxString str = build_volume->GetValue(); + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + + double val = 0.0; + if (!str.ToDouble(&val)) { + val = def_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (val < 0.0) { + val = def_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (val > max_value) { + val = max_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (was_replaced) + build_volume->SetValue(double_to_string(val)); + }, build_volume->GetId()); + + auto* sizer_volume = new wxFlexGridSizer(3, 5, 5); + auto* text_volume = new wxStaticText(this, wxID_ANY, _L("Max print height:")); + auto* unit_volume = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_volume->AddGrowableCol(0, 1); + sizer_volume->Add(text_volume, 0, wxALIGN_CENTRE_VERTICAL); + sizer_volume->Add(build_volume); + sizer_volume->Add(unit_volume, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_volume); +} + +void PageBuildVolume::apply_custom_config(DynamicPrintConfig& config) +{ + double val = 0.0; + build_volume->GetValue().ToDouble(&val); + auto* opt_volume = new ConfigOptionFloat(val); + config.set_key_value("max_print_height", opt_volume); +} + +PageDiameters::PageDiameters(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) + , diam_nozzle(new DiamTextCtrl(this)) + , diam_filam (new DiamTextCtrl(this)) +{ + auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); + wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + diam_nozzle->SetValue(value); + + auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); + value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + diam_filam->SetValue(value); + + diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); + diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); + + append_text(_L("Enter the diameter of your printer's hot end nozzle.")); + + auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); + auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:")); + auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_nozzle->AddGrowableCol(0, 1); + sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + sizer_nozzle->Add(diam_nozzle); + sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_nozzle); + + append_spacer(VERTICAL_SPACING); + + append_text(_L("Enter the diameter of your filament.")); + append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")); + + auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); + auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:")); + auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_filam->AddGrowableCol(0, 1); + sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); + sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL); + sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_filam); +} + +void PageDiameters::apply_custom_config(DynamicPrintConfig &config) +{ + double val = 0.0; + diam_nozzle->GetValue().ToDouble(&val); + auto *opt_nozzle = new ConfigOptionFloats(1, val); + config.set_key_value("nozzle_diameter", opt_nozzle); + + val = 0.0; + diam_filam->GetValue().ToDouble(&val); + auto * opt_filam = new ConfigOptionFloats(1, val); + config.set_key_value("filament_diameter", opt_filam); + + auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { + char buf[64]; // locales don't matter here (sprintf/atof) + sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); + config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); + }; + + set_extrusion_width("support_material_extrusion_width", 0.35); + set_extrusion_width("top_infill_extrusion_width", 0.40); + set_extrusion_width("first_layer_extrusion_width", 0.42); + + set_extrusion_width("extrusion_width", 0.45); + set_extrusion_width("perimeter_extrusion_width", 0.45); + set_extrusion_width("external_perimeter_extrusion_width", 0.45); + set_extrusion_width("infill_extrusion_width", 0.45); + set_extrusion_width("solid_infill_extrusion_width", 0.45); +} + +class SpinCtrlDouble: public wxSpinCtrlDouble +{ +public: + SpinCtrlDouble(wxWindow* parent) + { +#ifdef _WIN32 + long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; +#else + long style = wxSP_ARROW_KEYS; +#endif + Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style); +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this->GetText()); +#endif + this->Refresh(); + } + ~SpinCtrlDouble() {} +}; + +PageTemperatures::PageTemperatures(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1) + , spin_extr(new SpinCtrlDouble(this)) + , spin_bed (new SpinCtrlDouble(this)) +{ + spin_extr->SetIncrement(5.0); + const auto &def_extr = *print_config_def.get("temperature"); + spin_extr->SetRange(def_extr.min, def_extr.max); + auto *default_extr = def_extr.get_default_value(); + spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); + + spin_bed->SetIncrement(5.0); + const auto &def_bed = *print_config_def.get("bed_temperature"); + spin_bed->SetRange(def_bed.min, def_bed.max); + auto *default_bed = def_bed.get_default_value(); + spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); + + append_text(_L("Enter the temperature needed for extruding your filament.")); + append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")); + + auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); + auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:")); + auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C")); + sizer_extr->AddGrowableCol(0, 1); + sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); + sizer_extr->Add(spin_extr); + sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_extr); + + append_spacer(VERTICAL_SPACING); + + append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")); + append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")); + + auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); + auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:")); + auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C")); + sizer_bed->AddGrowableCol(0, 1); + sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); + sizer_bed->Add(spin_bed); + sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_bed); +} + +void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) +{ + auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("temperature", opt_extr); + auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("first_layer_temperature", opt_extr1st); + auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("bed_temperature", opt_bed); + auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("first_layer_bed_temperature", opt_bed1st); +} + + +// Index + +ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) + : wxPanel(parent) + , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192)) + , bullet_black(ScalableBitmap(parent, "bullet_black.png")) + , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) + , bullet_white(ScalableBitmap(parent, "bullet_white.png")) + , item_active(NO_ITEM) + , item_hover(NO_ITEM) + , last_page((size_t)-1) +{ +#ifndef __WXOSX__ + SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX +#endif //__WXOSX__ + SetMinSize(bg.GetSize()); + + const wxSize size = GetTextExtent("m"); + em_w = size.x; + em_h = size.y; + + Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); + Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); }); + Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this); + + Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) { + if (item_hover != -1) { + item_hover = -1; + Refresh(); + } + evt.Skip(); + }); + + Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) { + if (item_hover >= 0) { go_to(item_hover); } + }); +} + +wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); + +void ConfigWizardIndex::add_page(ConfigWizardPage *page) +{ + last_page = items.size(); + items.emplace_back(Item { page->shortname, page->indent, page }); + Refresh(); +} + +void ConfigWizardIndex::add_label(wxString label, unsigned indent) +{ + items.emplace_back(Item { std::move(label), indent, nullptr }); + Refresh(); +} + +ConfigWizardPage* ConfigWizardIndex::active_page() const +{ + if (item_active >= items.size()) { return nullptr; } + + return items[item_active].page; +} + +void ConfigWizardIndex::go_prev() +{ + // Search for a preceiding item that is a page (not a label, ie. page != nullptr) + + if (item_active == NO_ITEM) { return; } + + for (size_t i = item_active; i > 0; i--) { + if (items[i - 1].page != nullptr) { + go_to(i - 1); + return; + } + } +} + +void ConfigWizardIndex::go_next() +{ + // Search for a next item that is a page (not a label, ie. page != nullptr) + + if (item_active == NO_ITEM) { return; } + + for (size_t i = item_active + 1; i < items.size(); i++) { + if (items[i].page != nullptr) { + go_to(i); + return; + } + } +} + +// This one actually performs the go-to op +void ConfigWizardIndex::go_to(size_t i) +{ + if (i != item_active + && i < items.size() + && items[i].page != nullptr) { + auto *new_active = items[i].page; + auto *former_active = active_page(); + if (former_active != nullptr) { + former_active->Hide(); + } + + item_active = i; + new_active->Show(); + + wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); + AddPendingEvent(evt); + + Refresh(); + + new_active->on_activate(); + } +} + +void ConfigWizardIndex::go_to(const ConfigWizardPage *page) +{ + if (page == nullptr) { return; } + + for (size_t i = 0; i < items.size(); i++) { + if (items[i].page == page) { + go_to(i); + return; + } + } +} + +void ConfigWizardIndex::clear() +{ + auto *former_active = active_page(); + if (former_active != nullptr) { former_active->Hide(); } + + items.clear(); + item_active = NO_ITEM; +} + +void ConfigWizardIndex::on_paint(wxPaintEvent & evt) +{ + const auto size = GetClientSize(); + if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } + + wxPaintDC dc(this); + + const auto bullet_w = bullet_black.GetWidth(); + const auto bullet_h = bullet_black.GetHeight(); + const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; + const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; + const int yinc = item_height(); + + int index_width = 0; + + unsigned y = 0; + for (size_t i = 0; i < items.size(); i++) { + const Item& item = items[i]; + unsigned x = em_w/2 + item.indent * em_w; + + if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { + dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false); + } + else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); } + else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); } + + x += + bullet_w + em_w/2; + const auto text_size = dc.GetTextExtent(item.label); + dc.SetTextForeground(wxGetApp().get_label_clr_default()); + dc.DrawText(item.label, x, y + yoff_text); + + y += yinc; + index_width = std::max(index_width, (int)x + text_size.x); + } + + //draw logo + if (int y = size.y - bg.GetHeight(); y>=0) { + dc.DrawBitmap(bg.get_bitmap(), 0, y, false); + index_width = std::max(index_width, bg.GetWidth() + em_w / 2); + } + + if (GetMinSize().x < index_width) { + CallAfter([this, index_width]() { + SetMinSize(wxSize(index_width, GetMinSize().y)); + Refresh(); + }); + } +} + +void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) +{ + const wxClientDC dc(this); + const wxPoint pos = evt.GetLogicalPosition(dc); + + const ssize_t item_hover_new = pos.y / item_height(); + + if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { + item_hover = item_hover_new; + Refresh(); + } + + evt.Skip(); +} + +void ConfigWizardIndex::msw_rescale() +{ + const wxSize size = GetTextExtent("m"); + em_w = size.x; + em_h = size.y; + + SetMinSize(bg.GetSize()); + + Refresh(); +} + + +// Materials + +const std::string Materials::UNKNOWN = "(Unknown)"; + +void Materials::push(const Preset *preset) +{ + presets.emplace_back(preset); + types.insert(technology & T_FFF + ? Materials::get_filament_type(preset) + : Materials::get_material_type(preset)); +} + +void Materials::add_printer(const Preset* preset) +{ + printers.insert(preset); +} + +void Materials::clear() +{ + presets.clear(); + types.clear(); + printers.clear(); + compatibility_counter.clear(); +} + +const std::string& Materials::appconfig_section() const +{ + return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; +} + +const std::string& Materials::get_type(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); +} + +const std::string& Materials::get_vendor(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); +} + +const std::string& Materials::get_filament_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_type"); + if (opt != nullptr && opt->values.size() > 0) { + return opt->values[0]; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_filament_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + +const std::string& Materials::get_material_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_type"); + if (opt != nullptr) { + return opt->value; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_material_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + +// priv + +static const std::unordered_map> legacy_preset_map {{ + { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, + { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, +}}; + +void ConfigWizard::priv::load_pages() +{ + wxWindowUpdateLocker freeze_guard(q); + (void)freeze_guard; + + const ConfigWizardPage *former_active = index->active_page(); + + index->clear(); + + index->add_page(page_welcome); + + // Printers + if (!only_sla_mode) + index->add_page(page_fff); + index->add_page(page_msla); + if (!only_sla_mode) { + index->add_page(page_vendors); + for (const auto &pages : pages_3rdparty) { + for ( PagePrinters* page : { pages.second.first, pages.second.second }) + if (page && page->install) + index->add_page(page); + } + + index->add_page(page_custom); + if (page_custom->custom_wanted()) { + index->add_page(page_firmware); + index->add_page(page_bed); + index->add_page(page_bvolume); + index->add_page(page_diams); + index->add_page(page_temps); + } + + // Filaments & Materials + if (any_fff_selected) { index->add_page(page_filaments); } + // Filaments page if only custom printer is selected + const AppConfig* app_config = wxGetApp().app_config; + if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) { + update_materials(T_ANY); + index->add_page(page_filaments); + } + } + if (any_sla_selected) { index->add_page(page_sla_materials); } + + // there should to be selected at least one printer + btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle); + + index->add_page(page_update); + index->add_page(page_downloader); + index->add_page(page_reload_from_disk); +#ifdef _WIN32 + index->add_page(page_files_association); +#endif // _WIN32 + index->add_page(page_mode); + + index->go_to(former_active); // Will restore the active item/page if possible + + q->Layout(); +// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig + q->Refresh(); +} + +void ConfigWizard::priv::init_dialog_size() +{ + // Clamp the Wizard size based on screen dimensions + + const auto idx = wxDisplay::GetFromWindow(q); + wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); + + const auto disp_rect = display.GetClientArea(); + wxRect window_rect( + disp_rect.x + disp_rect.width / 20, + disp_rect.y + disp_rect.height / 20, + 9*disp_rect.width / 10, + 9*disp_rect.height / 10); + + const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution + if (width_hint < window_rect.width) { + window_rect.x += (window_rect.width - width_hint) / 2; + window_rect.width = width_hint; + } + + q->SetSize(window_rect); +} + +void ConfigWizard::priv::load_vendors() +{ + bundles = BundleMap::load(); + + // Load up the set of vendors / models / variants the user has had enabled up till now + AppConfig *app_config = wxGetApp().app_config; + if (! app_config->legacy_datadir()) { + appconfig_new.set_vendors(*app_config); + } else { + // In case of legacy datadir, try to guess the preference based on the printer preset files that are present + const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; + for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) + if (Slic3r::is_ini_file(dir_entry)) { + auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); + if (needle == legacy_preset_map.end()) { continue; } + + const auto &model = needle->second.first; + const auto &variant = needle->second.second; + appconfig_new.set_variant("PrusaResearch", model, variant, true); + } + } + + for (const auto& printer : wxGetApp().preset_bundle->printers) { + if (!printer.is_default && !printer.is_system && printer.is_visible) { + custom_printer_in_bundle = true; + break; + } + } + + // Initialize the is_visible flag in printer Presets + for (auto &pair : bundles) { + pair.second.preset_bundle->load_installed_printers(appconfig_new); + } + + // Copy installed filaments and SLA material names from app_config to appconfig_new + // while resolving current names of profiles, which were renamed in the meantime. + for (PrinterTechnology technology : { ptFFF, ptSLA }) { + const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; + std::map section_new; + if (app_config->has_section(section_name)) { + const std::map §ion_old = app_config->get_section(section_name); + for (const auto& material_name_and_installed : section_old) + if (material_name_and_installed.second == "1") { + // Material is installed. Resolve it in bundles. + size_t num_found = 0; + const std::string &material_name = material_name_and_installed.first; + for (auto &bundle : bundles) { + const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); + const Preset *preset = materials.find_preset(material_name); + if (preset == nullptr) { + // Not found. Maybe the material preset is there, bu it was was renamed? + const std::string *new_name = materials.get_preset_name_renamed(material_name); + if (new_name != nullptr) + preset = materials.find_preset(*new_name); + } + if (preset != nullptr) { + // Materal preset was found, mark it as installed. + section_new[preset->name] = "1"; + ++ num_found; + } + } + if (num_found == 0) + BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name; + else if (num_found > 1) + BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found; + } + } + appconfig_new.set_section(section_name, section_new); + }; +} + +void ConfigWizard::priv::add_page(ConfigWizardPage *page) +{ + const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; + hscroll_sizer->Add(page, proportion, wxEXPAND); + all_pages.push_back(page); +} + +void ConfigWizard::priv::enable_next(bool enable) +{ + btn_next->Enable(enable); + btn_finish->Enable(enable); +} + +void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) +{ + switch (start_page) { + case ConfigWizard::SP_PRINTERS: + index->go_to(page_fff); + btn_next->SetFocus(); + break; + case ConfigWizard::SP_FILAMENTS: + index->go_to(page_filaments); + btn_finish->SetFocus(); + break; + case ConfigWizard::SP_MATERIALS: + index->go_to(page_sla_materials); + btn_finish->SetFocus(); + break; + default: + index->go_to(page_welcome); + btn_next->SetFocus(); + break; + } +} + +void ConfigWizard::priv::create_3rdparty_pages() +{ + for (const auto &pair : bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + + bool is_fff_technology = false; + bool is_sla_technology = false; + + for (auto& model: vendor->models) + { + if (!is_fff_technology && model.technology == ptFFF) + is_fff_technology = true; + if (!is_sla_technology && model.technology == ptSLA) + is_sla_technology = true; + } + + PagePrinters* pageFFF = nullptr; + PagePrinters* pageSLA = nullptr; + + if (is_fff_technology) { + pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF); + add_page(pageFFF); + } + + if (is_sla_technology) { + pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA); + add_page(pageSLA); + } + + pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}}); + } +} + +void ConfigWizard::priv::set_run_reason(RunReason run_reason) +{ + this->run_reason = run_reason; + for (auto &page : all_pages) { + page->set_run_reason(run_reason); + } +} + +void ConfigWizard::priv::update_materials(Technology technology) +{ + if ((any_fff_selected || custom_printer_in_bundle || custom_printer_selected) && (technology & T_FFF)) { + filaments.clear(); + aliases_fff.clear(); + // Iterate filaments in all bundles + for (const auto &pair : bundles) { + for (const auto &filament : pair.second.preset_bundle->filaments) { + // Check if filament is already added + if (filaments.containts(&filament)) + continue; + // Iterate printers in all bundles + for (const auto &printer : pair.second.preset_bundle->printers) { + if (!printer.is_visible || printer.printer_technology() != ptFFF) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + filaments.add_printer(&printer); + } + } + // template filament bundle has no printers - filament would be never added + if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) + { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + } + } + } + // count compatible printers + for (const auto& preset : filaments.presets) { + // skip template filaments + if (preset->vendor && preset->vendor->templates_profile) + continue; + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { + continue; + } + // find all aliases (except templates) + std::vector idx_with_same_alias; + for (size_t i = 0; i < filaments.presets.size(); ++i) { + if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor)) + idx_with_same_alias.push_back(i); + } + // check compatibility with each printer + size_t counter = 0; + for (const auto& printer : filaments.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) + continue; + bool compatible = false; + // Test other materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + filaments.compatibility_counter.emplace_back(preset->alias, counter); + } + } + + if (any_sla_selected && (technology & T_SLA)) { + sla_materials.clear(); + aliases_sla.clear(); + + // Iterate SLA materials in all bundles + for (const auto &pair : bundles) { + for (const auto &material : pair.second.preset_bundle->sla_materials) { + // Check if material is already added + if (sla_materials.containts(&material)) + continue; + // Iterate printers in all bundles + // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. + for (const auto& printer : pair.second.preset_bundle->printers) { + if(!printer.is_visible || printer.printer_technology() != ptSLA) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { + // Check if material is already added + if(!sla_materials.containts(&material)) { + sla_materials.push(&material); + if (!material.alias.empty()) + aliases_sla[material.alias].insert(material.name); + } + sla_materials.add_printer(&printer); + } + } + } + } + // count compatible printers + for (const auto& preset : sla_materials.presets) { + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { + continue; + } + std::vector idx_with_same_alias; + for (size_t i = 0; i < sla_materials.presets.size(); ++i) { + if(preset->alias == sla_materials.presets[i]->alias) + idx_with_same_alias.push_back(i); + } + size_t counter = 0; + for (const auto& printer : sla_materials.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) + continue; + bool compatible = false; + // Test otrher materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + sla_materials.compatibility_counter.emplace_back(preset->alias, counter); + } + } +} + +void ConfigWizard::priv::on_custom_setup(const bool custom_wanted) +{ + custom_printer_selected = custom_wanted; + load_pages(); +} + +void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) +{ + if (check_sla_selected() != any_sla_selected || + check_fff_selected() != any_fff_selected) { + any_fff_selected = check_fff_selected(); + any_sla_selected = check_sla_selected(); + + load_pages(); + } + + // Update the is_visible flag on relevant printer profiles + for (auto &pair : bundles) { + if (pair.first != evt.vendor_id) { continue; } + + for (auto &preset : pair.second.preset_bundle->printers) { + if (preset.config.opt_string("printer_model") == evt.model_id + && preset.config.opt_string("printer_variant") == evt.variant_name) { + preset.is_visible = evt.enable; + } + } + + // When a printer model is picked, but there is no material installed compatible with this printer model, + // install default materials for selected printer model silently. + check_and_install_missing_materials(page->technology, evt.model_id); + } + + if (page->technology & T_FFF) { + page_filaments->clear(); + } else if (page->technology & T_SLA) { + page_sla_materials->clear(); + } +} + +void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology) +{ + PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; + for (const std::string& material : printer_model.default_materials) + appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); +} + +void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models) +{ + PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials; + const std::string &appconfig_section = page_materials->materials->appconfig_section(); + + // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. + // Filament is selected on same page for all printers of same technology. + /* + auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology) + { + const std::string vendor_id = page_printers->get_vendor_id(); + for (auto& pair : bundles) + if (pair.first == vendor_id) + for (const VendorProfile::PrinterModel *printer_model : printer_models) + for (const std::string &material : printer_model->default_materials) + appconfig_new.set(appconfig_section, material, "1"); + }; + + PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; + select_default_materials_for_printer_page(page_printers, technology); + + for (const auto& printer : pages_3rdparty) + { + page_printers = technology & T_FFF ? printer.second.first : printer.second.second; + if (page_printers) + select_default_materials_for_printer_page(page_printers, technology); + } + */ + + // Iterate printer_models and select default materials. If none available -> msg to user. + std::vector models_without_default; + for (const VendorProfile::PrinterModel* printer_model : printer_models) { + if (printer_model->default_materials.empty()) { + models_without_default.emplace_back(printer_model); + } else { + for (const std::string& material : printer_model->default_materials) + appconfig_new.set(appconfig_section, material, "1"); + } + } + + if (!models_without_default.empty()) { + std::string printer_names = "\n\n"; + for (const VendorProfile::PrinterModel* printer_model : models_without_default) { + printer_names += printer_model->name + "\n"; + } + printer_names += "\n\n"; + std::string message = (technology & T_FFF ? + GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) : + GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names)); + MessageDialog msg(q, message, _L("Notice"), wxOK); + msg.ShowModal(); + } + + update_materials(technology); + ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets(); +} + +void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) +{ + auto it = pages_3rdparty.find(vendor->id); + wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); + + for (PagePrinters* page : { it->second.first, it->second.second }) + if (page) { + if (page->install && !install) + page->select_all(false); + page->install = install; + // if some 3rd vendor is selected, select first printer for them + if (install) + page->printer_pickers[0]->select_one(0, true); + page->Layout(); + } + + load_pages(); +} + +bool ConfigWizard::priv::on_bnt_finish() +{ + wxBusyCursor wait; + + if (!page_downloader->on_finish_downloader()) { + index->go_to(page_downloader); + return false; + } + /* When Filaments or Sla Materials pages are activated, + * materials for this pages are automaticaly updated and presets are reloaded. + * + * But, if _Finish_ button was clicked without activation of those pages + * (for example, just some printers were added/deleted), + * than last changes wouldn't be updated for filaments/materials. + * SO, do that before close of Wizard + */ + update_materials(T_ANY); + if (any_fff_selected) + page_filaments->reload_presets(); + if (any_sla_selected) + page_sla_materials->reload_presets(); + + // theres no need to check that filament is selected if we have only custom printer + if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; + // check, that there is selected at least one filament/material + return check_and_install_missing_materials(T_ANY); +} + +// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed +// for each Printer preset of each Printer Model installed. +// +// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently. +// Otherwise the user is quieried whether to install the missing default materials or not. +// +// Return true if the tested Printer Models already had materials installed. +// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these +// respective Printer Models or not. +bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id) +{ + // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle, + // which is compatible with it. + const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion) + { + const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map(); + std::set printer_models_without_material; + for (const auto &pair : bundles) { + const PresetCollection &materials = pair.second.preset_bundle->materials(technology); + for (const auto &printer : pair.second.preset_bundle->printers) { + if (printer.is_visible && printer.printer_technology() == technology) { + const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer); + assert(printer_model != nullptr); + if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) && + printer_models_without_material.find(printer_model) == printer_models_without_material.end()) { + bool has_material = false; + for (const auto& preset : appconfig_presets) { + if (preset.second == "1") { + const Preset *material = materials.find_preset(preset.first, false); + if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) { + has_material = true; + break; + } + + // find if preset.first is part of the templates profile (up is searching if preset.first is part of printer vendor preset) + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material && is_compatible_with_printer(PresetWithVendorProfile(*template_material, &bp.second.preset_bundle->vendors.begin()->second), PresetWithVendorProfile(printer, nullptr))) { + has_material = true; + break; + } + } + } + if (has_material) + break; + + } + } + if (! has_material) + printer_models_without_material.insert(printer_model); + } + } + } + } + // template_profile_selected check + template_profile_selected = false; + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + for (const auto& preset : appconfig_presets) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material){ + template_profile_selected = true; + break; + } + } + if (template_profile_selected) { + break; + } + } + } + assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); + return printer_models_without_material; + }; + + const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) + { + //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); + MessageDialog msg(q, message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_YES) + select_default_materials_for_printer_models(technology, printer_models); + }; + + const auto printer_model_list = [](const std::set &printer_models) -> wxString { + wxString out; + for (const VendorProfile::PrinterModel *printer_model : printer_models) { + wxString name = from_u8(printer_model->name); + out += "\t\t"; + out += name; + out += "\n"; + } + return out; + }; + + if (any_fff_selected && (technology & T_FFF)) { + std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS); + if (! printer_models_without_material.empty()) { + if (only_for_model_id.empty()) + ask_and_select_default_materials( + _L("The following FFF printer models have no filament selected:") + + "\n\n\t" + + printer_model_list(printer_models_without_material) + + "\n\n\t" + + _L("Do you want to select default filaments for these FFF printer models?"), + printer_models_without_material, + T_FFF); + else + select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF); + return false; + } + } + + if (any_sla_selected && (technology & T_SLA)) { + std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS); + if (! printer_models_without_material.empty()) { + if (only_for_model_id.empty()) + ask_and_select_default_materials( + _L("The following SLA printer models have no materials selected:") + + "\n\n\t" + + printer_model_list(printer_models_without_material) + + "\n\n\t" + + _L("Do you want to select default SLA materials for these printer models?"), + printer_models_without_material, + T_SLA); + else + select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA); + return false; + } + } + + return true; +} + +static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data) +{ + auto get_aliases = [](const std::map& data) { + std::set old_aliases; + for (auto item : data) { + const std::string& name = item.first; + size_t pos = name.find("@"); + old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1)); + } + return old_aliases; + }; + + std::set old_aliases = get_aliases(old_data); + std::set new_aliases = get_aliases(new_data); + std::set diff; + std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin())); + + return diff; +} + +static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data) +{ + std::set diff = get_new_added_presets(old_data, new_data); + if (diff.empty()) + return std::string(); + return *diff.begin(); +} + +bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) +{ + wxString header, caption = _L("Configuration is edited in ConfigWizard"); + const auto enabled_vendors = appconfig_new.vendors(); + const auto enabled_vendors_old = app_config->vendors(); + + bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); + PrinterTechnology preferred_pt = ptAny; + auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { + const auto config = enabled_vendors.find(bundle_name); + PrinterTechnology pt = ptAny; + if (config != enabled_vendors.end()) { + for (const auto& model : bundle.vendor_profile->models) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0) { + pt = model.technology; + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } + + if (const auto model_it_old = config_old->second.find(model.id); + model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } + } + } + } + return pt; + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); + preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) + continue; + else if (preferred_pt == ptAny) + preferred_pt = pt; + if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) + break; + } + } + + if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) + return false; + + bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); + if (check_unsaved_preset_changes) + header = _L("All user presets will be deleted."); + int act_btns = ActionButtons::KEEP; + if (!check_unsaved_preset_changes) + act_btns |= ActionButtons::SAVE; + + // Install bundles from resources or cache / vendor if needed: + std::vector install_bundles; + for (const auto &pair : bundles) { + if (pair.second.location == BundleLocation::IN_VENDOR) { continue; } + + if (pair.second.is_prusa_bundle) { + // Always install Prusa bundle, because it has a lot of filaments/materials + // likely to be referenced by other profiles. + install_bundles.emplace_back(pair.first); + continue; + } + + const auto vendor = enabled_vendors.find(pair.first); + if (vendor == enabled_vendors.end() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } + + if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { + // Templates vendor needs to be installed + install_bundles.emplace_back(pair.first); + continue; + } + + size_t size_sum = 0; + for (const auto &model : vendor->second) { size_sum += model.second.size(); } + + if (size_sum > 0) { + // This vendor needs to be installed + install_bundles.emplace_back(pair.first); + } + } + if (!check_unsaved_preset_changes) + if ((check_unsaved_preset_changes = install_bundles.size() > 0)) + header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size()); + +#ifdef __linux__ + // Desktop integration on Linux + BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux(); + if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux()) + DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux()); +#endif + + // Decide whether to create snapshot based on run_reason and the reset profile checkbox + bool snapshot = true; + Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; + switch (run_reason) { + case ConfigWizard::RR_DATA_EMPTY: + snapshot = false; + break; + case ConfigWizard::RR_DATA_LEGACY: + snapshot = true; + break; + case ConfigWizard::RR_DATA_INCOMPAT: + // In this case snapshot has already been taken by + // PresetUpdater with the appropriate reason + snapshot = false; + break; + case ConfigWizard::RR_USER: + snapshot = page_welcome->reset_user_profile(); + snapshot_reason = Snapshot::SNAPSHOT_USER; + break; + } + + if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?"))) + return false; + + if (check_unsaved_preset_changes && + !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + + if (install_bundles.size() > 0) { + // Install bundles from resources or cache / vendor. + // Don't create snapshot - we've already done that above if applicable. + if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) + return false; + } else { + BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor"; + } + + if (page_welcome->reset_user_profile()) { + BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; + preset_bundle->reset(true); + } + + std::string preferred_model; + std::string preferred_variant; + auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { + const auto config = enabled_vendors.find(bundle_name); + if (config == enabled_vendors.end()) + return std::string(); + for (const auto& model : bundle.vendor_profile->models) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0 && + preferred_pt == model.technology) { + variant = *model_it->second.begin(); + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end()) + return model.id; + const auto model_it_old = config_old->second.find(model.id); + if (model_it_old == config_old->second.end()) + return model.id; + else if (model_it_old->second != model_it->second) { + for (const auto& var : model_it->second) + if (model_it_old->second.find(var) == model_it_old->second.end()) { + variant = var; + return model.id; + } + } + } + } + if (!variant.empty()) + variant.clear(); + return std::string(); + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant); + preferred_model.empty()) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant); + !preferred_model.empty()) + break; + } + } + + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !preferred_model.empty())) { + header = _L("A new Printer was installed and it will be activated."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { + header = _L("Some Printers were uninstalled."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + + std::string first_added_filament, first_added_sla_material; + auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { + if (appconfig_new.has_section(section_name)) { + // get first of new added preset names + const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map(); + first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); + } + }; + get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); + get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); + + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { + header = !first_added_filament.empty() ? + _L("A new filament was installed and it will be activated.") : + _L("A new SLA material was installed and it will be activated."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else { + auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { + return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name); + }; + bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); + bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); + if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { + header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + } + + // apply materials in app_config + for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) + app_config->set_section(section_name, appconfig_new.get_section(section_name)); + + app_config->set_vendors(appconfig_new); + + app_config->set("notify_release", page_update->version_check ? "all" : "none"); + app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); + +#ifdef _WIN32 + app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); + app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); +// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); + + if (wxGetApp().is_editor()) { + if (page_files_association->associate_3mf()) + wxGetApp().associate_3mf_files(); + if (page_files_association->associate_stl()) + wxGetApp().associate_stl_files(); + } +// else { +// if (page_files_association->associate_gcode()) +// wxGetApp().associate_gcode_files(); +// } +#endif // _WIN32 + + page_mode->serialize_mode(app_config); + + if (check_unsaved_preset_changes) + preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, + {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); + + if (!only_sla_mode && page_custom->custom_wanted() && page_custom->is_valid_profile_name()) { + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes && + !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes)) + return false; + + page_firmware->apply_custom_config(*custom_config); + page_bed->apply_custom_config(*custom_config); + page_bvolume->apply_custom_config(*custom_config); + page_diams->apply_custom_config(*custom_config); + page_temps->apply_custom_config(*custom_config); + + copy_bed_model_and_texture_if_needed(*custom_config); + + const std::string profile_name = page_custom->profile_name(); + preset_bundle->load_config_from_wizard(profile_name, *custom_config); + } + + // Update the selections from the compatibilty. + preset_bundle->export_selections(*app_config); + + return true; +} +void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) +{ + const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; + + auto update = [this, add](const std::string& s, const std::string& key) { + assert(! s.empty()); + if (add) + appconfig_new.set(s, key, "1"); + else + appconfig_new.erase(s, key); + }; + + // add or delete presets had a same alias + auto it = aliases.find(alias_key); + if (it != aliases.end()) + for (const std::string& name : it->second) + update(section, name); +} + +bool ConfigWizard::priv::check_fff_selected() +{ + bool ret = page_fff->any_selected(); + for (const auto& printer: pages_3rdparty) + if (printer.second.first) // FFF page + ret |= printer.second.first->any_selected(); + return ret; +} + +bool ConfigWizard::priv::check_sla_selected() +{ + bool ret = page_msla->any_selected(); + for (const auto& printer: pages_3rdparty) + if (printer.second.second) // SLA page + ret |= printer.second.second->any_selected(); + return ret; +} + + +// Public + +ConfigWizard::ConfigWizard(wxWindow *parent) + : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + , p(new priv(this)) +{ + this->SetFont(wxGetApp().normal_font()); + + p->load_vendors(); + p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ + "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + })); + + p->index = new ConfigWizardIndex(this); + + auto *vsizer = new wxBoxSizer(wxVERTICAL); + auto *topsizer = new wxBoxSizer(wxHORIZONTAL); + auto* hline = new StaticLine(this); + p->btnsizer = new wxBoxSizer(wxHORIZONTAL); + + // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. + // Later, we compare that to the size of the current screen and set minimum width based on that (see below). + p->hscroll = new wxScrolledWindow(this); + p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL); + p->hscroll->SetSizer(p->hscroll_sizer); + + topsizer->Add(p->index, 0, wxEXPAND); + topsizer->AddSpacer(INDEX_MARGIN); + topsizer->Add(p->hscroll, 1, wxEXPAND); + + p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers")); + p->btnsizer->Add(p->btn_sel_all); + + p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back")); + p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >")); + p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish")); + p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac + p->btnsizer->AddStretchSpacer(); + p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); + + wxGetApp().UpdateDarkUI(p->btn_sel_all); + wxGetApp().UpdateDarkUI(p->btn_prev); + wxGetApp().UpdateDarkUI(p->btn_next); + wxGetApp().UpdateDarkUI(p->btn_finish); + wxGetApp().UpdateDarkUI(p->btn_cancel); + + const auto prusa_it = p->bundles.find("PrusaResearch"); + wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); + const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; + + p->add_page(p->page_welcome = new PageWelcome(this)); + + + p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF); + p->only_sla_mode = !p->page_fff->has_printers; + if (!p->only_sla_mode) { + p->add_page(p->page_fff); + p->page_fff->is_primary_printer_page = true; + } + + + p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA); + p->add_page(p->page_msla); + if (p->only_sla_mode) { + p->page_msla->is_primary_printer_page = true; + } + + if (!p->only_sla_mode) { + // Pages for 3rd party vendors + p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors + p->add_page(p->page_vendors = new PageVendors(this)); + p->add_page(p->page_custom = new PageCustom(this)); + p->custom_printer_selected = p->page_custom->custom_wanted(); + } + + p->any_sla_selected = p->check_sla_selected(); + p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected(); + + p->update_materials(T_ANY); + if (!p->only_sla_mode) + p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, + _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") )); + + p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, + _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); + + + p->add_page(p->page_update = new PageUpdate(this)); + p->add_page(p->page_downloader = new PageDownloader(this)); + p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); +#ifdef _WIN32 + p->add_page(p->page_files_association = new PageFilesAssociation(this)); +#endif // _WIN32 + p->add_page(p->page_mode = new PageMode(this)); + p->add_page(p->page_firmware = new PageFirmware(this)); + p->add_page(p->page_bed = new PageBedShape(this)); + p->add_page(p->page_bvolume = new PageBuildVolume(this)); + p->add_page(p->page_diams = new PageDiameters(this)); + p->add_page(p->page_temps = new PageTemperatures(this)); + + p->load_pages(); + p->index->go_to(size_t{0}); + + vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); + vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); + vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); + + SetSizer(vsizer); + SetSizerAndFit(vsizer); + + // We can now enable scrolling on hscroll + p->hscroll->SetScrollRate(30, 30); + + on_window_geometry(this, [this]() { + p->init_dialog_size(); + }); + + p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); + + p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) + { + // check, that there is selected at least one filament/material + ConfigWizardPage* active_page = this->p->index->active_page(); + if (// Leaving the filaments or SLA materials page and + (active_page == p->page_filaments || active_page == p->page_sla_materials) && + // some Printer models had no filament or SLA material selected. + ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) + // In that case don't leave the page and the function above queried the user whether to install default materials. + return; + this->p->index->go_next(); + }); + + p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) + { + if (p->on_bnt_finish()) + this->EndModal(wxID_OK); + }); + + p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { + p->any_sla_selected = true; + p->load_pages(); + p->page_fff->select_all(true, false); + p->page_msla->select_all(true, false); + p->index->go_to(p->page_mode); + }); + + p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { + const bool is_last = p->index->active_is_last(); + p->btn_next->Show(! is_last); + if (is_last) + p->btn_finish->SetFocus(); + + Layout(); + }); + + if (wxLinux_gtk3) + this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { + ConfigWizardPage* active_page = p->index->active_page(); + if (!active_page) + return; + for (auto page : p->all_pages) + if (page != active_page) + page->Hide(); + // update best size for the dialog after hiding of the non-active pages + vsizer->SetSizeHints(this); + // set initial dialog size + p->init_dialog_size(); + }); +} + +ConfigWizard::~ConfigWizard() {} + +bool ConfigWizard::run(RunReason reason, StartPage start_page) +{ + BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; + + GUI_App &app = wxGetApp(); + + p->set_run_reason(reason); + p->set_start_page(start_page); + + if (ShowModal() == wxID_OK) { + bool apply_keeped_changes = false; + if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) + return false; + + if (apply_keeped_changes) + app.apply_keeped_preset_modifications(); + + app.app_config->set_legacy_datadir(false); + app.update_mode(); + app.obj_manipul()->update_ui_from_settings(); + BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; + return true; + } else { + BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; + return false; + } +} + +const wxString& ConfigWizard::name(const bool from_menu/* = false*/) +{ + // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. + // Note: Don't call _() macro here. + // This function just return the current name according to the OS. + // Translation is implemented inside GUI_App::add_config_menu() +#if __APPLE__ + static const wxString config_wizard_name = L("Configuration Assistant"); + static const wxString config_wizard_name_menu = L("Configuration &Assistant"); +#else + static const wxString config_wizard_name = L("Configuration Wizard"); + static const wxString config_wizard_name_menu = L("Configuration &Wizard"); +#endif + return from_menu ? config_wizard_name_menu : config_wizard_name; +} + +void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) +{ + p->index->msw_rescale(); + + const int em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_APPLY, + wxID_CANCEL, + p->btn_sel_all->GetId(), + p->btn_next->GetId(), + p->btn_prev->GetId() }); + + for (auto printer_picker: p->page_fff->printer_pickers) + msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); + + p->init_dialog_size(); + + Refresh(); +} + +void ConfigWizard::on_sys_color_changed() +{ + wxGetApp().UpdateDlgDarkUI(this); + Refresh(); +} + +} +} diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 0d63de95d..0c3fed13f 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -60,18 +60,25 @@ enum Technology { T_ANY = ~0, }; +enum BundleLocation{ + IN_VENDOR, + IN_ARCHIVE, + IN_RESOURCES +}; + struct Bundle { std::unique_ptr preset_bundle; VendorProfile* vendor_profile{ nullptr }; - bool is_in_resources{ false }; + //bool is_in_resources{ false }; + BundleLocation location; bool is_prusa_bundle{ false }; Bundle() = default; Bundle(Bundle&& other); // Returns false if not loaded. Reason for that is logged as boost::log error. - bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false); + bool load(fs::path source_path, BundleLocation location, bool is_prusa_bundle = false); const std::string& vendor_id() const { return vendor_profile->id; } }; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index cee611bdb..bb3d91ea5 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -20,6 +20,7 @@ #include "libslic3r/format.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/miniz_extension.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/UpdateDialogs.hpp" @@ -144,6 +145,7 @@ struct PresetUpdater::priv std::string version_check_url; fs::path cache_path; + fs::path cache_vendor_path; fs::path rsrc_path; fs::path vendor_path; @@ -168,6 +170,7 @@ struct PresetUpdater::priv PresetUpdater::priv::priv() : cache_path(fs::path(Slic3r::data_dir()) / "cache") + , cache_vendor_path(cache_path / "vendor") , rsrc_path(fs::path(resources_dir()) / "profiles") , vendor_path(fs::path(Slic3r::data_dir()) / "vendor") , cancel(false) @@ -234,44 +237,82 @@ void PresetUpdater::priv::prune_tmps() const // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_config(const VendorMap vendors) +void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& profile_archive_url) { BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; if (!enabled_config_update) { return; } - // Donwload vendor preset bundles + // Download profiles archive zip + // dk: Do we want to return here on error? Or skip archive dwnld and unzip and work with previous run state cache / vendor? I think return. + // Any error here also doesnt show any info in UI. Do we want maybe notification? + fs::path archive_path(cache_path / "Archive.zip"); + if (profile_archive_url.empty()) { + BOOST_LOG_TRIVIAL(error) << "Downloading profile archive failed - url has no value."; + return; + } + BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip."; + //check if idx_url is leading to our site + if (!boost::starts_with(profile_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(profile_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + BOOST_LOG_TRIVIAL(error) << "Unsafe url path for vedor profiles archive zip. Download is rejected."; + // TODO: this return must be uncommented when correct address is in use + //return; + } + if (!get_file(profile_archive_url, archive_path)) { + BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; + return; + } + if (cancel) { return; } + + // Unzip archive to cache / vendor + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + if (!open_zip_reader(&archive, archive_path.string())) { + BOOST_LOG_TRIVIAL(error) << "Couldn't open zipped bundle."; + return; + } else { + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + // loop the entries + mz_zip_archive_file_stat stat; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + BOOST_LOG_TRIVIAL(error) << "Failed to unzip " << stat.m_filename; + continue; + } + // create file from buffer + fs::path tmp_path(cache_vendor_path / (name + ".tmp")); + fs::path target_path(cache_vendor_path / name); + fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(buffer.c_str(), buffer.size()); + file.close(); + fs::rename(tmp_path, target_path); + } + } + } + close_zip_reader(&archive); + } + + // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { if (cancel) { return; } const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { - BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor(); + BOOST_LOG_TRIVIAL(info) << "No such vendor: " << index.vendor(); continue; } - const VendorProfile &vendor = vendor_it->second; - if (vendor.config_update_url.empty()) { - BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name; - continue; - } - - // Download a fresh index - BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name; - const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); - const std::string idx_path_temp = idx_path + "-update"; - //check if idx_url is leading to our site - if (! boost::starts_with(idx_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - ! boost::starts_with(idx_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) - { - BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url; - continue; - } - if (!get_file(idx_url, idx_path_temp)) { continue; } - if (cancel) { return; } - + const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); + // Load the fresh index up { Index new_index; @@ -285,7 +326,8 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) BOOST_LOG_TRIVIAL(warning) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); continue; } - Slic3r::rename_file(idx_path_temp, idx_path); + copy_file_fix(idx_path_temp, idx_path); + //if we rename path we need to change it in Index object too or create the object again //index = std::move(new_index); try { @@ -314,22 +356,24 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) recommended.to_string()); if (vendor.config_version >= recommended) { continue; } + + const auto path_in_archive = cache_vendor_path / (vendor.id + ".ini"); + const auto path_in_cache = cache_path / (vendor.id + ".ini"); + // Check version + if (!boost::filesystem::exists(path_in_archive)) + continue; + // vp is fully loaded to get all resources + auto vp = VendorProfile::from_ini(path_in_archive, true); + if (vp.config_version != recommended) + continue; + copy_file_fix(path_in_archive, path_in_cache); - // Download a fresh bundle - BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; - const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string()); - const auto bundle_path = cache_path / (vendor.id + ".ini"); - if (! get_file(bundle_url, bundle_path)) { continue; } - if (cancel) { return; } - - // check the newly downloaded bundle for missing resources - // for that, the ini file must be parsed + // check the fresh bundle for missing resources + // for that, the ini file must be parsed (done above) auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const std::string& url, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path){ if (!boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - // testing url (to be removed) - //const auto resource_url = format("https://raw.githubusercontent.com/prusa3d/PrusaSlicer/master/resources/profiles/%1%/%2%", vendor, filename); const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); const auto resource_path = (cache_path / (vendor + "/" + filename)); if (!fs::exists(resource_path.parent_path())) @@ -337,7 +381,6 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) self.get_file(resource_url, resource_path); } }; - auto vp = VendorProfile::from_ini(bundle_path, true); for (const auto& model : vp.models) { check_and_get_resource(vp.id, model.bed_texture, vendor.config_update_url, vendor_path, rsrc_path, cache_path); if (cancel) { return; } @@ -426,7 +469,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version bool current_not_supported = false; //if slcr is incompatible but situation is not downgrade, we do forced updated and this bool is information to do it if (ver_current_found && !ver_current->is_current_slic3r_supported()){ - if(ver_current->is_current_slic3r_downgrade()) { + if (ver_current->is_current_slic3r_downgrade()) { // "Reconfigure" situation. BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); @@ -694,8 +737,9 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) // Unfortunatelly as of C++11, it needs to be copied again // into the closure (but perhaps the compiler can elide this). VendorMap vendors = preset_bundle->vendors; + std::string profile_archive_url =GUI::wxGetApp().app_config->profile_archive_url(); - p->thread = std::thread([this, vendors]() { + p->thread = std::thread([this, vendors, profile_archive_url]() { this->p->prune_tmps(); this->p->sync_config(std::move(vendors)); }); @@ -873,8 +917,26 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool for (const auto &bundle : bundles) { auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini"); + auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); - updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + // find if in cache vendor is newer version than in resources + auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); + auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); + if (vp_cache.config_version > vp_rsrc.config_version) { + // in case we are installing from cache / vendor. we should also copy index to cache + // This needs to be done now bcs the current one would be missing this version on next start + auto path_idx_cache_vendor(path_in_cache_vendor); + path_idx_cache_vendor.replace_extension(".idx"); + auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); + // DK: do this during perform_updates() too? + if (fs::exists(path_idx_cache_vendor)) + copy_file_fix(path_idx_cache_vendor, path_idx_cache); + else // Should we dialog this? + BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); + updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); + + } else + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); } return p->perform_updates(std::move(updates), snapshot); From 7873c28584828e2c468717bd04dd9da0ecf8fd89 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 14 Apr 2022 12:52:49 +0200 Subject: [PATCH 137/206] after rebase changes --- src/slic3r/Utils/PresetUpdater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index bb3d91ea5..878b819f7 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -160,7 +160,7 @@ struct PresetUpdater::priv void set_download_prefs(AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; - void sync_config(const VendorMap vendors); + void sync_config(const VendorMap vendors, const std::string& profile_archive_url); void check_install_indices() const; Updates get_config_updates(const Semver& old_slic3r_version) const; @@ -741,7 +741,7 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) p->thread = std::thread([this, vendors, profile_archive_url]() { this->p->prune_tmps(); - this->p->sync_config(std::move(vendors)); + this->p->sync_config(std::move(vendors), profile_archive_url); }); } From 41d5c16b7672600f8b625cbac4ee57e9d2e361a3 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 22 Apr 2022 15:04:58 +0200 Subject: [PATCH 138/206] fix of crash on empty config -> add template filament fixed checking if template profile needs to be installed fixed checking path before loading profile header from cache / vendor --- src/slic3r/GUI/ConfigWizard.cpp | 39 +++++++++++++++++------------- src/slic3r/GUI/GUI_App.cpp | 1 + src/slic3r/Utils/PresetUpdater.cpp | 33 +++++++++++++------------ 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4d0269a8e..c22917b40 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2808,21 +2808,25 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo } } } - // template_profile_selected check - template_profile_selected = false; - for (const auto& bp : bundles) { - if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { - for (const auto& preset : appconfig_presets) { - const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); - const Preset* template_material = template_materials.find_preset(preset.first, false); - if (template_material){ - template_profile_selected = true; + // todo: just workaround so template_profile_selected wont get to false after this function is called for SLA + // this will work unltil there are no SLA template filaments + if (technology == ptFFF) { + // template_profile_selected check + template_profile_selected = false; + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + for (const auto& preset : appconfig_presets) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material){ + template_profile_selected = true; + break; + } + } + if (template_profile_selected) { break; } } - if (template_profile_selected) { - break; - } } } assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); @@ -2988,11 +2992,12 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } - - if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { - // Templates vendor needs to be installed - install_bundles.emplace_back(pair.first); + if (vendor == enabled_vendors.end()) { + // vendor not found + // if templates vendor and needs to be installed, add it + // then continue + if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile) + install_bundles.emplace_back(pair.first); continue; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5a2562aab..3c561b91d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -808,6 +808,7 @@ void GUI_App::post_init() // Configuration is not compatible and reconfigure was refused by the user. Application is closing. return; CallAfter([this] { + // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. bool cw_showed = this->config_wizard_startup(); this->preset_updater->sync(preset_bundle); this->app_version_check(false); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 878b819f7..1a720181c 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -920,22 +920,25 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); // find if in cache vendor is newer version than in resources - auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); - auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); - if (vp_cache.config_version > vp_rsrc.config_version) { - // in case we are installing from cache / vendor. we should also copy index to cache - // This needs to be done now bcs the current one would be missing this version on next start - auto path_idx_cache_vendor(path_in_cache_vendor); - path_idx_cache_vendor.replace_extension(".idx"); - auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); - // DK: do this during perform_updates() too? - if (fs::exists(path_idx_cache_vendor)) - copy_file_fix(path_idx_cache_vendor, path_idx_cache); - else // Should we dialog this? - BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); - updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); + if (boost::filesystem::exists(path_in_cache_vendor)) { + auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); + auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); + if (vp_cache.config_version > vp_rsrc.config_version) { + // in case we are installing from cache / vendor. we should also copy index to cache + // This needs to be done now bcs the current one would be missing this version on next start + auto path_idx_cache_vendor(path_in_cache_vendor); + path_idx_cache_vendor.replace_extension(".idx"); + auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); + // DK: do this during perform_updates() too? + if (fs::exists(path_idx_cache_vendor)) + copy_file_fix(path_idx_cache_vendor, path_idx_cache); + else // Should we dialog this? + BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); + updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); - } else + } else + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } else updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); } From e4313399eaa9dedec8b481c1360a3eea3a5ecf2c Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 26 Sep 2022 14:54:26 +0200 Subject: [PATCH 139/206] after rebase changes --- src/slic3r/GUI/PresetComboBoxes.cpp | 6 +++--- src/slic3r/GUI/SavePresetDialog.hpp | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index be34f6c5d..b76e903cd 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -912,7 +912,7 @@ void PlaterPresetComboBox::update() const AppConfig* app_config = wxGetApp().app_config; if (!template_presets.empty() && app_config->get("no_templates") == "0") { set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { Append(it->first, *it->second); validate_selection(it->first == selected_user_preset); } @@ -1100,7 +1100,7 @@ void TabPresetComboBox::update() if (preset.is_default || preset.is_system) { if (preset.vendor && preset.vendor->templates_profile) { - template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (i == idx_selected) selected = get_preset_name(preset); } else { @@ -1125,7 +1125,7 @@ void TabPresetComboBox::update() const AppConfig* app_config = wxGetApp().app_config; if (!template_presets.empty() && app_config->get("no_templates") == "0") { set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { int item_id = Append(it->first, *it->second.first); bool is_enabled = it->second.second; if (!is_enabled) diff --git a/src/slic3r/GUI/SavePresetDialog.hpp b/src/slic3r/GUI/SavePresetDialog.hpp index 0199e27de..3088d457f 100644 --- a/src/slic3r/GUI/SavePresetDialog.hpp +++ b/src/slic3r/GUI/SavePresetDialog.hpp @@ -87,7 +87,6 @@ private: public: -<<<<<<< master const wxString& get_info_line_extention() { return m_info_line_extention; } SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = "", bool template_filament = false); From 548205ffd83f99f38670095901fdeda1de43c67c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 14 Apr 2022 14:35:11 +0200 Subject: [PATCH 140/206] Fixes for downloading bundles and resources: - bed_texture/model may be empty. In that case, do not check for the existence of the file. - In case a vendor is new (=not in resources), it would have crashed when installing any printer from such vendor. The problem was that `install_bundles_rsrc` assumed that the INI is in resources. - several const keywords added - small refactoring - removed commented-out code in AppConfig::profile_archive_url(): the url shall not be customizable --- src/libslic3r/AppConfig.cpp | 5 +-- src/libslic3r/AppConfig.hpp | 2 +- src/libslic3r/Preset.cpp | 26 +++++------- src/libslic3r/Preset.hpp | 2 +- src/slic3r/GUI/ConfigWizard.cpp | 2 +- src/slic3r/Utils/PresetUpdater.cpp | 66 +++++++++++++++++++----------- src/slic3r/Utils/PresetUpdater.hpp | 6 +-- 7 files changed, 61 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 44658e852..7793a2bd4 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -671,11 +671,8 @@ std::string AppConfig::version_check_url() const return from_settings.empty() ? VERSION_CHECK_URL : from_settings; } -std::string AppConfig::profile_archive_url() const +const std::string& AppConfig::profile_archive_url() const { - // Do we want to have settable url? - //auto from_settings = get("profile_archive_url"); - //return from_settings.empty() ? PROFILE_ARCHIVE_URL : from_settings; return PROFILE_ARCHIVE_URL; } diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 219a5ff28..1ecd8cd64 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -140,7 +140,7 @@ public: // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. std::string version_check_url() const; // Get the Slic3r url to vendor profile archive zip. - std::string profile_archive_url() const; + const std::string& profile_archive_url() const; // Returns the original Slic3r version found in the ini file before it was overwritten // by the current version diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 486453bc6..355b44a5d 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2131,25 +2131,21 @@ namespace PresetUtils { return out; } - bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache) + bool vendor_profile_has_all_resources(const VendorProfile& vp) { + namespace fs = boost::filesystem; + std::string vendor_folder = Slic3r::data_dir() + "/vendor/" + vp.id + "/"; std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/"; std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/"; - for (const VendorProfile::PrinterModel& model : vp.models) - { - if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_texture)) - && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_texture)) - && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_texture)))) - return false; - if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_model)) - && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_model)) - && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_model)))) - return false; - if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.id + "_thumbnail.png")) - && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.id + "_thumbnail.png")) - && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.id + "_thumbnail.png")))) - return false; + for (const VendorProfile::PrinterModel& model : vp.models) { + for (const std::string& res : { model.bed_texture, model.bed_model, model.id + "_thumbnail.png" } ) { + if (! res.empty() + && !fs::exists(fs::path(vendor_folder + res)) + && !fs::exists(fs::path(rsrc_folder + res)) + && (fs::exists(fs::path(cache_folder + res)))) + return false; + } } return true; } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 35d4b5e84..234e9a0dc 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -620,7 +620,7 @@ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); std::string system_printer_bed_model(const Preset& preset); std::string system_printer_bed_texture(const Preset& preset); - bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache); + bool vendor_profile_has_all_resources(const VendorProfile& vp); } // namespace PresetUtils diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index c22917b40..49f5426f7 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -3051,7 +3051,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (install_bundles.size() > 0) { // Install bundles from resources or cache / vendor. // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) + if (! updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false)) return false; } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor"; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 1a720181c..940d92c7f 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -157,7 +157,7 @@ struct PresetUpdater::priv priv(); - void set_download_prefs(AppConfig *app_config); + void set_download_prefs(const AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; void sync_config(const VendorMap vendors, const std::string& profile_archive_url); @@ -183,7 +183,7 @@ PresetUpdater::priv::priv() } // Pull relevant preferences from AppConfig -void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) +void PresetUpdater::priv::set_download_prefs(const AppConfig *app_config) { enabled_version_check = app_config->get("notify_release") != "none"; version_check_url = app_config->version_check_url(); @@ -370,9 +370,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string // check the fresh bundle for missing resources // for that, the ini file must be parsed (done above) - auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const std::string& url, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path){ - if (!boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) - && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, + const std::string& url, const fs::path& vendor_path, + const fs::path& rsrc_path, const fs::path& cache_path) + { + if (!fs::exists((vendor_path / (vendor + "/" + filename))) + && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); const auto resource_path = (cache_path / (vendor + "/" + filename)); @@ -382,12 +385,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string } }; for (const auto& model : vp.models) { - check_and_get_resource(vp.id, model.bed_texture, vendor.config_update_url, vendor_path, rsrc_path, cache_path); - if (cancel) { return; } - check_and_get_resource(vp.id, model.bed_model, vendor.config_update_url, vendor_path, rsrc_path, cache_path); - if (cancel) { return; } - check_and_get_resource(vp.id, model.id +"_thumbnail.png", vendor.config_update_url, vendor_path, rsrc_path, cache_path); - if (cancel) { return; } + for (const std::string& res : { model.bed_texture, model.bed_model, model.id +"_thumbnail.png"} ) { + if (! res.empty()) + check_and_get_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) + return; + } } } } @@ -515,7 +518,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version if (new_vp.config_version == recommended->config_version) { // The config bundle from the cache directory matches the recommended version of the index from the cache directory. // This is the newest known recommended config. Use it. - if (PresetUtils::vendor_profile_has_all_resources(new_vp, true)) { + if (PresetUtils::vendor_profile_has_all_resources(new_vp)) { // All resources for the profile in cache dir are existing (either in resources or data_dir/vendor or waiting to be copied to vendor from cache) // This final check is not performed for updates from resources dir below. new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); @@ -523,6 +526,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version bundle_path_idx_to_install = idx.path(); found = true; } else { + // Resource missing - treat as if the INI file was corrupted. throw Slic3r::CriticalException("Some resources are missing."); } @@ -677,11 +681,14 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } - auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path) { - if ( !boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) - && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, + const fs::path& vendor_path, const fs::path& rsrc_path, + const fs::path& cache_path) + { + if ( !fs::exists((vendor_path / (vendor + "/" + filename))) + && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { - if (!boost::filesystem::exists((cache_path / (vendor + "/" + filename)))) { + if (!fs::exists((cache_path / (vendor + "/" + filename)))) { BOOST_LOG_TRIVIAL(error) << "Resources missing in cache directory: " << vendor << " / " << filename; return; } @@ -728,7 +735,7 @@ PresetUpdater::~PresetUpdater() } } -void PresetUpdater::sync(PresetBundle *preset_bundle) +void PresetUpdater::sync(const PresetBundle *preset_bundle) { p->set_download_prefs(GUI::wxGetApp().app_config); if (!p->enabled_version_check && !p->enabled_config_update) { return; } @@ -909,7 +916,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 return R_NOOP; } -bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool snapshot) const +bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector bundles, bool snapshot) const { Updates updates; @@ -919,11 +926,16 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini"); auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); + + bool is_in_rsrc = fs::exists(path_in_rsrc); + bool is_in_cache_vendor = fs::exists(path_in_cache_vendor); + // find if in cache vendor is newer version than in resources - if (boost::filesystem::exists(path_in_cache_vendor)) { + if (is_in_cache_vendor) { auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); - if (vp_cache.config_version > vp_rsrc.config_version) { + + if (! is_in_rsrc || vp_cache.config_version > vp_rsrc.config_version) { // in case we are installing from cache / vendor. we should also copy index to cache // This needs to be done now bcs the current one would be missing this version on next start auto path_idx_cache_vendor(path_in_cache_vendor); @@ -936,10 +948,18 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); - } else - updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); - } else + } else { + if (is_in_rsrc) + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } + } else { + if (! is_in_rsrc) { + // This should not happen. Instead of an assert, make it crash in Release mode too. + BOOST_LOG_TRIVIAL(error) << "Internal error in PresetUpdater! Terminating the application."; + std::terminate(); + } updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } } return p->perform_updates(std::move(updates), snapshot); diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 974a7dcfa..cf17bb685 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -26,7 +26,7 @@ public: ~PresetUpdater(); // If either version check or config updating is enabled, get the appropriate data in the background and cache it. - void sync(PresetBundle *preset_bundle); + void sync(const PresetBundle *preset_bundle); // If version check is enabled, check if chaced online slic3r version is newer, notify if so. void slic3r_update_notify(); @@ -53,8 +53,8 @@ public: // that the config index installed from the Internet is equal to the index contained in the installation package. UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const; - // "Update" a list of bundles from resources (behaves like an online update). - bool install_bundles_rsrc(std::vector bundles, bool snapshot = true) const; + // "Update" a list of bundles from resources or cache/vendor (behaves like an online update). + bool install_bundles_rsrc_or_cache_vendor(std::vector bundles, bool snapshot = true) const; void on_update_notification_confirm(); From 1589d89ca20d4ddf523a75b0d4b821b7e620da9e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 6 Oct 2022 16:28:38 +0200 Subject: [PATCH 141/206] Fixes for template filament profiles: - do not show "Template Filaments" in the list of vendors in wizard - slight refactoring - typos --- src/libslic3r/Preset.cpp | 3 +-- src/slic3r/GUI/ConfigWizard.cpp | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 355b44a5d..951cc7018 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1218,8 +1218,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++idx_preset) { if (m_presets[idx_preset].vendor && !m_presets[idx_preset].vendor->templates_profile && m_presets[idx_preset].is_compatible) { std::string preset_alias = m_presets[idx_preset].alias; - for (size_t idx_in_templates = 0; idx_in_templates < indices_of_template_presets.size(); ++idx_in_templates) { - size_t idx_of_template_in_presets = indices_of_template_presets[idx_in_templates]; + for (size_t idx_of_template_in_presets : indices_of_template_presets) { if (m_presets[idx_of_template_in_presets].alias == preset_alias) { // unselect selected template filament if there is non-template alias compatible if (idx_of_template_in_presets == m_idx_selected && (unselect_if_incompatible == PresetSelectCompatibleType::Always || unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible)) { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 49f5426f7..2ecdca1ea 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1197,7 +1197,7 @@ void PageMaterials::select_material(int i) const std::string& alias_key = list_profile->get_data(i); if (checked && template_shown && !notification_shown) { notification_shown = true; - wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); + wxString message = _L("You have selected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); MessageDialog msg(this, message, _L("Notice"), wxYES_NO); if (msg.ShowModal() == wxID_NO) { list_profile->Check(i, false); @@ -1608,6 +1608,8 @@ PageVendors::PageVendors(ConfigWizard *parent) for (const auto &pair : wizard_p()->bundles) { const VendorProfile *vendor = pair.second.vendor_profile; if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + if (vendor->templates_profile) + continue; auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { From fc65d73c2d8c2c76f9e19277895faa66549d4ec7 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 21 Oct 2022 14:28:05 +0200 Subject: [PATCH 142/206] Wizard and PresetUpdater changes updater: - Sync downloads also missing thumbnails. - Copying of downloaded resources (perform_updates) also downloads missing ones (new vendor or installing vendor with added printers ). - This copy&download shows progress dialog now. - Fix of crash when installing new vendor (not in rsrc dir) Wizard: - Cancel updater sync when starting wizard to avoid multiple downloads. - Load thumbnails from cache dir (downloaded by updater sync). Preset: - Profiles now has settable name of thumbnail. If not specified, name + _thubnail.png is used (as it was before). --- src/libslic3r/Preset.cpp | 6 +- src/libslic3r/Preset.hpp | 1 + src/slic3r/GUI/ConfigWizard.cpp | 26 ++-- src/slic3r/GUI/GUI_App.cpp | 2 + src/slic3r/Utils/PresetUpdater.cpp | 198 +++++++++++++++++++++++------ src/slic3r/Utils/PresetUpdater.hpp | 1 + 6 files changed, 188 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 951cc7018..e9d3b09ff 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -205,6 +205,10 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem } model.bed_model = section.second.get("bed_model", ""); model.bed_texture = section.second.get("bed_texture", ""); + model.thumbnail = section.second.get("thumbnail", ""); + if (model.thumbnail.empty()) + model.thumbnail = model.id + "_thumbnail.png"; + if (! model.id.empty() && ! model.variants.empty()) res.models.push_back(std::move(model)); } @@ -2138,7 +2142,7 @@ namespace PresetUtils { std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/"; std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/"; for (const VendorProfile::PrinterModel& model : vp.models) { - for (const std::string& res : { model.bed_texture, model.bed_model, model.id + "_thumbnail.png" } ) { + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail } ) { if (! res.empty() && !fs::exists(fs::path(vendor_folder + res)) && !fs::exists(fs::path(rsrc_folder + res)) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 234e9a0dc..9a16a16a9 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -53,6 +53,7 @@ public: // Vendor & Printer Model specific print bed model & texture. std::string bed_model; std::string bed_texture; + std::string thumbnail; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2ecdca1ea..2de72ad02 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -239,15 +239,23 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt } return false; }; - if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") - % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") - % vendor.id - % model.id; - load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); + + bool found = false; + for (const std::string& res : { Slic3r::data_dir() + "/vendor/" + vendor.id + "/", Slic3r::resources_dir() + "/profiles/" + vendor.id + "/", Slic3r::data_dir() + "/cache/" + vendor.id + "/" } ) { + if (load_bitmap(GUI::from_u8(res + "/" + model.thumbnail), bitmap, bitmap_width)) { + found = true; + break; } } + + if (!found) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") + % model.thumbnail + % vendor.id + % model.id; + load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); + } + auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); title->SetFont(font_name); const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); @@ -3053,7 +3061,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (install_bundles.size() > 0) { // Install bundles from resources or cache / vendor. // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false)) + + bool install_result = updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false); + if (!install_result) return false; } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor"; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3c561b91d..6975006a6 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2999,6 +2999,8 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); if (reason == ConfigWizard::RR_USER) { + // Cancel sync before starting wizard to prevent two downloads at same time + preset_updater->cancel_sync(); if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) return false; } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 940d92c7f..756fcb43e 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -264,9 +264,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; return; } - if (cancel) { return; } + if (cancel) { + return; + } // Unzip archive to cache / vendor + std::vector vendors_only_in_archive; mz_zip_archive archive; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, archive_path.string())) { @@ -293,22 +296,64 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string file.write(buffer.c_str(), buffer.size()); file.close(); fs::rename(tmp_path, target_path); + + if (name.substr(name.size() - 3) == "ini") + vendors_only_in_archive.push_back(name); } } } close_zip_reader(&archive); } + auto get_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, + const std::string& url, const fs::path& vendor_path, + const fs::path& rsrc_path, const fs::path& cache_path) + { + if (filename.empty() || vendor.empty()) + return; + + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); + + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + return; + } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + return; + } + + BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url + + if (!fs::exists(file_in_cache.parent_path())) + fs::create_directory(file_in_cache.parent_path()); + + self.get_file(resource_url, file_in_cache); + return; + }; + // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { - if (cancel) { return; } + if (cancel) { + return; + } const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { - BOOST_LOG_TRIVIAL(info) << "No such vendor: " << index.vendor(); + // Not installed vendor yet we need to check missing thumbnails (of new printers) + BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor(); continue; } + const VendorProfile &vendor = vendor_it->second; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); @@ -367,32 +412,59 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string if (vp.config_version != recommended) continue; copy_file_fix(path_in_archive, path_in_cache); + // vendors that are checked here, doesnt need to be checked again later + const auto archive_it = std::find(vendors_only_in_archive.begin(), vendors_only_in_archive.end(), index.vendor() + ".ini"); + if (archive_it != vendors_only_in_archive.end()) { + vendors_only_in_archive.erase(archive_it); + } // check the fresh bundle for missing resources // for that, the ini file must be parsed (done above) - auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, - const std::string& url, const fs::path& vendor_path, - const fs::path& rsrc_path, const fs::path& cache_path) - { - if (!fs::exists((vendor_path / (vendor + "/" + filename))) - && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { - BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); - const auto resource_path = (cache_path / (vendor + "/" + filename)); - if (!fs::exists(resource_path.parent_path())) - fs::create_directory(resource_path.parent_path()); - self.get_file(resource_url, resource_path); - } - }; for (const auto& model : vp.models) { - for (const std::string& res : { model.bed_texture, model.bed_model, model.id +"_thumbnail.png"} ) { - if (! res.empty()) - check_and_get_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail/*id +"_thumbnail.png"*/} ) { + if (! res.empty()) { + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_missing_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << res << " for " << vp.id << " " << model.id << ": " << e.what(); + } + + } if (cancel) return; } } } + // Download missing thumbnails for not-installed vendors. + for (const std::string& vendor : vendors_only_in_archive) + { + BOOST_LOG_TRIVIAL(error) << vendor; + const auto path_in_archive = cache_vendor_path / vendor; + assert(boost::filesystem::exists(path_in_archive)); + auto vp = VendorProfile::from_ini(path_in_archive, true); + for (const auto& model : vp.models) { + if (!model.thumbnail.empty()) { + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_missing_resource(vp.id, model.thumbnail, vp.config_update_url, vendor_path, rsrc_path, cache_path); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + + } + } } // Install indicies from resources. Only installs those that are either missing or older than in resources. @@ -643,6 +715,9 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size()); + wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles")); + progress_dialog.Pulse(); + for (const auto &update : updates.updates) { BOOST_LOG_TRIVIAL(info) << '\t' << update; @@ -681,32 +756,71 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } - auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, + auto get_and_copy_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const fs::path& vendor_path, const fs::path& rsrc_path, - const fs::path& cache_path) + const fs::path& cache_path, const std::string& url) { - if ( !fs::exists((vendor_path / (vendor + "/" + filename))) - && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { + if (filename.empty() || vendor.empty()) + return; - if (!fs::exists((cache_path / (vendor + "/" + filename)))) { - BOOST_LOG_TRIVIAL(error) << "Resources missing in cache directory: " << vendor << " / " << filename; - return; - } - if (!fs::exists((vendor_path / vendor))) - fs::create_directory((vendor_path / vendor)); + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); - copy_file_fix((cache_path / (vendor + "/" + filename)), (vendor_path / (vendor + "/" + filename))); + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + return; } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + return; + } + if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir. + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url + + if (!fs::exists(file_in_vendor.parent_path())) + fs::create_directory(file_in_vendor.parent_path()); + + self.get_file(resource_url, file_in_vendor); + return; + } + + if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor + fs::create_directory(file_in_vendor.parent_path()); + + BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor; + copy_file_fix(file_in_cache, file_in_vendor); }; + // check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/ auto vp = VendorProfile::from_ini(update.target, true); + progress_dialog.Update(1, GUI::format_wxstr(_L("Downloading resources for %1%."),vp.id)); + progress_dialog.Pulse(); for (const auto& model : vp.models) { - copy_missing_resource(vp.id, model.bed_texture, vendor_path, rsrc_path, cache_path); - copy_missing_resource(vp.id, model.bed_model, vendor_path, rsrc_path, cache_path); - copy_missing_resource(vp.id, model.id + "_thumbnail.png", vendor_path, rsrc_path, cache_path); + for (const std::string& resource : { model.bed_texture, model.bed_model, model.thumbnail }) { + if (resource.empty()) + continue; + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_and_copy_missing_resource(vp.id, resource, vendor_path, rsrc_path, cache_path, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to prepare " << resource << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } } } + + progress_dialog.Destroy(); } return true; @@ -752,6 +866,16 @@ void PresetUpdater::sync(const PresetBundle *preset_bundle) }); } +void PresetUpdater::cancel_sync() +{ + if (p && p->thread.joinable()) { + // This will stop transfers being done by the thread, if any. + // Cancelling takes some time, but should complete soon enough. + p->cancel = true; + p->thread.join(); + } +} + void PresetUpdater::slic3r_update_notify() { if (! p->enabled_version_check) @@ -932,10 +1056,10 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector vp_rsrc.config_version) { + if (!is_in_rsrc || version_cache > version_rsrc) { // in case we are installing from cache / vendor. we should also copy index to cache // This needs to be done now bcs the current one would be missing this version on next start auto path_idx_cache_vendor(path_in_cache_vendor); diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index cf17bb685..5bcba615b 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -27,6 +27,7 @@ public: // If either version check or config updating is enabled, get the appropriate data in the background and cache it. void sync(const PresetBundle *preset_bundle); + void cancel_sync(); // If version check is enabled, check if chaced online slic3r version is newer, notify if so. void slic3r_update_notify(); From af0e312542dd7ef98ea7957611f7fe9abe1a1fb4 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 18 Jan 2023 10:50:59 +0100 Subject: [PATCH 143/206] Profile updates and installation: - Refactoring and functionality change of PresetUpdater::sync_config. Zip archive now contains only index files. From index file it is decided wheter .ini file should be downloaded and where (cache for update of installed, cache/vendor for unistalled). New vendors are downloaded from set address. Fron .ini file it is decided wheter thumbnail should be downloaded. All resources for already installed vendors are checked and downloaded. - TemplateFilaments renamed to Templates (Warning: This might create duplicities if both files are present!). - Various checks added to prevent crashes when dealing with broken presets, wrong files etc. - Delayed error message when loading present finds duplicities - wait with dialog until Splash screen is gone. - Minor changes in Config wizard when searching & loading printer thumbnails. --- resources/profiles/TemplateFilaments.idx | 2 - resources/profiles/TemplateFilaments.ini | 1410 ----- resources/profiles/Templates.idx | 2 + resources/profiles/Templates.ini | 2144 +++++++ src/libslic3r/AppConfig.cpp | 27 +- src/libslic3r/AppConfig.hpp | 7 +- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PresetBundle.cpp | 15 +- src/slic3r/GUI/ConfigWizard.cpp | 40 +- src/slic3r/GUI/ConfigWizard_private.hpp | 4 +- src/slic3r/GUI/GUI_App.cpp | 6782 +++++++++++----------- src/slic3r/GUI/PresetComboBoxes.cpp | 46 +- src/slic3r/Utils/PresetUpdater.cpp | 613 +- src/slic3r/Utils/PresetUpdater.hpp | 2 + 14 files changed, 6085 insertions(+), 5011 deletions(-) delete mode 100644 resources/profiles/TemplateFilaments.idx delete mode 100644 resources/profiles/TemplateFilaments.ini create mode 100644 resources/profiles/Templates.idx create mode 100644 resources/profiles/Templates.ini diff --git a/resources/profiles/TemplateFilaments.idx b/resources/profiles/TemplateFilaments.idx deleted file mode 100644 index 94223722f..000000000 --- a/resources/profiles/TemplateFilaments.idx +++ /dev/null @@ -1,2 +0,0 @@ -min_slic3r_version = 2.4.0-rc -0.0.1 Initial diff --git a/resources/profiles/TemplateFilaments.ini b/resources/profiles/TemplateFilaments.ini deleted file mode 100644 index 000e25f34..000000000 --- a/resources/profiles/TemplateFilaments.ini +++ /dev/null @@ -1,1410 +0,0 @@ -# Print profiles for the Prusa Research printers. - -[vendor] -# Vendor name will be shown by the Config Wizard. -name = Templates Filaments -# Configuration version of this file. Config file will only be installed, if the config_version differs. -# This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.1 -# Where to get the updates from? -#config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ -# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% -templates_profile = 1 - -## XXX--- Generic filament profiles ---XXX - -[filament:*common*] -cooling = 1 -compatible_printers = -compatible_printers_condition = -end_filament_gcode = "; Filament-specific end gcode" -extrusion_multiplier = 1 -filament_cost = 0 -filament_density = 0 -filament_diameter = 1.75 -filament_notes = "" -filament_settings_id = "" -filament_soluble = 0 -min_print_speed = 15 -slowdown_below_layer_time = 15 -start_filament_gcode = "; Filament gcode" - -[filament:*PLA*] -inherits = *common* -bed_temperature = 60 -bridge_fan_speed = 100 -disable_fan_first_layers = 1 -fan_always_on = 1 -fan_below_layer_time = 100 -filament_colour = #FF8000 -filament_type = PLA -first_layer_bed_temperature = 60 -first_layer_temperature = 215 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 210 - -[filament:*PET*] -inherits = *common* -bed_temperature = 90 -bridge_fan_speed = 50 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -filament_colour = #FF8000 -filament_type = PETG -first_layer_bed_temperature = 85 -first_layer_temperature = 230 -max_fan_speed = 50 -min_fan_speed = 30 -temperature = 240 - -[filament:*ABS*] -inherits = *common* -bed_temperature = 100 -bridge_fan_speed = 25 -cooling = 0 -fan_always_on = 0 -fan_below_layer_time = 20 -filament_colour = #FFF2EC -filament_type = ABS -first_layer_bed_temperature = 100 -first_layer_temperature = 255 -max_fan_speed = 30 -min_fan_speed = 20 -temperature = 255 - -[filament:*ABSC*] -inherits = *common* -bed_temperature = 100 -bridge_fan_speed = 25 -cooling = 1 -disable_fan_first_layers = 4 -fan_always_on = 0 -fan_below_layer_time = 30 -slowdown_below_layer_time = 20 -filament_colour = #FFF2EC -filament_type = ABS -first_layer_bed_temperature = 100 -first_layer_temperature = 255 -max_fan_speed = 15 -min_fan_speed = 15 -min_print_speed = 15 -temperature = 255 - -[filament:*FLEX*] -inherits = *common* -bed_temperature = 50 -bridge_fan_speed = 80 -cooling = 0 -disable_fan_first_layers = 3 -extrusion_multiplier = 1.15 -fan_always_on = 0 -fan_below_layer_time = 100 -filament_colour = #008000 -filament_max_volumetric_speed = 1.5 -filament_type = FLEX -first_layer_bed_temperature = 50 -first_layer_temperature = 240 -max_fan_speed = 90 -min_fan_speed = 70 -temperature = 240 - -[filament:ColorFabb bronzeFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.2 -filament_density = 3.9 -filament_spool_weight = 236 -filament_colour = #804040 - -[filament:ColorFabb steelFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.2 -filament_density = 3.13 -filament_spool_weight = 236 -filament_colour = #808080 - -[filament:ColorFabb copperFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.2 -filament_density = 3.9 -filament_spool_weight = 236 -filament_colour = #82603E - -[filament:ColorFabb HT] -inherits = *PET* -filament_vendor = ColorFabb -bed_temperature = 100 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_density = 1.18 -filament_spool_weight = 236 -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 -temperature = 270 - -[filament:ColorFabb PLA-PHA] -inherits = *PLA* -filament_vendor = ColorFabb -filament_density = 1.24 -filament_spool_weight = 236 - -[filament:ColorFabb woodFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_density = 1.15 -filament_spool_weight = 236 -filament_colour = #dfc287 -filament_max_volumetric_speed = 9 -first_layer_temperature = 200 -temperature = 200 - -[filament:ColorFabb corkFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_density = 1.18 -filament_spool_weight = 236 -filament_colour = #634d33 -first_layer_temperature = 220 -temperature = 220 - -[filament:ColorFabb XT] -inherits = *PET* -filament_vendor = ColorFabb -filament_density = 1.27 -filament_spool_weight = 236 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 - -[filament:ColorFabb XT-CF20] -inherits = *PET* -filament_vendor = ColorFabb -extrusion_multiplier = 1.05 -filament_density = 1.35 -filament_spool_weight = 236 -filament_colour = #804040 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 260 - -[filament:ColorFabb nGen] -inherits = *PET* -filament_vendor = ColorFabb -filament_density = 1.2 -filament_spool_weight = 236 -bridge_fan_speed = 40 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_type = NGEN -first_layer_temperature = 240 -max_fan_speed = 35 -min_fan_speed = 20 - -[filament:ColorFabb nGen flex] -inherits = *FLEX* -filament_vendor = ColorFabb -filament_density = 1 -filament_spool_weight = 236 -bed_temperature = 85 -bridge_fan_speed = 40 -cooling = 1 -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -fan_below_layer_time = 10 -first_layer_bed_temperature = 85 -first_layer_temperature = 260 -max_fan_speed = 35 -min_fan_speed = 20 -temperature = 260 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Kimya PETG Carbon] -inherits = *PET* -filament_vendor = Kimya -extrusion_multiplier = 1.05 -filament_density = 1.317 -filament_colour = #804040 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 240 -filament_retract_length = nil - -[filament:Kimya ABS Carbon] -inherits = *ABSC* -filament_vendor = Kimya -filament_density = 1.032 -filament_colour = #804040 -first_layer_temperature = 260 -temperature = 260 - -[filament:Kimya ABS Kevlar] -inherits = Kimya ABS Carbon -filament_vendor = Kimya -filament_density = 1.037 - -[filament:E3D Edge] -inherits = *PET* -filament_vendor = E3D -filament_density = 1.26 -filament_type = EDGE - -[filament:E3D PC-ABS] -inherits = *ABS* -filament_vendor = E3D -filament_type = PC -filament_density = 1.05 -first_layer_temperature = 270 -temperature = 270 - -[filament:Fillamentum PLA] -inherits = *PLA* -filament_vendor = Fillamentum -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:Fillamentum ABS] -inherits = *ABSC* -filament_vendor = Fillamentum -filament_density = 1.04 -filament_spool_weight = 230 -first_layer_temperature = 240 -temperature = 240 - -[filament:Fillamentum ASA] -inherits = *ABS* -filament_vendor = Fillamentum -filament_density = 1.07 -filament_spool_weight = 230 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -min_print_speed = 15 -slowdown_below_layer_time = 15 -first_layer_temperature = 260 -temperature = 260 -filament_type = ASA - -[filament:Prusament ASA] -inherits = *ABS* -filament_vendor = Prusa Polymers -filament_density = 1.07 -filament_spool_weight = 201 -fan_always_on = 1 -first_layer_temperature = 260 -first_layer_bed_temperature = 105 -temperature = 260 -bed_temperature = 110 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 4 -filament_type = ASA -filament_colour = #FFF2EC - -[filament:Prusament PC Blend] -inherits = *ABS* -filament_vendor = Prusa Polymers -filament_density = 1.22 -filament_spool_weight = 201 -fan_always_on = 0 -first_layer_temperature = 275 -first_layer_bed_temperature = 110 -temperature = 275 -bed_temperature = 115 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 20 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -filament_type = PC -filament_colour = #DEE0E6 - -[filament:Prusament PC Blend Carbon Fiber] -inherits = Prusament PC Blend -filament_density = 1.16 -extrusion_multiplier = 1.04 -first_layer_temperature = 285 -temperature = 285 -disable_fan_first_layers = 4 -fan_below_layer_time = 10 -filament_colour = #BBBBBB - -[filament:Fillamentum CPE] -inherits = *PET* -filament_vendor = Fillamentum -filament_density = 1.25 -filament_spool_weight = 230 -filament_type = CPE -first_layer_bed_temperature = 90 -first_layer_temperature = 275 -min_fan_speed = 30 -max_fan_speed = 50 -disable_fan_first_layers = 3 -full_fan_speed_layer = 5 -temperature = 275 - -[filament:Fillamentum Timberfill] -inherits = *PLA* -filament_vendor = Fillamentum -extrusion_multiplier = 1.05 -filament_density = 1.15 -filament_spool_weight = 230 -filament_colour = #804040 -filament_max_volumetric_speed = 10 -first_layer_temperature = 190 -temperature = 190 - -[filament:Smartfil Wood] -inherits = *PLA* -filament_vendor = Smart Materials 3D -extrusion_multiplier = 1.05 -filament_density = 1.58 -filament_colour = #804040 -first_layer_temperature = 220 -temperature = 220 - -[filament:Generic ABS] -inherits = *ABSC* -filament_vendor = Generic -filament_density = 1.04 - -[filament:Esun ABS] -inherits = *ABSC* -filament_vendor = Esun -filament_density = 1.01 -filament_spool_weight = 265 - -[filament:Hatchbox ABS] -inherits = *ABSC* -filament_vendor = Hatchbox -filament_density = 1.04 -filament_spool_weight = 245 - -[filament:Filament PM ABS] -inherits = *ABSC* -filament_vendor = Filament PM -filament_density = 1.08 -filament_spool_weight = 230 - -[filament:Verbatim ABS] -inherits = *ABSC* -filament_vendor = Verbatim -filament_density = 1.05 -filament_spool_weight = 235 - -[filament:Generic PETG] -inherits = *PET* -filament_vendor = Generic -filament_density = 1.27 - -[filament:Extrudr DuraPro ASA] -inherits = Fillamentum ASA -filament_vendor = Extrudr -bed_temperature = 90 -filament_density = 1.05 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" -first_layer_bed_temperature = 90 -first_layer_temperature = 220 -temperature = 220 -filament_spool_weight = 230 - -[filament:Extrudr PETG] -inherits = *PET* -filament_vendor = Extrudr -bed_temperature = 70 -filament_density = 1.29 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" -first_layer_bed_temperature = 70 -first_layer_temperature = 220 -temperature = 220 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 -full_fan_speed_layer = 0 - -[filament:Extrudr XPETG CF] -inherits = Extrudr PETG -filament_density = 1.41 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" -first_layer_temperature = 235 -temperature = 235 -compatible_printers_condition = nozzle_diameter[0]>=0.4 -filament_spool_weight = 230 - -[filament:Extrudr XPETG Matt] -inherits = Extrudr PETG -filament_density = 1.41 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" -first_layer_temperature = 230 -temperature = 230 - -[filament:Extrudr BioFusion] -inherits = *PLA* -filament_vendor = Extrudr -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_density = 1.25 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" -first_layer_temperature = 220 -temperature = 220 -max_fan_speed = 45 -min_fan_speed = 25 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr Flax] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.45 -filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" -first_layer_temperature = 190 -temperature = 190 -max_fan_speed = 80 -min_fan_speed = 30 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 - -[filament:Extrudr GreenTEC] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=106" -first_layer_temperature = 208 -temperature = 208 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr GreenTEC Pro] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.35 -filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" -temperature = 215 -max_fan_speed = 80 -min_fan_speed = 30 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr GreenTEC Pro Carbon] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.2 -filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" -first_layer_temperature = 225 -max_fan_speed = 80 -min_fan_speed = 30 -temperature = 225 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Extrudr PLA NX1] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.24 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" -temperature = 205 -bed_temperature = 60 -first_layer_temperature = 205 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 0 -max_fan_speed = 90 -min_fan_speed = 30 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 - -[filament:Extrudr PLA NX2] -inherits = Extrudr PLA NX1 -filament_density = 1.3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" - -[filament:Extrudr Flex Hard] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.2 -filament_density = 1.2 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:Extrudr Flex Medium] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.2 -filament_density = 1.19 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:Extrudr Flex SemiSoft] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.2 -filament_density = 1.18 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:addnorth Adamant S1] -inherits = *FLEX* -filament_vendor = addnorth -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -filament_density = 1.22 -temperature = 250 -bed_temperature = 50 -first_layer_temperature = 245 -first_layer_bed_temperature = 50 -slowdown_below_layer_time = 20 -min_print_speed = 20 -fan_below_layer_time = 15 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 40 -max_fan_speed = 70 -bridge_fan_speed = 60 -filament_spool_weight = 0 - -[filament:addnorth Adura X] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -filament_type = NYLON -extrusion_multiplier = 1 -bed_temperature = 60 -first_layer_bed_temperature = 60 -first_layer_temperature = 265 -temperature = 270 -fan_always_on = 0 -min_fan_speed = 20 -max_fan_speed = 40 -bridge_fan_speed = 70 -slowdown_below_layer_time = 10 -min_print_speed = 20 -fan_below_layer_time = 10 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:addnorth E-PLA] -inherits = *PLA* -filament_vendor = addnorth -filament_density = 1.24 -extrusion_multiplier = 1 -temperature = 215 -bed_temperature = 60 -first_layer_temperature = 215 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 15 -filament_spool_weight = 0 - -[filament:addnorth ESD-PETG] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -extrusion_multiplier = 1 -bed_temperature = 80 -first_layer_bed_temperature = 85 -first_layer_temperature = 245 -temperature = 265 -fan_always_on = 1 -min_fan_speed = 15 -max_fan_speed = 30 -bridge_fan_speed = 35 -slowdown_below_layer_time = 10 -min_print_speed = 15 -fan_below_layer_time = 8 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:addnorth OBC Polyethylene] -inherits = *FLEX* -filament_vendor = addnorth -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -filament_density = 1.22 -temperature = 200 -bed_temperature = 100 -first_layer_temperature = 195 -first_layer_bed_temperature = 100 -slowdown_below_layer_time = 5 -fan_below_layer_time = 15 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 30 -bridge_fan_speed = 40 -min_print_speed = 20 -filament_spool_weight = 0 -filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." - -[filament:addnorth PETG] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -bed_temperature = 80 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 250 -fan_always_on = 1 -min_fan_speed = 15 -max_fan_speed = 40 -bridge_fan_speed = 50 -slowdown_below_layer_time = 10 -min_print_speed = 15 -fan_below_layer_time = 15 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:addnorth Rigid X] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -extrusion_multiplier = 1 -bed_temperature = 85 -first_layer_bed_temperature = 90 -first_layer_temperature = 250 -temperature = 260 -fan_always_on = 1 -min_fan_speed = 20 -max_fan_speed = 60 -bridge_fan_speed = 70 -slowdown_below_layer_time = 10 -fan_below_layer_time = 20 -min_print_speed = 20 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 -filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:addnorth Textura] -inherits = *PLA* -filament_vendor = addnorth -filament_density = 1.24 -extrusion_multiplier = 0.95 -temperature = 215 -bed_temperature = 65 -first_layer_temperature = 215 -first_layer_bed_temperature = 65 -min_fan_speed = 20 -max_fan_speed = 40 -bridge_fan_speed = 60 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 15 -min_print_speed = 20 -filament_spool_weight = 0 -filament_retract_length = 1 - -[filament:Filamentworld ABS] -inherits = *ABSC* -filament_vendor = Filamentworld -filament_density = 1.04 -temperature = 230 -bed_temperature = 95 -first_layer_temperature = 240 -first_layer_bed_temperature = 100 -max_fan_speed = 20 -min_fan_speed = 10 -min_print_speed = 20 -disable_fan_first_layers = 3 -fan_below_layer_time = 60 -slowdown_below_layer_time = 15 -bridge_fan_speed = 20 - -[filament:Filamentworld PETG] -inherits = *PET* -filament_vendor = Filamentworld -filament_density = 1.27 -bed_temperature = 70 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 235 -fan_always_on = 1 -min_fan_speed = 25 -max_fan_speed = 55 -bridge_fan_speed = 55 -slowdown_below_layer_time = 20 -min_print_speed = 20 -fan_below_layer_time = 35 -disable_fan_first_layers = 2 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:Filamentworld PLA] -inherits = *PLA* -filament_vendor = Filamentworld -filament_density = 1.24 -temperature = 205 -bed_temperature = 55 -first_layer_temperature = 215 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 10 -filament_spool_weight = 0 -min_print_speed = 20 - -[filament:Filament PM PETG] -inherits = *PET* -filament_vendor = Filament PM -filament_density = 1.27 -filament_spool_weight = 230 - -[filament:Generic PLA] -inherits = *PLA* -filament_vendor = Generic -filament_density = 1.24 - -[filament:Devil Design PLA] -inherits = *PLA* -filament_vendor = Devil Design -filament_density = 1.24 -filament_spool_weight = 250 - -[filament:Devil Design PETG] -inherits = *PET* -filament_vendor = Devil Design -filament_density = 1.23 -filament_spool_weight = 250 -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 - -[filament:Spectrum PLA] -inherits = *PLA* -filament_vendor = Spectrum -filament_density = 1.24 - -[filament:Generic FLEX] -inherits = *FLEX* -filament_vendor = Generic -filament_density = 1.22 -filament_retract_length = 0 -filament_retract_speed = nil -filament_retract_lift = nil - -[filament:Fillamentum Flexfill 92A] -inherits = *FLEX* -filament_vendor = Fillamentum -filament_density = 1.20 -filament_spool_weight = 230 -filament_deretract_speed = 20 -fan_always_on = 1 -cooling = 0 -max_fan_speed = 60 -min_fan_speed = 60 -disable_fan_first_layers = 4 -full_fan_speed_layer = 6 - -[filament:AmazonBasics TPU] -inherits = *FLEX* -filament_vendor = AmazonBasics -fan_always_on = 1 -extrusion_multiplier = 1.1 -first_layer_temperature = 235 -first_layer_bed_temperature = 50 -temperature = 235 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 80 -min_fan_speed = 80 -filament_density = 1.21 -disable_fan_first_layers = 4 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:SainSmart TPU] -inherits = *FLEX* -filament_vendor = SainSmart -fan_always_on = 1 -filament_max_volumetric_speed = 2.5 -extrusion_multiplier = 1.1 -first_layer_temperature = 230 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 80 -min_fan_speed = 80 -filament_density = 1.21 -disable_fan_first_layers = 4 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:Filatech FilaFlex40] -inherits = *FLEX* -filament_vendor = Filatech -fan_always_on = 1 -extrusion_multiplier = 1.1 -first_layer_temperature = 230 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 50 -min_fan_speed = 50 -filament_density = 1.22 -disable_fan_first_layers = 4 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:Filatech FilaFlex30] -inherits = Filatech FilaFlex40 -temperature = 225 -filament_density = 1.15 -extrusion_multiplier = 1.1 - -[filament:Filatech FilaFlex55] -inherits = Filatech FilaFlex40 -temperature = 230 -filament_density = 1.18 -bed_temperature = 60 -fan_always_on = 0 -fan_below_layer_time = 60 -first_layer_temperature = 235 -extrusion_multiplier = 1 - -[filament:Filatech TPE] -inherits = Filatech FilaFlex40 -first_layer_temperature = 230 -temperature = 225 -filament_density = 1.2 -fan_below_layer_time = 60 -max_fan_speed = 80 -min_fan_speed = 80 -fan_always_on = 1 - -[filament:Filatech TPU] -inherits = Filatech FilaFlex40 -first_layer_temperature = 230 -filament_density = 1.2 -fan_below_layer_time = 60 -max_fan_speed = 80 -min_fan_speed = 80 -fan_always_on = 1 -temperature = 235 - -[filament:Filatech ABS] -inherits = *ABSC* -filament_vendor = Filatech -extrusion_multiplier = 0.95 -filament_density = 1.05 - -[filament:Filatech FilaCarbon] -inherits = *ABSC* -filament_vendor = Filatech -extrusion_multiplier = 0.95 -filament_density = 1.1 -first_layer_bed_temperature = 105 -bed_temperature = 100 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Filatech FilaPLA] -inherits = *PLA* -filament_vendor = Filatech -filament_density = 1.3 -first_layer_temperature = 235 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 55 - -[filament:Filatech PLA] -inherits = *PLA* -filament_vendor = Filatech -filament_density = 1.25 -first_layer_temperature = 215 -temperature = 210 - -[filament:Filatech PLA+] -inherits = Filatech PLA -filament_density = 1.24 - -[filament:Filatech FilaTough] -inherits = Filatech ABS -extrusion_multiplier = 0.95 -filament_density = 1.29 -first_layer_temperature = 245 -first_layer_bed_temperature = 80 -temperature = 240 -bed_temperature = 90 -cooling = 0 - -[filament:Filatech HIPS] -inherits = Prusa HIPS -filament_vendor = Filatech -filament_density = 1.07 -filament_spool_weight = -first_layer_temperature = 230 -first_layer_bed_temperature = 100 -temperature = 225 -bed_temperature = 110 - -[filament:Filatech PA] -inherits = *ABSC* -filament_vendor = Filatech -filament_density = 1.1 -first_layer_temperature = 275 -first_layer_bed_temperature = 110 -temperature = 275 -bed_temperature = 115 -fan_always_on = 0 -cooling = 0 -bridge_fan_speed = 25 -filament_type = NYLON - -[filament:Filatech PC] -inherits = Filatech PA -first_layer_bed_temperature = 110 -bed_temperature = 115 -filament_density = 1.2 -filament_type = PC - -[filament:Filatech PC-ABS] -inherits = Filatech PC -first_layer_temperature = 270 -temperature = 270 -first_layer_bed_temperature = 100 -bed_temperature = 100 -filament_density = 1.08 -filament_type = PC -fan_always_on = 0 -cooling = 1 -extrusion_multiplier = 0.95 -disable_fan_first_layers = 6 - -[filament:Filatech PETG] -inherits = *PET* -filament_vendor = Filatech -filament_density = 1.27 -first_layer_temperature = 240 -first_layer_bed_temperature = 75 -temperature = 245 -bed_temperature = 80 -extrusion_multiplier = 0.95 -fan_always_on = 0 - -[filament:Filatech Wood-PLA] -inherits = Filatech PLA -filament_density = 1.05 -first_layer_temperature = 210 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Ultrafuse PET] -inherits = *PET* -filament_vendor = BASF -filament_density = 1.33 -first_layer_temperature = 220 -first_layer_bed_temperature = 70 -temperature = 215 -bed_temperature = 70 -fan_below_layer_time = 10 -min_fan_speed = 75 -max_fan_speed = 100 -bridge_fan_speed = 100 -filament_type = PET -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -filament_notes = "BASF Forward AM Ultrafuse PET\nMaterial profile version 1.0\n\nMaterial Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion.\n" - -[filament:Ultrafuse PRO1] -inherits = Prusament PLA -filament_vendor = BASF -filament_density = 1.25 -filament_spool_weight = 0 -filament_colour = #FFFFFF -filament_notes = "BASF Forward AM Ultrafuse PLA PRO1\nMaterial profile version 1.0\n\nMaterial Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate.\n" - -[filament:Ultrafuse ABS] -inherits = *ABSC* -filament_vendor = BASF -filament_density = 1.04 -min_fan_speed = 10 -max_fan_speed = 20 -bed_temperature = 100 -disable_fan_first_layers = 3 -filament_colour = #FFFFFF -filament_notes = "BASF Forward AM Ultrafuse ABS\nMaterial profile version 1.0\n\nMaterial Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion.\n" - -[filament:Ultrafuse Metal] -inherits = *ABSC* -filament_vendor = BASF -filament_density = 4.5 -extrusion_multiplier = 1.08 -first_layer_temperature = 250 -first_layer_bed_temperature = 100 -temperature = 250 -bed_temperature = 100 -min_fan_speed = 0 -max_fan_speed = 0 -bridge_fan_speed = 0 -cooling = 0 -fan_always_on = 0 -filament_max_volumetric_speed = 4 -filament_type = METAL -compatible_printers_condition = nozzle_diameter[0]>=0.4 -filament_colour = #FFFFFF - -[filament:Polymaker PC-Max] -inherits = *ABS* -filament_vendor = Polymaker -filament_density = 1.20 -filament_type = PC -bed_temperature = 115 -filament_colour = #FFF2EC -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 0 - -[filament:PrimaSelect PVA+] -inherits = *PLA* -filament_vendor = PrimaSelect -filament_density = 1.23 -cooling = 0 -fan_always_on = 0 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = PVA -first_layer_temperature = 195 -temperature = 195 - -[filament:Prusa ABS] -inherits = *ABSC* -filament_vendor = Made for Prusa -filament_density = 1.08 -filament_spool_weight = 230 - -[filament:Prusa HIPS] -inherits = *ABS* -filament_vendor = Made for Prusa -filament_density = 1.04 -filament_spool_weight = 230 -bridge_fan_speed = 50 -cooling = 1 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 10 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = HIPS -first_layer_temperature = 220 -max_fan_speed = 20 -min_fan_speed = 20 -temperature = 220 - -[filament:Generic HIPS] -inherits = *ABS* -filament_vendor = Generic -filament_density = 1.04 -bridge_fan_speed = 50 -cooling = 1 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 10 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = HIPS -first_layer_temperature = 230 -max_fan_speed = 20 -min_fan_speed = 20 -temperature = 230 - -[filament:Prusa PETG] -inherits = *PET* -filament_vendor = Made for Prusa -filament_density = 1.27 -filament_spool_weight = 230 - -[filament:Verbatim PETG] -inherits = *PET* -filament_vendor = Verbatim -filament_density = 1.27 -filament_spool_weight = 235 - -[filament:Fiberlogy PETG] -inherits = *PET* -filament_vendor = Fiberlogy -filament_density = 1.27 - -[filament:Prusament PETG] -inherits = *PET* -filament_vendor = Prusa Polymers -first_layer_temperature = 240 -temperature = 250 -filament_density = 1.27 -filament_spool_weight = 201 -filament_type = PETG - -[filament:Prusa PLA] -inherits = *PLA* -filament_vendor = Made for Prusa -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:Fiberlogy PLA] -inherits = *PLA* -filament_vendor = Fiberlogy -filament_density = 1.24 - -[filament:Filament PM PLA] -inherits = *PLA* -filament_vendor = Filament PM -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:AmazonBasics PLA] -inherits = *PLA* -filament_vendor = AmazonBasics -filament_density = 1.24 - -[filament:Overture PLA] -inherits = *PLA* -filament_vendor = Overture -filament_density = 1.24 -filament_spool_weight = 235 - -[filament:Hatchbox PLA] -inherits = *PLA* -filament_vendor = Hatchbox -filament_density = 1.27 -filament_spool_weight = 245 - -[filament:Esun PLA] -inherits = *PLA* -filament_vendor = Esun -filament_density = 1.24 -filament_spool_weight = 265 - -[filament:Das Filament PLA] -inherits = *PLA* -filament_vendor = Das Filament -filament_density = 1.24 - -[filament:EUMAKERS PLA] -inherits = *PLA* -filament_vendor = EUMAKERS -filament_density = 1.24 - -[filament:Floreon3D PLA] -inherits = *PLA* -filament_vendor = Floreon3D -filament_density = 1.24 - -[filament:Prusament PLA] -inherits = *PLA* -filament_vendor = Prusa Polymers -temperature = 215 -filament_density = 1.24 -filament_spool_weight = 201 -filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" -compatible_printers_condition = nozzle_diameter[0]!=0.8 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) - -[filament:Prusament PVB] -inherits = *PLA* -filament_vendor = Prusa Polymers -temperature = 215 -bed_temperature = 75 -first_layer_bed_temperature = 75 -filament_density = 1.09 -filament_spool_weight = 201 -filament_type = PVB -filament_soluble = 1 -filament_colour = #FFFF6F -slowdown_below_layer_time = 20 - -[filament:Fillamentum Flexfill 98A] -inherits = *FLEX* -filament_vendor = Fillamentum -filament_density = 1.23 -filament_spool_weight = 230 -extrusion_multiplier = 1.1 -filament_max_volumetric_speed = 1.35 -fan_always_on = 1 -cooling = 0 -max_fan_speed = 60 -min_fan_speed = 60 -disable_fan_first_layers = 4 - -[filament:Taulman Bridge] -inherits = *common* -filament_vendor = Taulman -filament_density = 1.13 -bed_temperature = 90 -bridge_fan_speed = 40 -cooling = 0 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -filament_colour = #DEE0E6 -filament_soluble = 0 -filament_type = NYLON -first_layer_bed_temperature = 60 -first_layer_temperature = 240 -max_fan_speed = 0 -min_fan_speed = 0 -temperature = 250 - -[filament:Fillamentum Nylon FX256] -inherits = *common* -filament_vendor = Fillamentum -filament_density = 1.01 -filament_spool_weight = 230 -bed_temperature = 90 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 6 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 20 -filament_colour = #DEE0E6 -filament_max_volumetric_speed = 6 -filament_soluble = 0 -filament_type = NYLON -first_layer_bed_temperature = 90 -first_layer_temperature = 250 -max_fan_speed = 0 -min_fan_speed = 0 -temperature = 250 - -[filament:Fiberthree F3 PA Pure Pro] -inherits = *common* -filament_vendor = Fiberthree -filament_density = 1.2 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 20 -min_fan_speed = 20 - -[filament:Fiberthree F3 PA-CF Pro] -inherits = *common* -filament_vendor = Fiberthree -filament_density = 1.25 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 275 -temperature = 275 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 0 -min_fan_speed = 0 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Fiberthree F3 PA-GF Pro] -inherits = Fiberthree F3 PA-CF Pro -filament_vendor = Fiberthree -filament_density = 1.27 -fan_always_on = 1 -max_fan_speed = 15 -min_fan_speed = 15 - -[filament:Taulman T-Glase] -inherits = *PET* -filament_vendor = Taulman -filament_density = 1.27 -bridge_fan_speed = 40 -cooling = 0 -fan_always_on = 0 -first_layer_bed_temperature = 90 -first_layer_temperature = 240 -max_fan_speed = 5 -min_fan_speed = 0 - -[filament:Verbatim PLA] -inherits = *PLA* -filament_vendor = Verbatim -filament_density = 1.24 -filament_spool_weight = 235 - -[filament:Verbatim BVOH] -inherits = *common* -filament_vendor = Verbatim -filament_density = 1.14 -filament_spool_weight = 235 -bed_temperature = 60 -bridge_fan_speed = 100 -cooling = 0 -disable_fan_first_layers = 1 -extrusion_multiplier = 1 -fan_always_on = 0 -fan_below_layer_time = 100 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = PVA -first_layer_bed_temperature = 60 -first_layer_temperature = 215 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 210 - -[filament:Verbatim PP] -inherits = *common* -filament_vendor = Verbatim -filament_density = 0.89 -filament_spool_weight = 235 -bed_temperature = 100 -bridge_fan_speed = 100 -cooling = 1 -disable_fan_first_layers = 2 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 100 -filament_colour = #DEE0E6 -filament_type = PP -first_layer_bed_temperature = 100 -first_layer_temperature = 220 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 220 - diff --git a/resources/profiles/Templates.idx b/resources/profiles/Templates.idx new file mode 100644 index 000000000..3edb4400f --- /dev/null +++ b/resources/profiles/Templates.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.6.0-alpha0 +1.0.0 Initial diff --git a/resources/profiles/Templates.ini b/resources/profiles/Templates.ini new file mode 100644 index 000000000..6c6ff646c --- /dev/null +++ b/resources/profiles/Templates.ini @@ -0,0 +1,2144 @@ +# Generic filament profile templates + +[vendor] +name = Filaments Templates +config_version = 1.0.0 +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Templates/ +templates_profile = 1 + +## Generic filament profiles + +# Print profiles for the Prusa Research printers. + +[filament:*common*] +cooling = 1 +compatible_printers = +compatible_printers_condition = +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_loading_speed = 14 +filament_loading_speed_start = 19 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_toolchange_delay = 0 +filament_cooling_moves = 1 +filament_cooling_initial_speed = 3 +filament_cooling_final_speed = 2 +filament_load_time = 0 +filament_unload_time = 0 +filament_ramming_parameters = "130 120 2.70968 2.93548 3.32258 3.83871 4.58065 5.54839 6.51613 7.35484 7.93548 8.16129| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" +filament_minimal_purge_on_wipe_tower = 0 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 10 +slowdown_below_layer_time = 10 +start_filament_gcode = + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF8000 +filament_max_volumetric_speed = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:*PET*] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_max_volumetric_speed = 0 +filament_type = PETG +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #FFF2EC +filament_max_volumetric_speed = 0 +filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*ABSC*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 1 +disable_fan_first_layers = 4 +fan_always_on = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +filament_colour = #FFF2EC +filament_max_volumetric_speed = 0 +filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 15 +min_fan_speed = 15 +min_print_speed = 15 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bed_temperature = 50 +bridge_fan_speed = 80 +cooling = 0 +disable_fan_first_layers = 3 +extrusion_multiplier = 1.15 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #008000 +filament_max_volumetric_speed = 1.8 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +temperature = 240 + +[filament:ColorFabb bronzeFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.12 +filament_cost = 80.65 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #804040 + +[filament:ColorFabb steelFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 80.65 +filament_density = 3.13 +filament_spool_weight = 236 +filament_colour = #808080 + +[filament:ColorFabb copperFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 80.65 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #82603E + +[filament:ColorFabb HT] +inherits = *PET* +filament_vendor = ColorFabb +bed_temperature = 100 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_cost = 65.66 +filament_density = 1.18 +filament_spool_weight = 236 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* +filament_vendor = ColorFabb +filament_cost = 54.84 +filament_density = 1.24 +filament_spool_weight = 236 + +[filament:ColorFabb woodFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 78.63 +filament_density = 1.15 +filament_spool_weight = 236 +filament_colour = #dfc287 +first_layer_temperature = 200 +temperature = 200 + +[filament:ColorFabb corkFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 78.63 +filament_density = 1.18 +filament_spool_weight = 236 +filament_colour = #634d33 +filament_max_volumetric_speed = 6 +first_layer_temperature = 220 +temperature = 220 + +[filament:ColorFabb XT] +inherits = *PET* +filament_vendor = ColorFabb +filament_cost = 62.90 +filament_density = 1.27 +filament_spool_weight = 236 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +filament_vendor = ColorFabb +extrusion_multiplier = 1.05 +filament_cost = 80.65 +filament_density = 1.35 +filament_spool_weight = 236 +filament_colour = #804040 +filament_max_volumetric_speed = 2 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +filament_vendor = ColorFabb +filament_cost = 52.46 +filament_density = 1.2 +filament_spool_weight = 236 +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:ColorFabb nGen flex] +inherits = *FLEX* +filament_vendor = ColorFabb +filament_cost = 58.30 +filament_density = 1 +filament_spool_weight = 236 +bed_temperature = 85 +bridge_fan_speed = 40 +cooling = 1 +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +fan_below_layer_time = 10 +filament_max_volumetric_speed = 5 +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +temperature = 260 + +[filament:Kimya PETG Carbon] +inherits = *PET* +filament_vendor = Kimya +extrusion_multiplier = 1.05 +filament_cost = 150.02 +filament_density = 1.317 +filament_colour = #804040 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 240 + +[filament:Kimya ABS Carbon] +inherits = *ABSC* +filament_vendor = Kimya +filament_cost = 140.34 +filament_density = 1.032 +filament_colour = #804040 +first_layer_temperature = 260 +temperature = 260 + +[filament:Kimya ABS Kevlar] +inherits = Kimya ABS Carbon +filament_vendor = Kimya +filament_density = 1.037 + +[filament:E3D Edge] +inherits = *PET* +filament_vendor = E3D +filament_cost = 56.9 +filament_density = 1.26 +filament_type = EDGE + +[filament:E3D PC-ABS] +inherits = *ABS* +filament_vendor = E3D +filament_cost = 0 +filament_type = PC +filament_density = 1.05 +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum PLA] +inherits = *PLA* +filament_vendor = Fillamentum +filament_cost = 35.48 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Fillamentum ABS] +inherits = *ABSC* +filament_vendor = Fillamentum +filament_cost = 32.4 +filament_density = 1.04 +filament_spool_weight = 230 +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +filament_vendor = Fillamentum +filament_cost = 38.7 +filament_density = 1.07 +filament_spool_weight = 230 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +filament_type = ASA + +[filament:Prusament ASA] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_cost = 42.69 +filament_density = 1.07 +filament_spool_weight = 201 +fan_always_on = 1 +first_layer_temperature = 260 +first_layer_bed_temperature = 100 +temperature = 260 +bed_temperature = 100 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 4 +filament_type = ASA +filament_colour = #FFF2EC + +[filament:Prusament PC Blend] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_cost = 62.36 +filament_density = 1.22 +filament_spool_weight = 201 +fan_always_on = 0 +first_layer_temperature = 275 +first_layer_bed_temperature = 105 +temperature = 275 +bed_temperature = 105 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 20 +disable_fan_first_layers = 4 +fan_below_layer_time = 30 +filament_type = PC +filament_colour = #DEE0E6 + +[filament:Prusament PC Blend Carbon Fiber] +inherits = Prusament PC Blend +filament_cost = 90.73 +filament_density = 1.16 +extrusion_multiplier = 1.04 +first_layer_temperature = 285 +temperature = 285 +disable_fan_first_layers = 4 +fan_below_layer_time = 10 +filament_colour = #BBBBBB + +[filament:Prusament PA11 Carbon Fiber] +inherits = Prusament PC Blend Carbon Fiber +filament_cost = 151.24 +filament_density = 1.11 +filament_type = PA +extrusion_multiplier = 1.05 +first_layer_temperature = 275 +temperature = 285 +first_layer_bed_temperature = 90 +bed_temperature = 105 +fan_below_layer_time = 10 + +[filament:Fillamentum CPE] +inherits = *PET* +filament_vendor = Fillamentum +filament_cost = 56.45 +filament_density = 1.25 +filament_spool_weight = 230 +filament_type = CPE +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +min_fan_speed = 30 +max_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +temperature = 275 + +[filament:Fillamentum Timberfill] +inherits = *PLA* +filament_vendor = Fillamentum +extrusion_multiplier = 1.05 +filament_cost = 68 +filament_density = 1.15 +filament_spool_weight = 230 +filament_colour = #804040 +first_layer_temperature = 190 +temperature = 190 +filament_retract_lift = 0.2 + +[filament:Smartfil Wood] +inherits = *PLA* +filament_vendor = Smart Materials 3D +extrusion_multiplier = 1.05 +filament_cost = 68 +filament_density = 1.58 +filament_colour = #804040 +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic ABS] +inherits = *ABSC* +filament_vendor = Generic +filament_cost = 27.82 +filament_density = 1.04 + +[filament:Esun ABS] +inherits = *ABSC* +filament_vendor = Esun +filament_cost = 27.82 +filament_density = 1.01 +filament_spool_weight = 265 + +[filament:Hatchbox ABS] +inherits = *ABSC* +filament_vendor = Hatchbox +filament_cost = 27.82 +filament_density = 1.04 +filament_spool_weight = 245 + +[filament:Filament PM ABS] +inherits = *ABSC* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Verbatim ABS] +inherits = *ABSC* +filament_vendor = Verbatim +filament_cost = 25.87 +filament_density = 1.05 +filament_spool_weight = 235 + +[filament:Generic PETG] +inherits = *PET* +filament_vendor = Generic +filament_cost = 27.82 +filament_density = 1.27 + +[filament:Extrudr DuraPro ASA] +inherits = Fillamentum ASA +filament_vendor = Extrudr +bed_temperature = 90 +filament_cost = 34.64 +filament_density = 1.05 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" +first_layer_bed_temperature = 90 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 230 + +[filament:Extrudr PETG] +inherits = *PET* +filament_vendor = Extrudr +bed_temperature = 70 +filament_cost = 35.45 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" +first_layer_bed_temperature = 70 +first_layer_temperature = 220 +temperature = 220 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 +full_fan_speed_layer = 0 + +[filament:Extrudr XPETG CF] +inherits = Extrudr PETG +filament_cost = 62.49 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" +first_layer_temperature = 235 +temperature = 235 +filament_spool_weight = 230 + +[filament:Extrudr XPETG Matt] +inherits = Extrudr PETG +filament_cost = 29.99 +filament_density = 1.41 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" +first_layer_temperature = 230 +temperature = 230 + +[filament:Extrudr BioFusion] +inherits = *PLA* +filament_vendor = Extrudr +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_cost = 31.23 +filament_density = 1.25 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" +first_layer_temperature = 220 +temperature = 220 +max_fan_speed = 45 +min_fan_speed = 25 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr Flax] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 50.91 +filament_density = 1.45 +filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" +first_layer_temperature = 190 +temperature = 190 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 50.91 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?ignorechildren=1&material=106" +first_layer_temperature = 208 +temperature = 208 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC Pro] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 56.23 +filament_density = 1.35 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" +temperature = 215 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr GreenTEC Pro Carbon] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 62.49 +filament_density = 1.2 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" +first_layer_temperature = 225 +max_fan_speed = 80 +min_fan_speed = 30 +temperature = 225 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr PLA NX1] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 22.76 +filament_density = 1.24 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 0 +max_fan_speed = 90 +min_fan_speed = 30 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr PLA NX2] +inherits = Extrudr PLA NX1 +filament_cost = 23.63 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" + +[filament:Extrudr Flex Hard] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.2 +filament_max_volumetric_speed = 3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex Medium] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.19 +filament_max_volumetric_speed = 3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=117" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex SemiSoft] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.18 +filament_max_volumetric_speed = 1.8 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=116" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:addnorth Adamant S1] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_cost = +filament_density = 1.22 +temperature = 250 +bed_temperature = 50 +first_layer_temperature = 245 +first_layer_bed_temperature = 50 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 40 +max_fan_speed = 70 +bridge_fan_speed = 60 +filament_max_volumetric_speed = 1.7 + +[filament:addnorth Adura X] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +filament_type = PA +extrusion_multiplier = 0.98 +bed_temperature = 105 +first_layer_bed_temperature = 105 +first_layer_temperature = 265 +temperature = 270 +fan_always_on = 0 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +min_print_speed = 20 +fan_below_layer_time = 10 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 + +[filament:addnorth E-PLA] +inherits = *PLA* +filament_vendor = addnorth +filament_cost = 24.99 +filament_density = 1.24 +extrusion_multiplier = 0.98 +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_spool_weight = 0 + +[filament:addnorth ESD-PETG] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 245 +temperature = 265 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 35 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 8 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 + +[filament:addnorth OBC Polyethylene] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_cost = 82 +filament_density = 1.22 +temperature = 200 +bed_temperature = 100 +first_layer_temperature = 195 +first_layer_bed_temperature = 100 +slowdown_below_layer_time = 5 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 30 +bridge_fan_speed = 40 +min_print_speed = 20 +filament_spool_weight = 0 +filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." + +[filament:addnorth PETG] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 250 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 40 +bridge_fan_speed = 50 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 15 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:addnorth Rigid X] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 85 +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +temperature = 260 +fan_always_on = 1 +min_fan_speed = 20 +max_fan_speed = 60 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +fan_below_layer_time = 20 +min_print_speed = 20 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 +filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." + +[filament:addnorth Textura] +inherits = *PLA* +filament_vendor = addnorth +filament_cost = 24.99 +filament_density = 1.24 +extrusion_multiplier = 0.95 +temperature = 215 +bed_temperature = 65 +first_layer_temperature = 215 +first_layer_bed_temperature = 65 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 60 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 15 +min_print_speed = 20 +filament_spool_weight = 0 + +[filament:Filamentworld ABS] +inherits = *ABSC* +filament_vendor = Filamentworld +filament_cost = 24.9 +filament_density = 1.04 +temperature = 230 +bed_temperature = 95 +first_layer_temperature = 240 +first_layer_bed_temperature = 100 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 20 +disable_fan_first_layers = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 15 +bridge_fan_speed = 20 + +[filament:Filamentworld PETG] +inherits = *PET* +filament_vendor = Filamentworld +filament_cost = 34.9 +filament_density = 1.27 +bed_temperature = 70 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 235 +fan_always_on = 1 +min_fan_speed = 25 +max_fan_speed = 55 +bridge_fan_speed = 55 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 35 +disable_fan_first_layers = 2 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:Filamentworld PLA] +inherits = *PLA* +filament_vendor = Filamentworld +filament_cost = 24.9 +filament_density = 1.24 +temperature = 205 +bed_temperature = 55 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 10 +filament_spool_weight = 0 +min_print_speed = 20 + +[filament:Filament PM PETG] +inherits = *PET* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Generic PLA] +inherits = *PLA* +filament_vendor = Generic +filament_cost = 25.4 +filament_density = 1.24 + +[filament:3D-Fuel Standard PLA] +inherits = *PLA* +filament_vendor = 3D-Fuel +filament_cost = 22.14 +filament_density = 1.24 +first_layer_temperature = 210 +temperature = 200 + +[filament:3D-Fuel EasiPrint PLA] +inherits = 3D-Fuel Standard PLA +filament_cost = 30.44 + +[filament:3D-Fuel Pro PLA] +inherits = *PLA* +filament_vendor = 3D-Fuel +filament_cost = 26.57 +filament_density = 1.22 +first_layer_temperature = 220 +temperature = 215 + +[filament:3D-Fuel Buzzed] +inherits = 3D-Fuel Standard PLA +filament_cost = 44.27 +filament_retract_lift = 0 +first_layer_temperature = 210 +temperature = 195 + +[filament:3D-Fuel Wound up] +inherits = 3D-Fuel Buzzed +filament_cost = 44.27 +filament_retract_lift = nil +first_layer_temperature = 215 +temperature = 210 + +[filament:3D-Fuel Workday ABS] +inherits = *ABSC* +filament_vendor = 3D-Fuel +filament_cost = 23.25 +filament_density = 1.04 + +[filament:Jessie PLA] +inherits = *PLA* +filament_vendor = Printed Solid +filament_cost = 21 +filament_density = 1.24 + +[filament:Jessie PETG] +inherits = *PET* +filament_vendor = Printed Solid +filament_cost = 22 +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 85 +temperature = 245 +bed_temperature = 90 + +[filament:Devil Design PLA] +inherits = *PLA* +filament_vendor = Devil Design +filament_cost = 20.99 +filament_density = 1.24 +filament_spool_weight = 250 + +[filament:Devil Design PETG] +inherits = *PET* +filament_vendor = Devil Design +filament_cost = 20.99 +filament_density = 1.23 +filament_spool_weight = 250 +first_layer_temperature = 230 +first_layer_bed_temperature = 85 +temperature = 230 +bed_temperature = 90 + +[filament:Spectrum PLA] +inherits = *PLA* +filament_vendor = Spectrum +filament_cost = 21.50 +filament_density = 1.24 + +[filament:Generic FLEX] +inherits = *FLEX* +filament_vendor = Generic +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil + +[filament:Fillamentum Flexfill 92A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_cost = 33.99 +filament_density = 1.20 +filament_spool_weight = 230 +filament_max_volumetric_speed = 1.2 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:AmazonBasics TPU] +inherits = *FLEX* +filament_vendor = AmazonBasics +fan_always_on = 1 +filament_max_volumetric_speed = 1.8 +extrusion_multiplier = 1.14 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 235 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_retract_before_travel = 3 +filament_cost = 19.99 +filament_density = 1.21 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:SainSmart TPU] +inherits = *FLEX* +filament_vendor = SainSmart +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.05 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_retract_before_travel = 3 +filament_cost = 32.99 +filament_density = 1.21 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:NinjaTek NinjaFlex TPU] +inherits = *FLEX* +filament_vendor = NinjaTek +fan_always_on = 1 +filament_max_volumetric_speed = 1.2 +extrusion_multiplier = 1.15 +first_layer_temperature = 238 +first_layer_bed_temperature = 50 +temperature = 238 +bed_temperature = 50 +bridge_fan_speed = 75 +max_fan_speed = 60 +min_fan_speed = 60 +filament_cost = 85 +filament_density = 1.19 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +min_print_speed = 10 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:NinjaTek Cheetah TPU] +inherits = NinjaTek NinjaFlex TPU +filament_retract_length = 1.5 +filament_density = 1.22 +filament_max_volumetric_speed = 4 +extrusion_multiplier = 1.05 +filament_retract_speed = 45 +filament_deretract_speed = 25 +first_layer_temperature = 240 +temperature = 240 + +[filament:Filatech FilaFlex40] +inherits = *FLEX* +filament_vendor = Filatech +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.1 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 50 +min_fan_speed = 50 +filament_cost = 84.68 +filament_density = 1.22 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:Filatech FilaFlex30] +inherits = Filatech FilaFlex40 +temperature = 225 +filament_density = 1.15 +extrusion_multiplier = 1.1 +filament_cost = + +[filament:Filatech FilaFlex55] +inherits = Filatech FilaFlex40 +temperature = 230 +filament_density = 1.18 +bed_temperature = 60 +fan_always_on = 0 +fan_below_layer_time = 60 +filament_cost = +first_layer_temperature = 235 +extrusion_multiplier = 1 + +[filament:Filatech TPU] +inherits = Filatech FilaFlex40 +first_layer_temperature = 230 +filament_density = 1.2 +fan_below_layer_time = 60 +max_fan_speed = 80 +min_fan_speed = 80 +fan_always_on = 1 +temperature = 235 + +[filament:Filatech ABS] +inherits = *ABSC* +filament_vendor = Filatech +filament_cost = +extrusion_multiplier = 1 +filament_density = 1.05 + +[filament:Filatech FilaCarbon] +inherits = *ABSC* +filament_vendor = Filatech +filament_cost = +extrusion_multiplier = 0.95 +filament_density = 1.1 +first_layer_bed_temperature = 100 +bed_temperature = 100 + +[filament:Filatech FilaPLA] +inherits = *PLA* +filament_vendor = Filatech +filament_cost = +filament_density = 1.3 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 55 + +[filament:Filatech PLA] +inherits = *PLA* +filament_vendor = Filatech +filament_cost = +filament_density = 1.25 +first_layer_temperature = 215 +temperature = 210 + +[filament:Filatech PLA+] +inherits = Filatech PLA +filament_density = 1.24 + +[filament:Filatech FilaTough] +inherits = Filatech ABS +filament_cost = +extrusion_multiplier = 0.95 +filament_density = 1.29 +first_layer_temperature = 245 +first_layer_bed_temperature = 80 +temperature = 240 +bed_temperature = 90 +cooling = 0 + +[filament:Filatech HIPS] +inherits = Prusa HIPS +filament_vendor = Filatech +filament_density = 1.07 +filament_spool_weight = +first_layer_temperature = 230 +first_layer_bed_temperature = 100 +temperature = 225 +bed_temperature = 100 + +[filament:Filatech PA] +inherits = *ABSC* +filament_vendor = Filatech +filament_density = 1.1 +first_layer_temperature = 275 +first_layer_bed_temperature = 105 +temperature = 275 +bed_temperature = 105 +fan_always_on = 0 +cooling = 0 +bridge_fan_speed = 25 +filament_type = PA + +[filament:Filatech PC] +inherits = Filatech PA +filament_density = 1.2 +filament_type = PC + +[filament:Filatech PC-ABS] +inherits = Filatech PC +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_density = 1.08 +filament_type = PC +fan_always_on = 0 +cooling = 1 +extrusion_multiplier = 0.95 +disable_fan_first_layers = 6 + +[filament:Filatech PETG] +inherits = *PET* +filament_vendor = Filatech +filament_cost = +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 75 +temperature = 245 +bed_temperature = 80 +extrusion_multiplier = 0.95 +fan_always_on = 0 + +[filament:Filatech Wood-PLA] +inherits = Filatech PLA +filament_density = 1.05 +first_layer_temperature = 210 + +[filament:Ultrafuse PET] +inherits = *PET* +filament_vendor = BASF +filament_density = 1.33 +filament_colour = #F7F7F7 +first_layer_temperature = 220 +first_layer_bed_temperature = 70 +temperature = 215 +bed_temperature = 70 +fan_below_layer_time = 10 +min_fan_speed = 75 +max_fan_speed = 100 +bridge_fan_speed = 100 +filament_type = PET +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +filament_notes = "Material Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PRO1] +inherits = Prusament PLA +filament_vendor = BASF +filament_cost = +filament_density = 1.25 +filament_spool_weight = 0 +filament_colour = #FFFFFF +filament_notes = "Material Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate." + +[filament:Ultrafuse ABS] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 1.04 +min_fan_speed = 10 +max_fan_speed = 20 +bed_temperature = 100 +disable_fan_first_layers = 3 +filament_colour = #FFFFFF +filament_notes = "Material Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion." + +[filament:Ultrafuse ABS Fusion+] +inherits = Ultrafuse ABS +filament_density = 1.08 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +filament_colour = #FFF8D9 +filament_notes = "Material Description\nABS Fusion+ made with Polyscope XILOY™ 3D is an engineering filament which has been optimized for 3D-printing. This special grade has been developed in collaboration with Polyscope Polymers - renowned for its material solutions in the automotive industry. ABS is a thermoplastic which is used in many applications. Although ABS has been classified as a standard material in 3D-printing it is known to be quite challenging to process. ABS Fusion+ combines the properties of ABS with an improved processability. The filament is based on an ABS grade which can be directly printed on glass without any adhesives or tape and has a higher success rate of prints due to extreme low warping." + +[filament:Ultrafuse ASA] +inherits = Ultrafuse ABS Fusion+ +filament_density = 1.07 +filament_colour = #FFF4CA +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ASA +min_fan_speed = 25 +max_fan_speed = 50 +bridge_fan_speed = 100 +disable_fan_first_layers = 4 +filament_notes = "Material Description\nUltrafuse ASA is a high-performance thermoplastic with similar mechanical properties as ABS. ASA offers additional benefits such as high outdoor weather resistance. The UV resistance, toughness, and rigidity make it an ideal material to 3D-print outdoor fixtures and appliances without losing its properties or color. When also taking into account the high heat resistance and high chemical resistance, this filament is a good choice for many types of applications.\n\nPrinting Recommendations:\nApply Magigoo PC, 3D lac or Dimafix to a clean build plate to improve adhesion." + +[filament:Ultrafuse HIPS] +inherits = Ultrafuse ABS +temperature = 250 +filament_density = 1.02 +filament_type = HIPS +min_fan_speed = 20 +max_fan_speed = 20 +filament_soluble = 1 +filament_notes = "Material Description\nUltrafuse HIPS is a high-quality engineering thermoplastic, which is well known in the 3D-printing industry as a support material for ABS. But this material has additional properties to offer like good impact resistance, good dimensional stability, and easy post-processing. HiPS is a great material to use as a support for ABS because there is a good compatibility between the two materials, and HIPS is an easy breakaway support. Now you have the opportunity to create ABS models with complex geometry. HIPS is easy to post process with glue or with sanding paper." + +[filament:Ultrafuse PA] +inherits = Fillamentum Nylon FX256 +filament_vendor = BASF +filament_density = 1.12 +filament_colour = #ECFAFF +first_layer_temperature = 240 +temperature = 240 +first_layer_bed_temperature = 80 +bed_temperature = 70 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +min_print_speed = 15 +filament_notes = "Material Description\nThe key features of Ultrafuse PA are the high strength and high modulus. Furthermore, Ultrafuse PA shows a good thermal distortion stability.\n\nPrinting Recommendations:\nApply PVA glue, Kapton tape or PA adhesive to a clean buildplate to improve adhesion." + +[filament:Ultrafuse PA6 GF30] +inherits = Ultrafuse PA +filament_density = 1.17 +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_colour = #404040 +fan_always_on = 1 +min_fan_speed = 0 +max_fan_speed = 50 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_notes = "Material Description\nUltrafuse® PA6 GF30 is a unique compound specifically developed for FFF printing. Due to the glass fiber content of 30%, parts tend to warp less. In addition the excellent layer adhesion and its compatibility with the water soluble support Ultrafuse® BVOH make this material the perfect solution to develop industrial applications on an FFF printer.\n\nWith its high wear and chemical resistance, high stiffness and strength, Ultrafuse® PA6 GF30 is perfect for a wide variety of applications in automotive, electronics or transportation.\n\nUltrafuse PA6 GF30 is designed for functional prototyping and demanding applications such as industrial tooling, transportation, electronics, small appliances, sports & leisure\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PA6 GF30 can be printed directly onto a clean build plate. For challenging prints, use Magigoo PA gluestick to improve adhesion." + +[filament:Ultrafuse PAHT-CF15] +inherits = Ultrafuse PA6 GF30 +filament_density = 1.23 +filament_notes = "Material Description\nPAHT CF15 is a high-performance 3D printing filament that opens new application fields in FFF printing. In parallel to its advanced mechanical properties, dimensional stability, and chemical resistance, it has very good processability. It works in any FFF printer with a hardened nozzle. In addition to that, it is compatible with water-soluble support material and HiPS, which allow printing complex geometries that work in challenging environments. PAHT CF15 has high heat resistance up to 130 °C and low moisture absorption.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PAHT-CF can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PC-ABS-FR] +inherits = Ultrafuse ABS +filament_colour = #505050 +filament_density = 1.17 +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_type = PC +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +disable_fan_first_layers = 4 +filament_notes = "Material Description\nUltrafuse® PC/ABS FR Black is a V-0 flame retardant blend of Polycarbonate and ABS – two of the most used thermoplastics for engineering & electrical applications. The combination of these two materials results in a premium material with a mix of the excellent mechanical properties of PC and the comparably low printing temperature of ABS. Combined with a halogen free flame retardant, parts printed with Ultrafuse® PC/ABS FR Black feature great tensile and impact strength, higher thermal resistance than ABS and can fulfill the requirements of the UL94 V-0 standard.\n\nPrinting Recommendations:\nApply Magigoo PC to a clean build plate to improve adhesion." + +[filament:Ultrafuse PET-CF15] +inherits = Ultrafuse PET +filament_density = 1.36 +filament_colour = #404040 +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 75 +bed_temperature = 75 +min_fan_speed = 60 +max_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +fan_below_layer_time = 30 +filament_notes = "Material Description\nPET CF15 is a Carbon Fiber reinforced PET which has precisely tuned material properties, for a wide range of technical applications. The filament is very strong and stiff and has high heat resistance. With its high dimensional stability and low abrasiveness, the filament offers an easy to print experience which allows direct printing on glass or a PEI sheet. It is compatible with HiPS for breakaway support and water soluble support and has an excellent surface finish.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PET-CF15 can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PLA] +inherits = *PLA* +filament_vendor = BASF +filament_density = 1.25 +full_fan_speed_layer = 3 +filament_notes = "Material Description\nPLA is one of the most used materials for 3D printing. Ultrafuse PLA is available in a wide range of colors. The glossy feel often attracts those who print display models or items for household use. Many appreciate the plant-based origin of this material. When properly cooled, PLA has a high maximum printing speed and sharp printed corners. Combining this with low warping of the print makes it a popular plastic for home printers, hobbyists, prototyping and schools.\n\nPrinting Recommendations:\nUltrafuse PLA can be printed directly onto a clean build plate." + +[filament:Ultrafuse PP] +inherits = Ultrafuse ABS +filament_density = 0.91 +filament_colour = #F0F0F0 +first_layer_temperature = 240 +temperature = 240 +first_layer_bed_temperature = 80 +bed_temperature = 70 +min_fan_speed = 100 +max_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 20 +min_print_speed = 10 +filament_type = PP +filament_max_volumetric_speed = 2.5 +filament_notes = "Material Description\nUltrafuse PP is high-performance thermoplastic with low density, high elasticity and high resistance to fatigue. The mechanical properties make it an ideal material for 3D-printing applications which have to endure high stress or strain. The filament has high chemical resistance and a high isolation value. PP is one of the most used materials in the world, due to its versatility and ability to engineer lightweight tough parts.\n\nPrinting Recommendations:\nApply PP tape or Magigoo PP adhesive to the buildplate for optimal adhesion." + +[filament:Ultrafuse PP-GF30] +inherits = Ultrafuse PP +filament_density = 1.07 +filament_colour = #404040 +first_layer_temperature = 260 +temperature = 250 +first_layer_bed_temperature = 90 +bed_temperature = 40 +min_fan_speed = 40 +max_fan_speed = 75 +fan_always_on = 1 +fan_below_layer_time = 30 +slowdown_below_layer_time = 15 +min_print_speed = 15 +filament_notes = "Ultrafuse PP GF30 is polypropylene, reinforced with 30% glass fiber content. The fibers in this compound are specially designed for 3D-printing filaments and are compatible with a wide range of standard FFF 3D-printers. The extreme stiffness makes this material highly suitable for demanding applications. Other key properties of PPGF30 are high heat resistance and improved UV-resistance. All these excellent properties make this filament highly suitable in an industrial environment.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nApply PP strapping tape or PPGF adhesive to a clean build plate for optimal adhesion." + +[filament:Ultrafuse TPC-45D] +inherits = *FLEX* +filament_vendor = BASF +extrusion_multiplier = 1 +filament_density = 1.15 +filament_colour = #0035EC +first_layer_temperature = 235 +temperature = 235 +first_layer_bed_temperature = 60 +bed_temperature = 60 +min_fan_speed = 10 +max_fan_speed = 50 +bridge_fan_speed = 80 +fan_below_layer_time = 30 +slowdown_below_layer_time = 15 +min_print_speed = 15 +fan_always_on = 1 +cooling = 1 +filament_max_volumetric_speed = 1.2 +filament_notes = "Material Description\nTPC 45D is a flexible, shore 45D, rubber-like Thermoplastic Copolyester Elastomer (TPE-C), which is derived from rapeseed oil and combines the best properties of elastomers (rubbers) and polyesters. The material delivers excellent adhesion in the Z-direction, meaning that the printed layers do not detach - even with extreme deformation.\n\nPrinting Recommendations:\nApply Magigoo Flex to a clean build plate to improve adhesion." + +[filament:Ultrafuse TPU-64D] +inherits = Ultrafuse TPC-45D +filament_density = 1.16 +first_layer_temperature = 230 +temperature = 225 +first_layer_bed_temperature = 40 +bed_temperature = 40 +min_fan_speed = 20 +max_fan_speed = 100 +filament_notes = "Material Description\nUltrafuse® TPU 64D is the hardest elastomer in BASF Forward AM’s flexible productline. The material shows a relatively high rigidity while maintaining a certain flexibility. This filament is the perfect match for industrial applications requiring rigid parts being resistant to impact, wear and tear. Due to its property profile, the material can be used as an alternative for parts made from ABS and rubbers. Ultrafuse® TPU 64D is easy to print on direct drive and bowden style printers and is compatible with soluble BVOH support to realize the most complex geometries.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse TPU-85A] +inherits = Ultrafuse TPU-64D +filament_density = 1.11 +first_layer_temperature = 225 +temperature = 220 +filament_notes = "Material Description\nUltrafuse® TPU 85A comes in its natural white color. Chemical properties (e.g. resistance against particular substances) and tolerance for solvents can be made available, if these factors are relevant for a specific application. Generally, these properties correspond to publicly available data on polyether based TPUs. This material is not FDA conform. Good flexibility at low temperature, good wear performance and good damping behavior are the key features of Ultrafuse® TPU 85A.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse TPU-95A] +inherits = Ultrafuse TPU-85A +filament_density = 1.14 +first_layer_temperature = 230 +temperature = 225 +filament_notes = "Material Description\nUltrafuse® TPU 95A comes with a well-balanced profile of flexibility and durability. On top of that, it allows for easier and faster printing then softer TPU grades. Parts printed with Ultrafuse® TPU 95A show a high elongation, good impact resistance, excellent layer adhesion and a good resistance to oils and common industrially used chemicals. Due to its good printing behavior, Ultrafuse® TPU 95A is a good choice for starting printing flexible materials on both direct drive and bowden style printers.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse rPET] +inherits = Ultrafuse PET +filament_density = 1.27 +filament_colour = #9DC5FF +first_layer_temperature = 235 +temperature = 235 +first_layer_bed_temperature = 80 +bed_temperature = 75 +min_fan_speed = 50 +max_fan_speed = 100 +fan_below_layer_time = 15 +filament_notes = "Material Description\nPET is mainly known by the well-known PET bottle material. This recycled has a natural transparent blueish look. It has excellent 3D printing properties and good mechanical characteristics." + +[filament:Ultrafuse Metal] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 4.5 +extrusion_multiplier = 1.08 +first_layer_temperature = 250 +first_layer_bed_temperature = 100 +temperature = 250 +bed_temperature = 100 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +cooling = 0 +fan_always_on = 0 +filament_max_volumetric_speed = 4 +filament_type = METAL +compatible_printers_condition = nozzle_diameter[0]>=0.4 +filament_colour = #FFFFFF + +[filament:Polymaker PC-Max] +inherits = *ABS* +filament_vendor = Polymaker +filament_cost = 77.3 +filament_density = 1.20 +filament_type = PC +bed_temperature = 115 +filament_colour = #FFF2EC +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +bridge_fan_speed = 0 + +[filament:PrimaSelect PVA+] +inherits = *PLA* +filament_vendor = PrimaSelect +filament_cost = 122.1 +filament_density = 1.23 +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +temperature = 195 + +[filament:Prusa ABS] +inherits = *ABSC* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Prusa HIPS] +inherits = *Generic HIPS* +filament_vendor = Made for Prusa +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic HIPS] +inherits = *ABS* +filament_vendor = Generic +filament_cost = 27.3 +filament_density = 1.04 +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 230 +max_fan_speed = 20 +min_fan_speed = 20 +temperature = 230 +compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) + +[filament:Prusa PETG] +inherits = *PET* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Verbatim PETG] +inherits = *PET* +filament_vendor = Verbatim +filament_cost = 27.90 +filament_density = 1.27 +filament_spool_weight = 235 + +[filament:Prusament PETG] +inherits = *PET* +filament_vendor = Prusa Polymers +first_layer_temperature = 240 +temperature = 250 +filament_cost = 36.29 +filament_density = 1.27 +filament_spool_weight = 201 +filament_type = PETG + +[filament:Prusa PLA] +inherits = *PLA* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Eolas Prints PLA] +inherits = *PLA* +filament_vendor = Eolas Prints +filament_cost = 23.50 +filament_density = 1.24 +filament_spool_weight = 0 +filament_colour = #4D9398 +temperature = 208 + +[filament:Eolas Prints PLA Matte] +inherits = Eolas Prints PLA +filament_cost = 25.50 +temperature = 212 + +[filament:Eolas Prints INGEO 850] +inherits = Eolas Prints PLA +filament_cost = 25.90 +temperature = 210 + +[filament:Eolas Prints INGEO 870] +inherits = Eolas Prints PLA +filament_cost = 25.90 +temperature = 215 +first_layer_bed_temperature = 68 +first_layer_temperature = 220 +bed_temperature = 65 + +[filament:Eolas Prints PETG] +inherits = *PET* +filament_vendor = Eolas Prints +filament_cost = 29.90 +filament_density = 1.27 +filament_spool_weight = 0 +filament_colour = #4D9398 +temperature = 240 +first_layer_bed_temperature = 85 +first_layer_temperature = 235 +bed_temperature = 90 + +[filament:Eolas Prints PETG - UV Resistant] +inherits = Eolas Prints PETG +filament_cost = 35.90 +temperature = 237 +first_layer_temperature = 232 + +[filament:Eolas Prints TPU 93A] +inherits = *FLEX* +filament_vendor = Eolas Prints +filament_cost = 34.99 +filament_density = 1.21 +filament_colour = #4D9398 +filament_max_volumetric_speed = 1.2 +temperature = 235 +first_layer_bed_temperature = 30 +bed_temperature = 30 +filament_retract_length = 0 +extrusion_multiplier = 1.16 + +[filament:Fiberlogy Easy PLA] +inherits = *PLA* +filament_vendor = Fiberlogy +filament_cost = 20 +filament_density = 1.24 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 330 + +[filament:Fiberlogy Easy PET-G] +inherits = *PET* +filament_vendor = Fiberlogy +filament_spool_weight = 330 +filament_cost = 20 +filament_density = 1.27 +first_layer_bed_temperature = 80 +bed_temperature = 80 +first_layer_temperature = 235 +temperature = 235 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 60 +disable_fan_first_layers = 5 +full_fan_speed_layer = 5 +slowdown_below_layer_time = 15 + +[filament:Fiberlogy ASA] +inherits = *ABS* +filament_vendor = Fiberlogy +filament_cost = 33 +filament_density = 1.07 +filament_spool_weight = 330 +fan_always_on = 0 +cooling = 1 +min_fan_speed = 10 +max_fan_speed = 15 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ASA +fan_below_layer_time = 30 +disable_fan_first_layers = 5 + +[filament:Fiberlogy Easy ABS] +inherits = Fiberlogy ASA +filament_cost = 22.67 +filament_density = 1.09 +fan_always_on = 0 +cooling = 1 +min_fan_speed = 10 +max_fan_speed = 15 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 250 +temperature = 250 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ABS +fan_below_layer_time = 25 +disable_fan_first_layers = 5 + +[filament:Fiberlogy CPE HT] +inherits = *PET* +filament_vendor = Fiberlogy +filament_cost = 42.67 +filament_density = 1.18 +extrusion_multiplier = 0.98 +filament_spool_weight = 330 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 50 +min_print_speed = 15 +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_type = CPE +fan_below_layer_time = 20 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 5 + +[filament:Fiberlogy PCTG] +inherits = Fiberlogy CPE HT +filament_cost = 29.41 +filament_density = 1.23 +extrusion_multiplier = 1 +min_fan_speed = 10 +max_fan_speed = 15 +first_layer_temperature = 265 +temperature = 265 +first_layer_bed_temperature = 90 +bed_temperature = 90 +filament_type = PCTG + +[filament:Fiberlogy FiberFlex 40D] +inherits = *FLEX* +filament_vendor = Fiberlogy +fan_always_on = 1 +filament_max_volumetric_speed = 1.5 +extrusion_multiplier = 1.12 +first_layer_temperature = 230 +first_layer_bed_temperature = 60 +temperature = 230 +bed_temperature = 60 +bridge_fan_speed = 75 +min_fan_speed = 25 +max_fan_speed = 75 +filament_cost = 39.41 +filament_density = 1.16 +disable_fan_first_layers = 5 +full_fan_speed_layer = 5 +min_print_speed = 15 +cooling = 1 +filament_spool_weight = 330 + +[filament:Fiberlogy MattFlex 40D] +inherits = Fiberlogy FiberFlex 40D +filament_vendor = Fiberlogy +fan_always_on = 1 +filament_max_volumetric_speed = 1.35 +extrusion_multiplier = 1.1 +filament_cost = 49.11 + +[filament:Fiberlogy FiberFlex 30D] +inherits = Fiberlogy FiberFlex 40D +filament_max_volumetric_speed = 1.2 +extrusion_multiplier = 1.15 +first_layer_temperature = 240 +temperature = 240 +min_fan_speed = 25 +max_fan_speed = 60 +filament_density = 1.07 + +[filament:Fiberlogy FiberSatin] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 215 +temperature = 215 +extrusion_multiplier = 1 +filament_density = 1.2 +filament_cost = 32.35 + +[filament:Fiberlogy FiberSilk] +inherits = Fiberlogy FiberSatin +first_layer_temperature = 230 +temperature = 230 +extrusion_multiplier = 0.97 +filament_density = 1.22 +filament_cost = 32.35 + +[filament:Fiberlogy FiberWood] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 185 +temperature = 185 +extrusion_multiplier = 1 +filament_density = 1.23 +filament_cost = 38.66 + +[filament:Fiberlogy HD PLA] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 230 +temperature = 230 +extrusion_multiplier = 1 +filament_density = 1.24 +filament_cost = 30.59 + +[filament:Fiberlogy PLA Mineral] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 195 +temperature = 190 +extrusion_multiplier = 0.98 +filament_density = 1.38 +filament_cost = 37.64 + +[filament:Fiberlogy Impact PLA] +inherits = Fiberlogy HD PLA +filament_density = 1.22 +filament_cost = 27.65 + +[filament:Fiberlogy Nylon PA12] +inherits = Fiberlogy ASA +filament_type = PA +filament_density = 1.01 +filament_cost = 48 +first_layer_bed_temperature = 105 +bed_temperature = 105 +first_layer_temperature = 265 +temperature = 265 +min_fan_speed = 10 +max_fan_speed = 15 +fan_below_layer_time = 20 +bridge_fan_speed = 30 +fan_always_on = 0 + +[filament:Fiberlogy Nylon PA12+CF15] +inherits = Fiberlogy Nylon PA12 +extrusion_multiplier = 0.97 +filament_density = 1.07 +filament_cost = 87.5 +first_layer_bed_temperature = 105 +bed_temperature = 105 +first_layer_temperature = 265 +temperature = 265 +min_fan_speed = 10 +max_fan_speed = 15 +fan_below_layer_time = 20 +bridge_fan_speed = 30 +fan_always_on = 0 + +[filament:Fiberlogy Nylon PA12+GF15] +inherits = Fiberlogy Nylon PA12+CF15 +filament_density = 1.13 + +[filament:Fiberlogy PP] +inherits = *ABS* +filament_vendor = Fiberlogy +filament_cost = 36.67 +filament_density = 1.05 +extrusion_multiplier = 1.05 +filament_spool_weight = 330 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 0 +max_fan_speed = 25 +bridge_fan_speed = 70 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 245 +temperature = 245 +first_layer_bed_temperature = 0 +bed_temperature = 0 +filament_type = PP +fan_below_layer_time = 100 +disable_fan_first_layers = 5 +filament_max_volumetric_speed = 5 + +[filament:Filament PM PLA] +inherits = *PLA* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:AmazonBasics PLA] +inherits = *PLA* +filament_vendor = AmazonBasics +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Overture PLA] +inherits = *PLA* +filament_vendor = Overture +filament_cost = 22 +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Hatchbox PLA] +inherits = *PLA* +filament_vendor = Hatchbox +filament_cost = 25.4 +filament_density = 1.27 +filament_spool_weight = 245 + +[filament:Esun PLA] +inherits = *PLA* +filament_vendor = Esun +filament_cost = 25.4 +filament_density = 1.24 +filament_spool_weight = 265 + +[filament:Das Filament PLA] +inherits = *PLA* +filament_vendor = Das Filament +filament_cost = 25.4 +filament_density = 1.24 + +[filament:EUMAKERS PLA] +inherits = *PLA* +filament_vendor = EUMAKERS +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Floreon3D PLA] +inherits = *PLA* +filament_vendor = Floreon3D +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Prusament PLA] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +filament_cost = 36.29 +filament_density = 1.24 +filament_spool_weight = 201 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" + +[filament:Prusament PVB] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +bed_temperature = 75 +first_layer_bed_temperature = 75 +filament_cost = 60.48 +filament_density = 1.09 +filament_spool_weight = 201 +filament_max_volumetric_speed = 8 +filament_type = PVB +filament_soluble = 1 +filament_colour = #FFFF6F +slowdown_below_layer_time = 20 + +[filament:Fillamentum Flexfill 98A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_cost = 82.26 +filament_density = 1.23 +filament_spool_weight = 230 +extrusion_multiplier = 1.1 +filament_max_volumetric_speed = 1.5 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:ColorFabb VarioShore TPU] +inherits = Fillamentum Flexfill 98A +filament_vendor = ColorFabb +filament_colour = #BBBBBB +filament_cost = 71.35 +filament_density = 1.22 +filament_spool_weight = 0 +extrusion_multiplier = 0.85 +first_layer_temperature = 220 +temperature = 220 + +[filament:Taulman Bridge] +inherits = *common* +filament_vendor = Taulman +filament_cost = 40 +filament_density = 1.13 +bed_temperature = 110 +bridge_fan_speed = 40 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = PA +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 +max_fan_speed = 0 +min_fan_speed = 0 +filament_max_volumetric_speed = 6 + +[filament:Fillamentum Nylon FX256] +inherits = *common* +filament_vendor = Fillamentum +filament_cost = 56.99 +filament_density = 1.01 +filament_spool_weight = 230 +bed_temperature = 90 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 6 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 6 +filament_soluble = 0 +filament_type = PA +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +max_fan_speed = 0 +min_fan_speed = 0 +temperature = 250 + +[filament:Fiberthree F3 PA Pure Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_cost = 200.84 +filament_density = 1.2 +bed_temperature = 90 +first_layer_bed_temperature = 90 +first_layer_temperature = 285 +temperature = 285 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_soluble = 0 +filament_type = PA +max_fan_speed = 20 +min_fan_speed = 20 + +[filament:Fiberthree F3 PA-CF Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_cost = 208.1 +filament_density = 1.25 +bed_temperature = 90 +first_layer_bed_temperature = 90 +first_layer_temperature = 285 +temperature = 285 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_soluble = 0 +filament_type = PA +max_fan_speed = 0 +min_fan_speed = 0 + +[filament:Fiberthree F3 PA-GF Pro] +inherits = Fiberthree F3 PA-CF Pro +filament_vendor = Fiberthree +filament_cost = 205.68 +filament_density = 1.27 +fan_always_on = 1 +max_fan_speed = 15 +min_fan_speed = 15 + +[filament:Taulman T-Glase] +inherits = *PET* +filament_vendor = Taulman +filament_cost = 40 +filament_density = 1.27 +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 + +[filament:Verbatim PLA] +inherits = *PLA* +filament_vendor = Verbatim +filament_cost = 42.99 +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Verbatim BVOH] +inherits = *common* +filament_vendor = Verbatim +filament_cost = 193.58 +filament_density = 1.14 +filament_spool_weight = 235 +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +filament_vendor = Verbatim +filament_cost = 72 +filament_density = 0.89 +filament_spool_weight = 235 +bed_temperature = 100 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_type = PP +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 220 + +[filament:FormFutura Centaur PP] +inherits = *common* +filament_vendor = FormFutura +filament_cost = 70 +filament_density = 0.89 +filament_spool_weight = 212 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1.05 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 4 +filament_type = PP +first_layer_bed_temperature = 85 +bed_temperature = 85 +first_layer_temperature = 235 +max_fan_speed = 70 +min_fan_speed = 70 +temperature = 235 +filament_wipe = 0 +filament_retract_lift = 0 \ No newline at end of file diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 7793a2bd4..0d9888d40 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -36,10 +36,10 @@ static const std::string MODEL_PREFIX = "model:"; // are phased out, then we will revert to the original name. //static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; -// url to folder with profile archive zip -// TODO: Uncomment and delete 2nd when we have archive online -//static const std::string PROFILE_ARCHIVE_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Archive/Archive.zip"; -static const std::string PROFILE_ARCHIVE_URL = "https://raw.githubusercontent.com/kocikdav/PrusaSlicer-settings/master/live/Bundle/Archive.zip"; +// Url to index archive zip that contains latest indicies +static const std::string INDEX_ARCHIVE_URL= "https://files.prusa3d.com/wp-content/uploads/repository/vendor_indices.zip"; +// Url to folder with vendor profile files. Used when downloading new profiles that are not in resources folder. +static const std::string PROFILE_FOLDER_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; @@ -671,9 +671,24 @@ std::string AppConfig::version_check_url() const return from_settings.empty() ? VERSION_CHECK_URL : from_settings; } -const std::string& AppConfig::profile_archive_url() const +std::string AppConfig::index_archive_url() const { - return PROFILE_ARCHIVE_URL; +#if 0 + // this code is for debug & testing purposes only - changed url wont get trough inner checks anyway. + auto from_settings = get("index_archive_url"); + return from_settings.empty() ? INDEX_ARCHIVE_URL : from_settings; +#endif + return INDEX_ARCHIVE_URL; +} + +std::string AppConfig::profile_folder_url() const +{ +#if 0 + // this code is for debug & testing purposes only - changed url wont get trough inner checks anyway. + auto from_settings = get("profile_folder_url"); + return from_settings.empty() ? PROFILE_FOLDER_URL : from_settings; +#endif + return PROFILE_FOLDER_URL; } bool AppConfig::exists() diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 1ecd8cd64..78a7065d9 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -139,8 +139,11 @@ public: // Get the Slic3r version check url. // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. std::string version_check_url() const; - // Get the Slic3r url to vendor profile archive zip. - const std::string& profile_archive_url() const; + // Get the Slic3r url to vendor index archive zip. + std::string index_archive_url() const; + // Get the Slic3r url to folder with vendor profile files. + std::string profile_folder_url() const; + // Returns the original Slic3r version found in the ini file before it was overwritten // by the current version diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e9d3b09ff..1f5fbc6b2 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2146,7 +2146,7 @@ namespace PresetUtils { if (! res.empty() && !fs::exists(fs::path(vendor_folder + res)) && !fs::exists(fs::path(rsrc_folder + res)) - && (fs::exists(fs::path(cache_folder + res)))) + && !fs::exists(fs::path(cache_folder + res))) return false; } } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index ed063415f..81de20716 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1308,13 +1308,22 @@ std::pair PresetBundle::load_configbundle( try { pt::read_ini(ifs, tree); } catch (const boost::property_tree::ini_parser::ini_parser_error &err) { - throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); + // This throw was uncatched. While other similar problems later are just returning empty pair. + //throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); + BOOST_LOG_TRIVIAL(error) << format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str(); + return std::make_pair(PresetsConfigSubstitutions{}, 0); } } const VendorProfile *vendor_profile = nullptr; if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { - auto vp = VendorProfile::from_ini(tree, path); + VendorProfile vp; + try { + vp = VendorProfile::from_ini(tree, path); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Failed to open profile file.") % path; + return std::make_pair(PresetsConfigSubstitutions{}, 0); + } if (vp.models.size() == 0 && !vp.templates_profile) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; return std::make_pair(PresetsConfigSubstitutions{}, 0); @@ -1360,7 +1369,7 @@ std::pair PresetBundle::load_configbundle( } else if (boost::starts_with(section.first, "filament:")) { presets = &this->filaments; preset_name = section.first.substr(9); - if (vendor_profile->templates_profile) { + if (vendor_profile && vendor_profile->templates_profile) { preset_name += " @Template"; } } else if (boost::starts_with(section.first, "sla_print:")) { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2de72ad02..6ba866ac1 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -141,6 +141,8 @@ BundleMap BundleMap::load() typedef std::pair DirData; std::vector dir_list { {vendor_dir, BundleLocation::IN_VENDOR}, {archive_dir, BundleLocation::IN_ARCHIVE}, {rsrc_vendor_dir, BundleLocation::IN_RESOURCES} }; for ( auto dir : dir_list) { + if (!fs::exists(dir.first)) + continue; for (const auto &dir_entry : boost::filesystem::directory_iterator(dir.first)) { if (Slic3r::is_ini_file(dir_entry)) { std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part @@ -226,26 +228,30 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt bool is_variants = false; + const fs::path vendor_dir_path = (fs::path(Slic3r::data_dir()) / "vendor").make_preferred(); + const fs::path cache_dir_path = (fs::path(Slic3r::data_dir()) / "cache").make_preferred(); + const fs::path rsrc_dir_path = (fs::path(resources_dir()) / "profiles").make_preferred(); + for (const auto &model : models) { if (! filter(model)) { continue; } wxBitmap bitmap; int bitmap_width = 0; - auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { - if (wxFileExists(bitmap_file)) { - bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); - bitmap_width = bitmap.GetWidth(); - return true; - } - return false; + auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width) { + bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); + bitmap_width = bitmap.GetWidth(); }; bool found = false; - for (const std::string& res : { Slic3r::data_dir() + "/vendor/" + vendor.id + "/", Slic3r::resources_dir() + "/profiles/" + vendor.id + "/", Slic3r::data_dir() + "/cache/" + vendor.id + "/" } ) { - if (load_bitmap(GUI::from_u8(res + "/" + model.thumbnail), bitmap, bitmap_width)) { - found = true; - break; - } + for (const fs::path& res : { rsrc_dir_path / vendor.id / model.thumbnail + , vendor_dir_path / vendor.id / model.thumbnail + , cache_dir_path / vendor.id / model.thumbnail }) + { + if (!fs::exists(res)) + continue; + load_bitmap(GUI::from_u8(res.string()), bitmap, bitmap_width); + found = true; + break; } if (!found) { @@ -256,7 +262,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); } - auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + wxStaticText* title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); title->SetFont(font_name); const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); title->Wrap(wrap_width); @@ -269,7 +275,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt titles.push_back(title); - auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); + wxStaticBitmap* bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); bitmaps.push_back(bitmap_widget); auto *variants_panel = new wxPanel(this); @@ -292,7 +298,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt is_variants = true; } - auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); + Checkbox* cbox = new Checkbox(variants_panel, label, model_id, variant.name); i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); @@ -1616,7 +1622,7 @@ PageVendors::PageVendors(ConfigWizard *parent) for (const auto &pair : wizard_p()->bundles) { const VendorProfile *vendor = pair.second.vendor_profile; if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - if (vendor->templates_profile) + if (vendor && vendor->templates_profile) continue; auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); @@ -2498,7 +2504,7 @@ void ConfigWizard::priv::update_materials(Technology technology) } } // template filament bundle has no printers - filament would be never added - if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) + if(pair.second.vendor_profile&& pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) { if (!filaments.containts(&filament)) { filaments.push(&filament); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 0c3fed13f..17c9fb25f 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -369,11 +369,11 @@ struct Materials if (((printer == nullptr && printer_name == PageMaterials::EMPTY) || (printer != nullptr && is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor)))) && (type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor) && - !prst.vendor->templates_profile) { + prst.vendor && !prst.vendor->templates_profile) { cb(preset); } - else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor->templates_profile && + else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor && prst.vendor->templates_profile && (type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor)) { cb(preset); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6975006a6..3e561d04e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1,3388 +1,3394 @@ -#include "libslic3r/Technologies.hpp" -#include "GUI_App.hpp" -#include "GUI_Init.hpp" -#include "GUI_ObjectList.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "GUI_Factories.hpp" -#include "format.hpp" -#include "I18N.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/I18N.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Color.hpp" - -#include "GUI.hpp" -#include "GUI_Utils.hpp" -#include "3DScene.hpp" -#include "MainFrame.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" - -#include "../Utils/PresetUpdater.hpp" -#include "../Utils/PrintHost.hpp" -#include "../Utils/Process.hpp" -#include "../Utils/MacDarkMode.hpp" -#include "../Utils/AppUpdater.hpp" -#include "../Utils/WinRegistry.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "ConfigSnapshotDialog.hpp" -#include "FirmwareDialog.hpp" -#include "Preferences.hpp" -#include "Tab.hpp" -#include "SysInfoDialog.hpp" -#include "KBShortcutsDialog.hpp" -#include "UpdateDialogs.hpp" -#include "Mouse3DController.hpp" -#include "RemovableDriveManager.hpp" -#include "InstanceCheck.hpp" -#include "NotificationManager.hpp" -#include "UnsavedChangesDialog.hpp" -#include "SavePresetDialog.hpp" -#include "PrintHostDialogs.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "SendSystemInfoDialog.hpp" -#include "Downloader.hpp" - -#include "BitmapCache.hpp" -#include "Notebook.hpp" - -#ifdef __WXMSW__ -#include -#include -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE -#endif -#ifdef _WIN32 -#include -#endif - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -#include -#include -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -// Needed for forcing menu icons back under gtk2 and gtk3 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - #include -#endif - -using namespace std::literals; - -namespace Slic3r { -namespace GUI { - -class MainFrame; - -class SplashScreen : public wxSplashScreen -{ -public: - SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) - : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, -#ifdef __APPLE__ - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP -#else - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR -#endif // !__APPLE__ - ) - { - wxASSERT(bitmap.IsOk()); - -// int init_dpi = get_dpi_for_window(this); - this->SetPosition(pos); - // The size of the SplashScreen can be hanged after its moving to another display - // So, update it from a bitmap size - this->SetClientSize(bitmap.GetWidth(), bitmap.GetHeight()); - this->CenterOnScreen(); -// int new_dpi = get_dpi_for_window(this); - -// m_scale = (float)(new_dpi) / (float)(init_dpi); - m_main_bitmap = bitmap; - -// scale_bitmap(m_main_bitmap, m_scale); - - // init constant texts and scale fonts - init_constant_text(); - - // this font will be used for the action string - m_action_font = m_constant_text.credits_font.Bold(); - - // draw logo and constant info text - Decorate(m_main_bitmap); - } - - void SetText(const wxString& text) - { - set_bitmap(m_main_bitmap); - if (!text.empty()) { - wxBitmap bitmap(m_main_bitmap); - - wxMemoryDC memDC; - memDC.SelectObject(bitmap); - - memDC.SetFont(m_action_font); - memDC.SetTextForeground(wxColour(237, 107, 33)); - memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position); - - memDC.SelectObject(wxNullBitmap); - set_bitmap(bitmap); -#ifdef __WXOSX__ - // without this code splash screen wouldn't be updated under OSX - wxYield(); -#endif - } - } - - static wxBitmap MakeBitmap(wxBitmap bmp) - { - if (!bmp.IsOk()) - return wxNullBitmap; - - // create dark grey background for the splashscreen - // It will be 5/3 of the weight of the bitmap - int width = lround((double)5 / 3 * bmp.GetWidth()); - int height = bmp.GetHeight(); - - wxImage image(width, height); - unsigned char* imgdata_ = image.GetData(); - for (int i = 0; i < width * height; ++i) { - *imgdata_++ = 51; - *imgdata_++ = 51; - *imgdata_++ = 51; - } - - wxBitmap new_bmp(image); - - wxMemoryDC memDC; - memDC.SelectObject(new_bmp); - memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); - - return new_bmp; - } - - void Decorate(wxBitmap& bmp) - { - if (!bmp.IsOk()) - return; - - // draw text to the box at the left of the splashscreen. - // this box will be 2/5 of the weight of the bitmap, and be at the left. - int width = lround(bmp.GetWidth() * 0.4); - - // load bitmap for logo - BitmapCache bmp_cache; - int logo_size = lround(width * 0.25); - wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); - - wxCoord margin = int(m_scale * 20); - - wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight())); - banner_rect.Deflate(margin, 2 * margin); - - // use a memory DC to draw directly onto the bitmap - wxMemoryDC memDc(bmp); - - // draw logo - memDc.DrawBitmap(logo_bmp, margin, margin, true); - - // draw the (white) labels inside of our black box (at the left of the splashscreen) - memDc.SetTextForeground(wxColour(255, 255, 255)); - - memDc.SetFont(m_constant_text.title_font); - memDc.DrawLabel(m_constant_text.title, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - - int title_height = memDc.GetTextExtent(m_constant_text.title).GetY(); - banner_rect.SetTop(banner_rect.GetTop() + title_height); - banner_rect.SetHeight(banner_rect.GetHeight() - title_height); - - memDc.SetFont(m_constant_text.version_font); - memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); - - memDc.SetFont(m_constant_text.credits_font); - memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); - int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); - int text_height = memDc.GetTextExtent("text").GetY(); - - // calculate position for the dynamic text - int logo_and_header_height = margin + logo_size + title_height + version_height; - m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height); - } - -private: - wxBitmap m_main_bitmap; - wxFont m_action_font; - int m_action_line_y_position; - float m_scale {1.0}; - - struct ConstantText - { - wxString title; - wxString version; - wxString credits; - - wxFont title_font; - wxFont version_font; - wxFont credits_font; - - void init(wxFont init_font) - { - // title - title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; - - // dynamically get the version to display - version = _L("Version") + " " + std::string(SLIC3R_VERSION); - - // credits infornation - credits = title + " " + - _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + - _L("Developed by Prusa Research.") + "\n\n" + - title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + ".\n\n" + - _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + - _L("Artwork model by Creative Tools"); - - title_font = version_font = credits_font = init_font; - } - } - m_constant_text; - - void init_constant_text() - { - m_constant_text.init(get_default_font(this)); - - // As default we use a system font for current display. - // Scale fonts in respect to banner width - - int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins - - float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX(); - scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale); - - float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX(); - scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale); - - // The width of the credits information string doesn't respect to the banner width some times. - // So, scale credits_font in the respect to the longest string width - int longest_string_width = word_wrap_string(m_constant_text.credits); - float font_scale = (float)text_banner_width / longest_string_width; - scale_font(m_constant_text.credits_font, font_scale); - } - - void set_bitmap(wxBitmap& bmp) - { - m_window->SetBitmap(bmp); - m_window->Refresh(); - m_window->Update(); - } - - void scale_bitmap(wxBitmap& bmp, float scale) - { - if (scale == 1.0) - return; - - wxImage image = bmp.ConvertToImage(); - if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) - return; - - int width = int(scale * image.GetWidth()); - int height = int(scale * image.GetHeight()); - image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); - - bmp = wxBitmap(std::move(image)); - } - - void scale_font(wxFont& font, float scale) - { -#ifdef __WXMSW__ - // Workaround for the font scaling in respect to the current active display, - // not for the primary display, as it's implemented in Font.cpp - // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp - // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) - wxNativeFontInfo nfi= *font.GetNativeFontInfo(); - float pointSizeNew = wxDisplay(this).GetScaleFactor() * scale * font.GetPointSize(); - nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); - nfi.pointSize = pointSizeNew; - font = wxFont(nfi); -#else - font.Scale(scale); -#endif //__WXMSW__ - } - - // wrap a string for the strings no longer then 55 symbols - // return extent of the longest string - int word_wrap_string(wxString& input) - { - size_t line_len = 55;// count of symbols in one line - int idx = -1; - size_t cur_len = 0; - - wxString longest_sub_string; - auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { - if (cur_len > longest_sub_str.Len()) - longest_sub_str = input.SubString(i - cur_len + 1, i); - }; - - for (size_t i = 0; i < input.Len(); i++) - { - cur_len++; - if (input[i] == ' ') - idx = i; - if (input[i] == '\n') - { - get_longest_sub_string(longest_sub_string, cur_len, i); - idx = -1; - cur_len = 0; - } - if (cur_len >= line_len && idx >= 0) - { - get_longest_sub_string(longest_sub_string, cur_len, i); - input[idx] = '\n'; - cur_len = i - static_cast(idx); - } - } - - return GetTextExtent(longest_sub_string).GetX(); - } -}; - - -#ifdef __linux__ -bool static check_old_linux_datadir(const wxString& app_name) { - // If we are on Linux and the datadir does not exist yet, look into the old - // location where the datadir was before version 2.3. If we find it there, - // tell the user that he might wanna migrate to the new location. - // (https://github.com/prusa3d/PrusaSlicer/issues/2911) - // To be precise, the datadir should exist, it is created when single instance - // lock happens. Instead of checking for existence, check the contents. - - namespace fs = boost::filesystem; - - std::string new_path = Slic3r::data_dir(); - - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - std::string default_path = (dir + "/" + app_name).ToUTF8().data(); - - if (new_path != default_path) { - // This happens when the user specifies a custom --datadir. - // Do not show anything in that case. - return true; - } - - fs::path data_dir = fs::path(new_path); - if (! fs::is_directory(data_dir)) - return true; // This should not happen. - - int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator()); - - if (file_count <= 1) { // just cache dir with an instance lock - std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data(); - - if (fs::is_directory(old_path)) { - wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration " - "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n" - "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, " - "an old %1% configuration directory was detected in \n%3%.\n\n" - "Consider moving the contents of the old directory to the new location in order to access " - "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old " - "location again.\n\n" - "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); - wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); - RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); - dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); - if (dlg.ShowModal() != wxID_NO) - return false; - } - } else { - // If the new directory exists, be silent. The user likely already saw the message. - } - return true; -} -#endif - -#ifdef _WIN32 -#if 0 // External Updater is replaced with AppUpdater.cpp -static bool run_updater_win() -{ - // find updater exe - boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; - // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst - std::string msg; - bool res = create_process(path_updater, L"/silent", msg); - if (!res) - BOOST_LOG_TRIVIAL(error) << msg; - return res; -} -#endif // 0 -#endif // _WIN32 - -struct FileWildcards { - std::string_view title; - std::vector file_extensions; -}; - - - -static const FileWildcards file_wildcards_by_type[FT_SIZE] = { - /* FT_STL */ { "STL files"sv, { ".stl"sv } }, - /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, - /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, - /* FT_STEP */ { "STEP files"sv, { ".stp"sv, ".step"sv } }, - /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, - /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, - /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, - /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv } }, - /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, - /* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } }, - /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, - - /* FT_INI */ { "INI files"sv, { ".ini"sv } }, - /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, - - /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, -}; - -#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR -wxString file_wildcards(FileType file_type) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - - // Generate cumulative first item - for (const std::string_view& ext : data.file_extensions) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } - else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); - - // Adds an item for each of the extensions - if (data.file_extensions.size() > 1) { - for (const std::string_view& ext : data.file_extensions) { - title = "*"; - title += ext; - ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); - } - } - - return ret; -} -#else -// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. -// The function accepts a custom extension parameter. If the parameter is provided, the custom extension -// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips -// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). -wxString file_wildcards(FileType file_type, const std::string &custom_extension) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - std::string custom_ext_lower; - - if (! custom_extension.empty()) { - // Generate an extension into the title mask and into the list of extensions. - custom_ext_lower = boost::to_lower_copy(custom_extension); - const std::string custom_ext_upper = boost::to_upper_copy(custom_extension); - if (custom_ext_lower == custom_extension) { - // Add a lower case version. - title = std::string("*") + custom_ext_lower; - mask = title; - // Add an upper case version. - mask += ";*"; - mask += custom_ext_upper; - } else if (custom_ext_upper == custom_extension) { - // Add an upper case version. - title = std::string("*") + custom_ext_upper; - mask = title; - // Add a lower case version. - mask += ";*"; - mask += custom_ext_lower; - } else { - // Add the mixed case version only. - title = std::string("*") + custom_extension; - mask = title; - } - } - - for (const std::string_view &ext : data.file_extensions) - // Only add an extension if it was not added first as the custom extension. - if (ext != custom_ext_lower) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); -} -#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR - -static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) -static void register_win32_dpi_event() -{ - enum { WM_DPICHANGED_ = 0x02e0 }; - - wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { - const int dpi = wParam & 0xffff; - const auto rect = reinterpret_cast(lParam); - const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right)); - - DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect); - win->GetEventHandler()->AddPendingEvent(evt); - - return true; - }); -} -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - -static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; - -static void register_win32_device_notification_event() -{ - wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; - switch (wParam) { - case DBT_DEVICEARRIVAL: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { -// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - case DBT_DEVICEREMOVECOMPLETE: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) -// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - default: - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - wchar_t sPath[MAX_PATH]; - if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { - struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); - if (! SHGetPathFromIDList(pidl, sPath)) { - BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; - return false; - } - } - switch (lParam) { - case SHCNE_MEDIAINSERTED: - { - //printf("SHCNE_MEDIAINSERTED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - break; - } - case SHCNE_MEDIAREMOVED: - { - //printf("SHCNE_MEDIAREMOVED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - break; - } - default: -// printf("Unknown\n"); - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - auto main_frame = dynamic_cast(Slic3r::GUI::find_toplevel_parent(win)); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); -// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) { - if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) { - RAWINPUT raw; - UINT rawSize = sizeof(RAWINPUT); - ::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); - if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid)) - return true; - } - return false; - }); - - wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - COPYDATASTRUCT* copy_data_structure = { 0 }; - copy_data_structure = (COPYDATASTRUCT*)lParam; - if (copy_data_structure->dwData == 1) { - LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; - Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); - } - return true; - }); -} -#endif // WIN32 - -static void generic_exception_handle() -{ - // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage - // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0 - // - // wxLogError typically goes around exception handling and display an error dialog some time - // after an error is logged even if exception handling and OnExceptionInMainLoop() take place. - // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates - // errors if multiple have been collected and displays just one error message for all of them. - // Otherwise we would get multiple error messages for one missing png, for example. - // - // If a custom error message window (or some other solution) were to be used, it would be necessary - // to turn off wxLogError() usage in wx APIs, most notably in wxImage - // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a - - try { - throw; - } catch (const std::bad_alloc& ex) { - // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) - // and terminate the app so it is at least certain to happen now. - wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. " - "If you are sure you have enough RAM on your system, this may also be a bug and we would " - "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); - std::terminate(); - } catch (const boost::io::bad_format_string& ex) { - wxString errmsg = _L("PrusaSlicer has encountered a localization error. " - "Please report to PrusaSlicer team, what language was active and in which scenario " - "this issue happened. Thank you.\n\nThe application will now terminate."); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - std::terminate(); - throw; - } catch (const std::exception& ex) { - wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what())); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - throw; - } -} - -void GUI_App::post_init() -{ - assert(initialized()); - if (! this->initialized()) - throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); - - if (this->is_gcode_viewer()) { - if (! this->init_params->input_files.empty()) - this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); - } - else if (this->init_params->start_downloader) { - start_download(this->init_params->download_url); - } else { - if (! this->init_params->preset_substitutions.empty()) - show_substitutions_info(this->init_params->preset_substitutions); - -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - this->gui->mainframe->load_config(m_print_config); -#endif - if (! this->init_params->load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - this->mainframe->load_config_file(this->init_params->load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (!this->init_params->input_files.empty()) { - const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); - if (!res.empty() && this->init_params->input_files.size() == 1) { - // Update application titlebar when opening a project file - const std::string& filename = this->init_params->input_files.front(); - if (boost::algorithm::iends_with(filename, ".amf") || - boost::algorithm::iends_with(filename, ".amf.xml") || - boost::algorithm::iends_with(filename, ".3mf")) - this->plater()->set_project_filename(from_u8(filename)); - } - if (this->init_params->delete_after_load) { - for (const std::string& p : this->init_params->input_files) { - boost::system::error_code ec; - boost::filesystem::remove(boost::filesystem::path(p), ec); - if (ec) { - BOOST_LOG_TRIVIAL(error) << ec.message(); - } - } - } - } - if (! this->init_params->extra_config.empty()) - this->mainframe->load_config(this->init_params->extra_config); - } - - // show "Did you know" notification - if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) - plater_->get_notification_manager()->push_hint_notification(true); - - // The extra CallAfter() is needed because of Mac, where this is the only way - // to popup a modal dialog on start without screwing combo boxes. - // This is ugly but I honestly found no better way to do it. - // Neither wxShowEvent nor wxWindowCreateEvent work reliably. - if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. - if (! this->check_updates(false)) - // Configuration is not compatible and reconfigure was refused by the user. Application is closing. - return; - CallAfter([this] { - // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. - bool cw_showed = this->config_wizard_startup(); - this->preset_updater->sync(preset_bundle); - this->app_version_check(false); - if (! cw_showed) { - // The CallAfter is needed as well, without it, GL extensions did not show. - // Also, we only want to show this when the wizard does not, so the new user - // sees something else than "we want something" on the first start. - show_send_system_info_dialog_if_needed(); - } - }); - } - - // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. - app_config->set("version", SLIC3R_VERSION); - app_config->save(); - -#ifdef _WIN32 - // Sets window property to mainframe so other instances can indentify it. - OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); -#endif //WIN32 -} - -IMPLEMENT_APP(GUI_App) - -GUI_App::GUI_App(EAppMode mode) - : wxApp() - , m_app_mode(mode) - , m_em_unit(10) - , m_imgui(new ImGuiWrapper()) - , m_removable_drive_manager(std::make_unique()) - , m_other_instance_message_handler(std::make_unique()) - , m_downloader(std::make_unique()) -{ - //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp - this->init_app_config(); - // init app downloader after path to datadir is set - m_app_updater = std::make_unique(); -} - -GUI_App::~GUI_App() -{ - if (app_config != nullptr) - delete app_config; - - if (preset_bundle != nullptr) - delete preset_bundle; - - if (preset_updater != nullptr) - delete preset_updater; -} - -// If formatted for github, plaintext with OpenGL extensions enclosed into
. -// Otherwise HTML formatted for the system info dialog. -std::string GUI_App::get_gl_info(bool for_github) -{ - return OpenGLManager::get_gl_info().to_string(for_github); -} - -wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) -{ -#if ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_DEBUG_OPTION - return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0), - init_params != nullptr ? init_params->opengl_debug : false); -#else - return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0)); -#endif // ENABLE_OPENGL_DEBUG_OPTION -#else - return m_opengl_mgr.init_glcontext(canvas); -#endif // ENABLE_GL_CORE_PROFILE -} - -bool GUI_App::init_opengl() -{ -#ifdef __linux__ - bool status = m_opengl_mgr.init_gl(); - m_opengl_initialized = true; - return status; -#else - return m_opengl_mgr.init_gl(); -#endif -} - -// gets path to PrusaSlicer.ini, returns semver from first line comment -static boost::optional parse_semver_from_ini(std::string path) -{ - std::ifstream stream(path); - std::stringstream buffer; - buffer << stream.rdbuf(); - std::string body = buffer.str(); - size_t start = body.find("PrusaSlicer "); - if (start == std::string::npos) - return boost::none; - body = body.substr(start + 12); - size_t end = body.find_first_of(" \n"); - if (end < body.size()) - body.resize(end); - return Semver::parse(body); -} - -void GUI_App::init_app_config() -{ - // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. - -// SetAppName(SLIC3R_APP_KEY); - SetAppName(SLIC3R_APP_KEY "-alpha"); -// SetAppName(SLIC3R_APP_KEY "-beta"); - - -// SetAppDisplayName(SLIC3R_APP_NAME); - - // Set the Slic3r data directory at the Slic3r XS module. - // Unix: ~/ .Slic3r - // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" - // Mac : "~/Library/Application Support/Slic3r" - - if (data_dir().empty()) { - #ifndef __linux__ - set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); - #else - // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. - // https://github.com/prusa3d/PrusaSlicer/issues/2911 - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); - #endif - } else { - m_datadir_redefined = true; - } - - if (!app_config) - app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); - - // load settings - m_app_conf_exists = app_config->exists(); - if (m_app_conf_exists) { - std::string error = app_config->load(); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - } -} - -// returns old config path to copy from if such exists, -// returns an empty string if such config path does not exists or if it cannot be loaded. -std::string GUI_App::check_older_app_config(Semver current_version, bool backup) -{ - std::string older_data_dir_path; - - // If the config folder is redefined - do not check - if (m_datadir_redefined) - return {}; - - // find other version app config (alpha / beta / release) - std::string config_path = app_config->config_path(); - boost::filesystem::path parent_file_path(config_path); - std::string filename = parent_file_path.filename().string(); - parent_file_path.remove_filename().remove_filename(); - - std::vector candidates; - - if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); - if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); - if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); - - Semver last_semver = current_version; - for (const auto& candidate : candidates) { - if (boost::filesystem::exists(candidate)) { - // parse - boost::optionalother_semver = parse_semver_from_ini(candidate.string()); - if (other_semver && *other_semver > last_semver) { - last_semver = *other_semver; - older_data_dir_path = candidate.parent_path().string(); - } - } - } - if (older_data_dir_path.empty()) - return {}; - BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path; - // ask about using older data folder - - InfoDialog msg(nullptr - , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) - , backup ? - format_wxstr(_L( - "The active configuration was created by %1% %2%," - "\nwhile a newer configuration was found in %3%" - "\ncreated by %1% %4%." - "\n\nShall the newer configuration be imported?" - "\nIf so, your active configuration will be backed up before importing the new configuration." - ) - , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string()) - : format_wxstr(_L( - "An existing configuration was found in %3%" - "\ncreated by %1% %2%." - "\n\nShall this configuration be imported?" - ) - , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path) - , true, wxYES_NO); - - if (backup) { - msg.SetButtonLabel(wxID_YES, _L("Import")); - msg.SetButtonLabel(wxID_NO, _L("Don't import")); - } - - if (msg.ShowModal() == wxID_YES) { - std::string snapshot_id; - if (backup) { - const Config::Snapshot* snapshot{ nullptr }; - if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "", - _u8L("Continue and import newer configuration?"), &snapshot)) - return {}; - if (snapshot) { - // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail. - snapshot_id = snapshot->id; - assert(! snapshot_id.empty()); - app_config->set("on_snapshot", snapshot_id); - } else - BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot"; - } - - // load app config from older file - std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string()); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - if (!snapshot_id.empty()) - app_config->set("on_snapshot", snapshot_id); - m_app_conf_exists = true; - return older_data_dir_path; - } - return {}; -} - -void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) -{ - BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; - m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); -} - -bool GUI_App::OnInit() -{ - try { - return on_init_inner(); - } catch (const std::exception&) { - generic_exception_handle(); - return false; - } -} - -bool GUI_App::on_init_inner() -{ - // Set initialization of image handlers before any UI actions - See GH issue #7469 - wxInitAllImageHandlers(); - -#if defined(_WIN32) && ! defined(_WIN64) - // Win32 32bit build. - if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { - RichMessageDialog dlg(nullptr, - _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows." - "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system." - "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/." - "\nDo you wish to continue?"), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - if (dlg.ShowModal() != wxID_YES) - return false; - } -#endif // _WIN64 - - // Forcing back menu icons under gtk2 and gtk3. Solution is based on: - // https://docs.gtk.org/gtk3/class.Settings.html - // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb - // TODO: Find workaround for GTK4 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); -#endif - - // Verify resources path - const wxString resources_dir = from_u8(Slic3r::resources_dir()); - wxCHECK_MSG(wxDirExists(resources_dir), false, - wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - -#ifdef __linux__ - if (! check_old_linux_datadir(GetAppName())) { - std::cerr << "Quitting, user chose to move their data to new location." << std::endl; - return false; - } -#endif - - // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. -// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); - // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible - // performance when working on high resolution multi-display setups. -// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); - -// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; - - // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action. - // Like here, before the show InfoDialog in check_older_app_config() - - // If load_language() fails, the application closes. - load_language(wxString(), true); -#ifdef _MSW_DARK_MODE - bool init_dark_color_mode = app_config->get("dark_color_mode") == "1"; - bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); -#endif - // initialize label colors and fonts - init_ui_colours(); - init_fonts(); - - std::string older_data_dir_path; - if (m_app_conf_exists) { - if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION)) - // Only copying configuration if it was saved with a newer slicer than the one currently running. - older_data_dir_path = check_older_app_config(app_config->orig_version(), true); - } else { - // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration. - older_data_dir_path = check_older_app_config(Semver(), false); - } - -#ifdef _MSW_DARK_MODE - // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed - if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1"; - init_dark_color_mode != new_dark_color_mode) { - NppDarkMode::SetDarkMode(new_dark_color_mode); - init_ui_colours(); - update_ui_colours_from_appconfig(); - } - if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - init_sys_menu_enabled != new_sys_menu_enabled) - NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled); -#endif - - if (is_editor()) { - std::string msg = Http::tls_global_init(); - std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); - bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); - - if (!msg.empty() && !ssl_accept) { - RichMessageDialog - dlg(nullptr, - wxString::Format(_L("%s\nDo you want to continue?"), msg), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - dlg.ShowCheckBox(_L("Remember my choice")); - if (dlg.ShowModal() != wxID_YES) return false; - - app_config->set("tls_cert_store_accepted", - dlg.IsCheckBoxChecked() ? "yes" : "no"); - app_config->set("tls_accepted_cert_store_location", - dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); - } - } - - SplashScreen* scrn = nullptr; - if (app_config->get("show_splash_screen") == "1") { - // make a bitmap with dark grey banner on the left side - wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); - - // Detect position (display) to show the splash screen - // Now this position is equal to the mainframe position - wxPoint splashscreen_pos = wxDefaultPosition; - bool default_splashscreen_pos = true; - if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { - auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); - default_splashscreen_pos = metrics == boost::none; - if (!default_splashscreen_pos) - splashscreen_pos = metrics->get_rect().GetPosition(); - } - - if (!default_splashscreen_pos) { - // workaround for crash related to the positioning of the window on secondary monitor - get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); - get_app_config()->save(); - } - - // create splash screen with updated bmp - scrn = new SplashScreen(bmp.IsOk() ? bmp : get_bmp_bundle("PrusaSlicer", 400)->GetPreferredBitmapSizeAtScale(1.0), - wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); - - if (!default_splashscreen_pos) - // revert "restore_win_position" value if application wasn't crashed - get_app_config()->set("restore_win_position", "1"); -#ifndef __linux__ - wxYield(); -#endif - scrn->SetText(_L("Loading configuration")+ dots); - } - - preset_bundle = new PresetBundle(); - - // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory - // supplied as argument to --datadir; in that case we should still run the wizard - preset_bundle->setup_directories(); - - if (! older_data_dir_path.empty()) { - preset_bundle->import_newer_configs(older_data_dir_path); - app_config->save(); - } - - if (is_editor()) { -#ifdef __WXMSW__ - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); -#endif // __WXMSW__ - - preset_updater = new PresetUpdater(); - Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); - Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { - std::string evt_string = into_u8(evt.GetString()); - if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { - auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); - this->plater_->get_notification_manager()->push_notification( notif_type - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) - , _u8L("See Releases page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; } - ); - } - } - }); - Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { - //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); - }); - - Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); - if(!evt.GetString().IsEmpty()) - show_error(nullptr, evt.GetString()); - }); - - Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { - show_error(nullptr, evt.GetString()); - }); - - } - else { -#ifdef __WXMSW__ - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); -#endif // __WXMSW__ - } - - // Suppress the '- default -' presets. - preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); - try { - // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. - // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force - // installation of a compatible system preset, thus nullifying the system preset substitutions. - init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); - } catch (const std::exception &ex) { - show_error(nullptr, ex.what()); - } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - register_win32_dpi_event(); -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - register_win32_device_notification_event(); -#endif // WIN32 - - // Let the libslic3r know the callback, which will translate messages on demand. - Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); - - // application frame - if (scrn && is_editor()) - scrn->SetText(_L("Preparing settings tabs") + dots); - - mainframe = new MainFrame(); - // hide settings tabs after first Layout - if (is_editor()) - mainframe->select_tab(size_t(0)); - - sidebar().obj_list()->init_objects(); // propagate model objects to object list -// update_mode(); // !!! do that later - SetTopWindow(mainframe); - - plater_->init_notification_manager(); - - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - - if (is_gcode_viewer()) { - mainframe->update_layout(); - if (plater_ != nullptr) - // ensure the selected technology is ptFFF - plater_->set_printer_technology(ptFFF); - } - else - load_current_presets(); - - // Save the active profiles as a "saved into project". - update_saved_preset_from_current_preset(); - - if (plater_ != nullptr) { - // Save the names of active presets and project specific config into ProjectDirtyStateManager. - plater_->reset_project_dirty_initial_presets(); - // Update Project dirty state, update application title bar. - plater_->update_project_dirty_from_presets(); - } - - mainframe->Show(true); - - obj_list()->set_min_height(); - - update_mode(); // update view mode after fix of the object_list size - -#ifdef __APPLE__ - other_instance_message_handler()->bring_instance_forward(); -#endif //__APPLE__ - - Bind(wxEVT_IDLE, [this](wxIdleEvent& event) - { - if (! plater_) - return; - - this->obj_manipul()->update_if_dirty(); - - // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT - // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. -#ifdef __linux__ - if (! m_post_initialized && m_opengl_initialized) { -#else - if (! m_post_initialized) { -#endif - m_post_initialized = true; -#ifdef WIN32 - this->mainframe->register_win32_callbacks(); -#endif - this->post_init(); - } - - if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1") - app_config->save(); - }); - - m_initialized = true; - - if (const std::string& crash_reason = app_config->get("restore_win_position"); - boost::starts_with(crash_reason,"crashed")) - { - wxString preferences_item = _L("Restore window position on start"); - InfoDialog dialog(nullptr, - _L("PrusaSlicer started after a crash"), - format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n" - "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n" - "More precise reason for the crash: \"%1%\".\n" - "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n" - "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " - "Otherwise, the application will most likely crash again next time."), - "" + from_u8(crash_reason) + "", - "#2939", - "#5573", - "" + preferences_item + ""), - true, wxYES_NO); - - dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); - dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item)); - - auto answer = dialog.ShowModal(); - if (answer == wxID_YES) - app_config->set("restore_win_position", "0"); - else if (answer == wxID_NO) - app_config->set("restore_win_position", "1"); - app_config->save(); - } - - return true; -} - -unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) -{ - double r = colour.Red(); - double g = colour.Green(); - double b = colour.Blue(); - - return std::round(std::sqrt( - r * r * .241 + - g * g * .691 + - b * b * .068 - )); -} - -bool GUI_App::dark_mode() -{ -#if __APPLE__ - // The check for dark mode returns false positive on 10.12 and 10.13, - // which allowed setting dark menu bar and dock area, which is - // is detected as dark mode. We must run on at least 10.14 where the - // proper dark mode was first introduced. - return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode(); -#else - if (wxGetApp().app_config->has("dark_color_mode")) - return wxGetApp().app_config->get("dark_color_mode") == "1"; - return check_dark_mode(); -#endif -} - -const wxColour GUI_App::get_label_default_clr_system() -{ - return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); -} - -const wxColour GUI_App::get_label_default_clr_modified() -{ - return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1); -} - -const std::vector GUI_App::get_mode_default_palette() -{ - return { "#7DF028", "#FFDC00", "#E70000" }; -} - -void GUI_App::init_ui_colours() -{ - m_color_label_modified = get_label_default_clr_modified(); - m_color_label_sys = get_label_default_clr_system(); - m_mode_palette = get_mode_default_palette(); - - bool is_dark_mode = dark_mode(); -#ifdef _WIN32 - m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); - m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); - m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); - m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); -#else - m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -#endif - m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); -} - -void GUI_App::update_ui_colours_from_appconfig() -{ - // load label colors - if (app_config->has("label_clr_sys")) { - auto str = app_config->get("label_clr_sys"); - if (!str.empty()) - m_color_label_sys = wxColour(str); - } - - if (app_config->has("label_clr_modified")) { - auto str = app_config->get("label_clr_modified"); - if (!str.empty()) - m_color_label_modified = wxColour(str); - } - - // load mode markers colors - if (app_config->has("mode_palette")) { - const auto colors = app_config->get("mode_palette"); - if (!colors.empty()) { - m_mode_palette.clear(); - if (!unescape_strings_cstyle(colors, m_mode_palette)) - m_mode_palette = get_mode_default_palette(); - } - } -} - -void GUI_App::update_label_colours() -{ - for (Tab* tab : tabs_list) - tab->update_label_colours(); -} - -#ifdef _WIN32 -static bool is_focused(HWND hWnd) -{ - HWND hFocusedWnd = ::GetFocus(); - return hFocusedWnd && hWnd == hFocusedWnd; -} - -static bool is_default(wxWindow* win) -{ - wxTopLevelWindow* tlw = find_toplevel_parent(win); - if (!tlw) - return false; - - return win == tlw->GetDefaultItem(); -} -#endif - -void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) -{ -#ifdef _WIN32 - bool is_focused_button = false; - bool is_default_button = false; - if (wxButton* btn = dynamic_cast(window)) { - if (!(btn->GetWindowStyle() & wxNO_BORDER)) { - btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); - highlited = true; - } - // button marking - { - auto mark_button = [this, btn, highlited](const bool mark) { - if (btn->GetLabel().IsEmpty()) - btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); - else - btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); - btn->Refresh(); - btn->Update(); - }; - - // hovering - btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); - // focusing - btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); - - is_focused_button = is_focused(btn->GetHWND()); - is_default_button = is_default(btn); - if (is_focused_button || is_default_button) - mark_button(is_focused_button); - } - } - else if (wxTextCtrl* text = dynamic_cast(window)) { - if (text->GetBorder() != wxBORDER_SIMPLE) - text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); - } - else if (wxCheckListBox* list = dynamic_cast(window)) { - list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); - list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - for (size_t i = 0; i < list->GetCount(); i++) - if (wxOwnerDrawn* item = list->GetItem(i)) { - item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - item->SetTextColour(m_color_label_default); - } - return; - } - else if (dynamic_cast(window)) - window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); - - if (!just_font) - window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - if (!is_focused_button && !is_default_button) - window->SetForegroundColour(m_color_label_default); -#endif -} - -// recursive function for scaling fonts for all controls in Window -#ifdef _WIN32 -static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false) -{ - bool is_btn = dynamic_cast(window) != nullptr; - if (!(just_buttons_update && !is_btn)) - wxGetApp().UpdateDarkUI(window, is_btn); - - auto children = window->GetChildren(); - for (auto child : children) { - update_dark_children_ui(child); - } -} -#endif - -// Note: Don't use this function for Dialog contains ScalableButtons -void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/) -{ -#ifdef _WIN32 - update_dark_children_ui(dlg, just_buttons_update); -#endif -} -void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) -{ -#ifdef _WIN32 - UpdateDarkUI(dvc, highlited ? dark_mode() : false); -#ifdef _MSW_DARK_MODE - dvc->RefreshHeaderDarkMode(&m_normal_font); -#endif //_MSW_DARK_MODE - if (dvc->HasFlag(wxDV_ROW_LINES)) - dvc->SetAlternateRowColour(m_color_highlight_default); - if (dvc->GetBorder() != wxBORDER_SIMPLE) - dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE); -#endif -} - -void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) -{ -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(parent); - - auto children = parent->GetChildren(); - for (auto child : children) { - if (dynamic_cast(child)) - child->SetForegroundColour(m_color_label_default); - } -#endif -} - -void GUI_App::init_fonts() -{ - m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); - m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - -#ifdef __WXMAC__ - m_small_font.SetPointSize(11); - m_bold_font.SetPointSize(13); -#endif /*__WXMAC__*/ - - // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as - // DEFAULT in wxGtk. Use the TELETYPE family as a work-around - m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::update_fonts(const MainFrame *main_frame) -{ - /* Only normal and bold fonts are used for an application rescale, - * because of under MSW small and normal fonts are the same. - * To avoid same rescaling twice, just fill this values - * from rescaled MainFrame - */ - if (main_frame == nullptr) - main_frame = this->mainframe; - m_normal_font = main_frame->normal_font(); - m_small_font = m_normal_font; - m_bold_font = main_frame->normal_font().Bold(); - m_link_font = m_bold_font.Underlined(); - m_em_unit = main_frame->em_unit(); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::set_label_clr_modified(const wxColour& clr) -{ - if (m_color_label_modified == clr) - return; - m_color_label_modified = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_modified", str); - app_config->save(); -} - -void GUI_App::set_label_clr_sys(const wxColour& clr) -{ - if (m_color_label_sys == clr) - return; - m_color_label_sys = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_sys", str); - app_config->save(); -} - -const std::string& GUI_App::get_mode_btn_color(int mode_id) -{ - assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size()); - return m_mode_palette[mode_id]; -} - -std::vector GUI_App::get_mode_palette() -{ - return { wxColor(m_mode_palette[0]), - wxColor(m_mode_palette[1]), - wxColor(m_mode_palette[2]) }; -} - -void GUI_App::set_mode_palette(const std::vector& palette) -{ - bool save = false; - - for (size_t mode = 0; mode < palette.size(); ++mode) { - const wxColour& clr = palette[mode]; - std::string color_str = clr == wxTransparentColour ? std::string("") : encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - if (m_mode_palette[mode] != color_str) { - m_mode_palette[mode] = color_str; - save = true; - } - } - - if (save) { - mainframe->update_mode_markers(); - app_config->set("mode_palette", escape_strings_cstyle(m_mode_palette)); - app_config->save(); - } -} - -bool GUI_App::tabs_as_menu() const -{ - return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); -} - -wxSize GUI_App::get_min_size() const -{ - return wxSize(76*m_em_unit, 49 * m_em_unit); -} - -float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit*0.1f; -#endif // __APPLE__ - - const std::string& use_val = app_config->get("use_custom_toolbar_size"); - const std::string& val = app_config->get("custom_toolbar_size"); - const std::string& auto_val = app_config->get("auto_toolbar_size"); - - if (val.empty() || auto_val.empty() || use_val.empty()) - return icon_sc; - - int int_val = use_val == "0" ? 100 : atoi(val.c_str()); - // correct value in respect to auto_toolbar_size - int_val = std::min(atoi(auto_val.c_str()), int_val); - - if (is_limited && int_val < 50) - int_val = 50; - - return 0.01f * int_val * icon_sc; -} - -void GUI_App::set_auto_toolbar_icon_scale(float scale) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit * 0.1f; -#endif // __APPLE__ - - long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); - std::string val = std::to_string(int_val); - - app_config->set("auto_toolbar_size", val); -} - -// check user printer_presets for the containing information about "Print Host upload" -void GUI_App::check_printer_presets() -{ - std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); - if (preset_names.empty()) - return; - - wxString msg_text = _L("You have the following presets with saved options for \"Print Host upload\"") + ":"; - for (const std::string& preset_name : preset_names) - msg_text += "\n \"" + from_u8(preset_name) + "\","; - msg_text.RemoveLast(); - msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" - "Settings will be available in physical printers settings.") + "\n\n" + - _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" - "Note: This name can be changed later from the physical printers settings"); - - //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - - preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); -} - -void GUI_App::recreate_GUI(const wxString& msg_name) -{ - m_is_recreating_gui = true; - - mainframe->shutdown(); - - wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); - dlg.Pulse(); - dlg.Update(10, _L("Recreating") + dots); - - MainFrame *old_main_frame = mainframe; - mainframe = new MainFrame(); - if (is_editor()) - // hide settings tabs after first Layout - mainframe->select_tab(size_t(0)); - // Propagate model objects to object list. - sidebar().obj_list()->init_objects(); - SetTopWindow(mainframe); - - dlg.Update(30, _L("Recreating") + dots); - old_main_frame->Destroy(); - - dlg.Update(80, _L("Loading of current presets") + dots); - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - load_current_presets(); - mainframe->Show(true); - - dlg.Update(90, _L("Loading of a mode view") + dots); - - obj_list()->set_min_height(); - update_mode(); - - // #ys_FIXME_delete_after_testing Do we still need this ? -// CallAfter([]() { -// // Run the config wizard, don't offer the "reset user profile" checkbox. -// config_wizard_startup(true); -// }); - - m_is_recreating_gui = false; -} - -void GUI_App::system_info() -{ - SysInfoDialog dlg; - dlg.ShowModal(); -} - -void GUI_App::keyboard_shortcuts() -{ - KBShortcutsDialog dlg; - dlg.ShowModal(); -} - -// static method accepting a wxWindow object as first parameter -bool GUI_App::catch_error(std::function cb, - // wxMessageDialog* message_dialog, - const std::string& err /*= ""*/) -{ - if (!err.empty()) { - if (cb) - cb(); - // if (message_dialog) - // message_dialog->(err, "Error", wxOK | wxICON_ERROR); - show_error(/*this*/nullptr, err); - return true; - } - return false; -} - -// static method accepting a wxWindow object as first parameter -void fatal_error(wxWindow* parent) -{ - show_error(parent, ""); - // exit 1; // #ys_FIXME -} - -#ifdef _WIN32 - -#ifdef _MSW_DARK_MODE -static void update_scrolls(wxWindow* window) -{ - wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst(); - while (node) - { - wxWindow* win = node->GetData(); - if (dynamic_cast(win) || - dynamic_cast(win) || - dynamic_cast(win)) - NppDarkMode::SetDarkExplorerTheme(win->GetHWND()); - - update_scrolls(win); - node = node->GetNext(); - } -} -#endif //_MSW_DARK_MODE - - -#ifdef _MSW_DARK_MODE -void GUI_App::force_menu_update() -{ - NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1"); -} -#endif //_MSW_DARK_MODE - -void GUI_App::force_colors_update() -{ -#ifdef _MSW_DARK_MODE - NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); - if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl()) - NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND); - NppDarkMode::SetDarkTitleBar(mainframe->GetHWND()); - NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND()); -#endif //_MSW_DARK_MODE - m_force_colors_update = true; -} -#endif //_WIN32 - -// Called after the Preferences dialog is closed and the program settings are saved. -// Update the UI based on the current preferences. -void GUI_App::update_ui_from_settings() -{ - update_label_colours(); -#ifdef _WIN32 - // Upadte UI colors before Update UI from settings - if (m_force_colors_update) { - m_force_colors_update = false; - mainframe->force_color_changed(); - mainframe->diff_dialog.force_color_changed(); - mainframe->preferences_dialog->force_color_changed(); - mainframe->printhost_queue_dlg()->force_color_changed(); -#ifdef _MSW_DARK_MODE - update_scrolls(mainframe); - if (mainframe->is_dlg_layout()) { - // update for tabs bar - UpdateDarkUI(&mainframe->m_settings_dialog); - mainframe->m_settings_dialog.Fit(); - mainframe->m_settings_dialog.Refresh(); - // update scrollbars - update_scrolls(&mainframe->m_settings_dialog); - } -#endif //_MSW_DARK_MODE - } -#endif - mainframe->update_ui_from_settings(); -} - -void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) -{ - const std::string name = into_u8(window->GetName()); - - window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { - window_pos_save(window, name); - event.Skip(); - }); - - window_pos_restore(window, name, default_maximized); - - on_window_geometry(window, [=]() { - window_pos_sanitize(window); - }); -} - -void GUI_App::load_project(wxWindow *parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (3MF/AMF):"), - app_config->get_last_dir(), "", - file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const -{ - input_files.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one or more files (STL/3MF/STEP/OBJ/AMF/PRUSA):"), - from_u8(app_config->get_last_dir()), "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - dialog.GetPaths(input_files); -} - -void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"), - app_config->get_last_dir(), "", - file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -bool GUI_App::switch_language() -{ - if (select_language()) { - recreate_GUI(_L("Changing of an application language") + dots); - return true; - } else { - return false; - } -} - -#ifdef __linux__ -static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, - const wxLanguageInfo* system_language) -{ - constexpr size_t max_len = 50; - char path[max_len] = ""; - std::vector locales; - const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); - - // Call locale -a so we can parse the output to get the list of available locales - // We expect lines such as "en_US.utf8". Pick ones starting with the language code - // we are switching to. Lines with different formatting will be removed later. - FILE* fp = popen("locale -a", "r"); - if (fp != NULL) { - while (fgets(path, max_len, fp) != NULL) { - std::string line(path); - line = line.substr(0, line.find('\n')); - if (boost::starts_with(line, lang_prefix)) - locales.push_back(line); - } - pclose(fp); - } - - // locales now contain all candidates for this language. - // Sort them so ones containing anything about UTF-8 are at the end. - std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) - { - auto has_utf8 = [](const std::string & s) { - auto S = boost::to_upper_copy(s); - return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; - }; - return ! has_utf8(a) && has_utf8(b); - }); - - // Remove the suffix behind a dot, if there is one. - for (std::string& s : locales) - s = s.substr(0, s.find(".")); - - // We just hope that dear Linux "locale -a" returns country codes - // in ISO 3166-1 alpha-2 code (two letter) format. - // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes - // To be sure, remove anything not looking as expected - // (any number of lowercase letters, underscore, two uppercase letters). - locales.erase(std::remove_if(locales.begin(), - locales.end(), - [](const std::string& s) { - return ! std::regex_match(s, - std::regex("^[a-z]+_[A-Z]{2}$")); - }), - locales.end()); - - if (system_language) { - // Is there a candidate matching a country code of a system language? Move it to the end, - // while maintaining the order of matches, so that the best match ends up at the very end. - std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); - int cnt = locales.size(); - for (int i = 0; i < cnt; ++i) - if (locales[i].find(system_country) != std::string::npos) { - locales.emplace_back(std::move(locales[i])); - locales[i].clear(); - } - } - - // Now try them one by one. - for (auto it = locales.rbegin(); it != locales.rend(); ++ it) - if (! it->empty()) { - const std::string &locale = *it; - const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); - if (wxLocale::IsAvailable(lang->Language)) - return lang; - } - return language; -} -#endif - -int GUI_App::GetSingleChoiceIndex(const wxString& message, - const wxString& caption, - const wxArrayString& choices, - int initialSelection) -{ -#ifdef _WIN32 - wxSingleChoiceDialog dialog(nullptr, message, caption, choices); - wxGetApp().UpdateDlgDarkUI(&dialog); - - dialog.SetSelection(initialSelection); - return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1; -#else - return wxGetSingleChoiceIndex(message, caption, choices, initialSelection); -#endif -} - -// select language from the list of installed languages -bool GUI_App::select_language() -{ - wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); - std::vector language_infos; - language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); - for (size_t i = 0; i < translations.GetCount(); ++ i) { - const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); - if (langinfo != nullptr) - language_infos.emplace_back(langinfo); - } - sort_remove_duplicates(language_infos); - std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); - - wxArrayString names; - names.Alloc(language_infos.size()); - - // Some valid language should be selected since the application start up. - const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage()); - int init_selection = -1; - int init_selection_alt = -1; - int init_selection_default = -1; - for (size_t i = 0; i < language_infos.size(); ++ i) { - if (wxLanguage(language_infos[i]->Language) == current_language) - // The dictionary matches the active language and country. - init_selection = i; - else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) || - // if the active language is Slovak, mark the Czech language as active. - (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk")) - // The dictionary matches the active language, it does not necessarily match the country. - init_selection_alt = i; - if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") - // This will be the default selection if the active language does not match any dictionary. - init_selection_default = i; - names.Add(language_infos[i]->Description); - } - if (init_selection == -1) - // This is the dictionary matching the active language. - init_selection = init_selection_alt; - if (init_selection != -1) - // This is the language to highlight in the choice dialog initially. - init_selection_default = init_selection; - - const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); - // Try to load a new language. - if (index != -1 && (init_selection == -1 || init_selection != index)) { - const wxLanguageInfo *new_language_info = language_infos[index]; - if (this->load_language(new_language_info->CanonicalName, false)) { - // Save language at application config. - // Which language to save as the selected dictionary language? - // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its - // stability in the future: - // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - // 2) Current locale language may not match the dictionary name, see GH issue #3901 - // m_wxLocale->GetCanonicalName() - // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. - app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data()); - app_config->save(); - return true; - } - } - - return false; -} - -// Load gettext translation files and activate them at the start of the application, -// based on the "translation_language" key stored in the application config. -bool GUI_App::load_language(wxString language, bool initial) -{ - if (initial) { - // There is a static list of lookup path prefixes in wxWidgets. Add ours. - wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir())); - // Get the active language from PrusaSlicer.ini, or empty string if the key does not exist. - language = app_config->get("translation_language"); - if (! language.empty()) - BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language; - - // Get the system language. - { - const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); - if (lang_system != wxLANGUAGE_UNKNOWN) { - m_language_info_system = wxLocale::GetLanguageInfo(lang_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data(); - } - } - { - // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. - wxLocale temp_locale; -#ifdef __WXOSX__ - // ysFIXME - temporary workaround till it isn't fixed in wxWidgets: - // Use English as an initial language, because of under OSX it try to load "inappropriate" language for wxLANGUAGE_DEFAULT. - // For example in our case it's trying to load "en_CZ" and as a result PrusaSlicer catch warning message. - // But wxWidgets guys work on it. - temp_locale.Init(wxLANGUAGE_ENGLISH); -#else - temp_locale.Init(); -#endif // __WXOSX__ - // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). - wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); - // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer - // and try to match them with the system specific "preferred languages". - // There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage(). - // The last parameter gets added to the list of detected dictionaries. This is a workaround - // for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way. - wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - if (! best_language.IsEmpty()) { - m_language_info_best = wxLocale::FindLanguageInfo(best_language); - BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data(); - } - #ifdef __linux__ - wxString lc_all; - if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) { - // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL. - // Disregard the "best" suggestion in case LC_ALL is provided. - m_language_info_best = nullptr; - } - #endif - } - } - - const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language); - if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) { - // Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI). - language_info = nullptr; - BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data(); - } - - if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) { - BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data(); - language_info = nullptr; - } - - if (language_info == nullptr) { - // PrusaSlicer does not support the Right to Left languages yet. - if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_system; - if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_best; - if (language_info == nullptr) - language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US); - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data(); - - // Alternate language code. - wxLanguage language_dict = wxLanguage(language_info->Language); - if (language_info->CanonicalName.BeforeFirst('_') == "sk") { - // Slovaks understand Czech well. Give them the Czech translation. - language_dict = wxLANGUAGE_CZECH; - BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language"; - } - - // Select language for locales. This language may be different from the language of the dictionary. - if (language_info == m_language_info_best || language_info == m_language_info_system) { - // The current language matches user's default profile exactly. That's great. - } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) { - // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language. - // This allows a Swiss guy to use a German dictionary without forcing him to German locales. - language_info = m_language_info_best; - } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_')) - language_info = m_language_info_system; - -#ifdef __linux__ - // If we can't find this locale , try to use different one for the language - // instead of just reporting that it is impossible to switch. - if (! wxLocale::IsAvailable(language_info->Language)) { - std::string original_lang = into_u8(language_info->CanonicalName); - language_info = linux_get_existing_locale_language(language_info, m_language_info_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.") - % original_lang % language_info->CanonicalName.ToUTF8().data(); - } -#endif - - if (! wxLocale::IsAvailable(language_info->Language)) { - // Loading the language dictionary failed. - wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed."; -#if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux system - message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"; -#endif - if (initial) - message + "\n\nApplication will close."; - wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); - if (initial) - std::exit(EXIT_FAILURE); - else - return false; - } - - // Release the old locales, create new locales. - //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now. - m_wxLocale.release(); - m_wxLocale = Slic3r::make_unique(); - m_wxLocale->Init(language_info->Language); - // Override language at the active wxTranslations class (which is stored in the active m_wxLocale) - // to load possibly different dictionary, for example, load Czech dictionary for Slovak language. - wxTranslations::Get()->SetLanguage(language_dict); - { - // UKR Localization specific workaround till the wxWidgets doesn't fixed: - // From wxWidgets 3.1.6 calls setlocation(0, wxInfoLanguage->LocaleTag), see (https://github.com/prusa3d/wxWidgets/commit/deef116a09748796711d1e3509965ee208dcdf0b#diff-7de25e9a71c4dce61bbf76492c589623d5b93fd1bb105ceaf0662075d15f4472), - // where LocaleTag is a Tag of locale in BCP 47 - like notation. - // For Ukrainian Language LocaleTag == "uk". - // But setlocale(0, "uk") returns "English_United Kingdom.1252" instead of "uk", - // and, as a result, locales are set to English_United Kingdom - - if (language_info->CanonicalName == "uk") - setlocale(0, language_info->GetCanonicalWithRegion().data()); - } - m_wxLocale->AddCatalog(SLIC3R_APP_KEY); - m_imgui->set_language(into_u8(language_info->CanonicalName)); - //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. - //wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); - return true; -} - -Tab* GUI_App::get_tab(Preset::Type type) -{ - for (Tab* tab: tabs_list) - if (tab->type() == type) - return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab - return nullptr; -} - -ConfigOptionMode GUI_App::get_mode() -{ - if (!app_config->has("view_mode")) - return comSimple; - - const auto mode = app_config->get("view_mode"); - return mode == "expert" ? comExpert : - mode == "simple" ? comSimple : comAdvanced; -} - -void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) -{ - const std::string mode_str = mode == comExpert ? "expert" : - mode == comSimple ? "simple" : "advanced"; - app_config->set("view_mode", mode_str); - app_config->save(); - update_mode(); -} - -// Update view mode according to selected menu -void GUI_App::update_mode() -{ - sidebar().update_mode(); - -#ifdef _WIN32 //_MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); -#endif - - for (auto tab : tabs_list) - tab->update_mode(); - - plater()->update_menus(); - plater()->canvas3D()->update_gizmos_on_off_state(); -} - -void GUI_App::add_config_menu(wxMenuBar *menu) -{ - auto local_menu = new wxMenu(); - wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); - - const auto config_wizard_name = _(ConfigWizard::name(true)); - const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str()); - // Cmd+, is standard on OS X - what about other operating systems? - if (is_editor()) { - local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); - local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); - local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); - local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - //if (DesktopIntegrationDialog::integration_possible()) - local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); -#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - local_menu->AppendSeparator(); - } - local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + -#ifdef __APPLE__ - "\tCtrl+,", -#else - "\tCtrl+P", -#endif - _L("Application preferences")); - wxMenu* mode_menu = nullptr; - if (is_editor()) { - local_menu->AppendSeparator(); - mode_menu = new wxMenu(); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); -// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); - - local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); - } - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); - if (is_editor()) { - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer")); - // TODO: for when we're able to flash dictionaries - // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a Prusa printer")); - } - - local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { - switch (event.GetId() - config_id_base) { - case ConfigMenuWizard: - run_wizard(ConfigWizard::RR_USER); - break; - case ConfigMenuUpdateConf: - check_updates(true); - break; - case ConfigMenuUpdateApp: - app_version_check(true); - break; -#ifdef __linux__ - case ConfigMenuDesktopIntegration: - show_desktop_integration_dialog(); - break; -#endif - case ConfigMenuTakeSnapshot: - // Take a configuration snapshot. - if (wxString action_name = _L("Taking a configuration snapshot"); - check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { - wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); - UpdateDlgDarkUI(&dlg); - - // set current normal font for dialog children, - // because of just dlg.SetFont(normal_font()) has no result; - for (auto child : dlg.GetChildren()) - child->SetFont(normal_font()); - - if (dlg.ShowModal() == wxID_OK) - if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( - *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); - snapshot != nullptr) - app_config->set("on_snapshot", snapshot->id); - } - break; - case ConfigMenuSnapshots: - if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { - std::string on_snapshot; - if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) - on_snapshot = app_config->get("on_snapshot"); - ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); - dlg.ShowModal(); - if (!dlg.snapshot_to_activate().empty()) { - if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && - ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", - GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) - break; - try { - app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); - // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system - // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users - // are known to be creative and mess with the config files in various ways. - if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); - ! all_substitutions.empty()) - show_substitutions_info(all_substitutions); - - // Load the currently selected preset into the GUI, update the preset selection box. - load_current_presets(); - } catch (std::exception &ex) { - GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); - } - } - } - break; - case ConfigMenuPreferences: - { - open_preferences(); - break; - } - case ConfigMenuLanguage: - { - /* Before change application language, let's check unsaved changes on 3D-Scene - * and draw user's attention to the application restarting after a language change - */ - { - // the dialog needs to be destroyed before the call to switch_language() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); - title += " - " + _L("Language selection"); - //wxMessageDialog dialog(nullptr, - MessageDialog dialog(nullptr, - _L("Switching the language will trigger application restart.\n" - "You will lose content of the plater.") + "\n\n" + - _L("Do you want to proceed?"), - title, - wxICON_QUESTION | wxOK | wxCANCEL); - if (dialog.ShowModal() == wxID_CANCEL) - return; - } - - switch_language(); - break; - } - case ConfigMenuFlashFirmware: - FirmwareDialog::run(mainframe); - break; - default: - break; - } - }); - - using std::placeholders::_1; - - if (mode_menu != nullptr) { - auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); - } - - menu->Append(local_menu, _L("&Configuration")); -} - -void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) -{ - mainframe->preferences_dialog->show(highlight_option, tab_name); - - if (mainframe->preferences_dialog->recreate_GUI()) - recreate_GUI(_L("Restart application") + dots); - -#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) -#else - if (mainframe->preferences_dialog->seq_top_layer_only_changed()) -#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); - -#ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 - - if (mainframe->preferences_dialog->settings_layout_changed()) { - // hide full main_sizer for mainFrame - mainframe->GetSizer()->Show(false); - mainframe->update_layout(); - mainframe->select_tab(size_t(0)); - } -} - -bool GUI_App::has_unsaved_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) - return true; - } - return false; -} - -bool GUI_App::has_current_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - return true; - } - return false; -} - -void GUI_App::update_saved_preset_from_current_preset() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->update_saved_preset_from_current_preset(); - } -} - -std::vector GUI_App::get_active_preset_collections() const -{ - std::vector ret; - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) - ret.push_back(tab->get_presets()); - return ret; -} - -// To notify the user whether he is aware that some preset changes will be lost, -// UnsavedChangesDialog: "Discard / Save / Cancel" -// This is called when: -// - Close Application & Current project isn't saved -// - Load Project & Current project isn't saved -// - Undo / Redo with change of print technologie -// - Loading snapshot -// - Loading config_file/bundle -// UnsavedChangesDialog: "Don't save / Save / Cancel" -// This is called when: -// - Exporting config_bundle -// - Taking snapshot -bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) -{ - if (has_current_preset_changes()) { - const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; - int act_buttons = ActionButtons::SAVE; - if (dont_save_insted_of_discard) - act_buttons |= ActionButtons::DONT_SAVE; - UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - if (dlg.save_preset()) // save selected changes - { - for (const std::pair& nt : dlg.get_names_and_types()) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - load_current_presets(false); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - MessageDialog(nullptr, dlg.msg_success_saved_modifications(dlg.get_names_and_types().size())).ShowModal(); - } - } - - return true; -} - -void GUI_App::apply_keeped_preset_modifications() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->apply_config_from_cache(); - } - load_current_presets(false); -} - -// This is called when creating new project or load another project -// OR close ConfigWizard -// to ask the user what should we do with unsaved changes for presets. -// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" -// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed -bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) -{ - if (has_current_preset_changes()) { - bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; - - const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; - UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - auto reset_modifications = [this, is_called_from_configwizard]() { - if (is_called_from_configwizard) - return; // no need to discared changes. It will be done fromConfigWizard closing - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - tab->m_presets->discard_current_changes(); - } - load_current_presets(false); - }; - - if (dlg.discard()) - reset_modifications(); - else // save selected changes - { - const auto& preset_names_and_types = dlg.get_names_and_types(); - if (dlg.save_preset()) { - for (const std::pair& nt : preset_names_and_types) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - wxString text = dlg.msg_success_saved_modifications(preset_names_and_types.size()); - if (!is_called_from_configwizard) - text += "\n\n" + _L("For new project all modifications will be reseted"); - - MessageDialog(nullptr, text).ShowModal(); - reset_modifications(); - } - else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { - // execute this part of code only if not all modifications are keeping to the new project - // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected - for (const std::pair& nt : preset_names_and_types) { - Preset::Type type = nt.second; - Tab* tab = get_tab(type); - std::vector selected_options = dlg.get_selected_options(type); - if (type == Preset::TYPE_PRINTER) { - auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); - if (it != selected_options.end()) { - // erase "extruders_count" option from the list - selected_options.erase(it); - // cache the extruders count - static_cast(tab)->cache_extruder_cnt(); - } - } - tab->cache_config_diff(selected_options); - if (!is_called_from_configwizard) - tab->m_presets->discard_current_changes(); - } - if (is_called_from_configwizard) - *postponed_apply_of_keeped_changes = true; - else - apply_keeped_preset_modifications(); - } - } - } - - return true; -} - -bool GUI_App::can_load_project() -{ - int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); - if (saved_project == wxID_CANCEL || - (plater()->is_project_dirty() && saved_project == wxID_NO && - !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved.")))) - return false; - return true; -} - -bool GUI_App::check_print_host_queue() -{ - wxString dirty; - std::vector> jobs; - // Get ongoing jobs from dialog - mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); - if (jobs.empty()) - return true; - // Show dialog - wxString job_string = wxString(); - for (const auto& job : jobs) { - job_string += format_wxstr(" %1% : %2% \n", job.first, job.second); - } - wxString message; - message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); - //wxMessageDialog dialog(mainframe, - MessageDialog dialog(mainframe, - message, - wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), - wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - if (dialog.ShowModal() == wxID_YES) - return true; - - // TODO: If already shown, bring forward - mainframe->m_printhost_queue_dlg->Show(); - return false; -} - -bool GUI_App::checked_tab(Tab* tab) -{ - bool ret = true; - if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) - ret = false; - return ret; -} - -// Update UI / Tabs to reflect changes in the currently loaded presets -void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) -{ - // check printer_presets for the containing information about "Print Host upload" - // and create physical printer from it, if any exists - if (check_printer_presets_) - check_printer_presets(); - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - this->plater()->set_printer_technology(printer_technology); - for (Tab *tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) { - if (tab->type() == Preset::TYPE_PRINTER) { - static_cast(tab)->update_pages(); - // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). - this->plater()->force_print_bed_update(); - } - tab->load_current_preset(); - } -} - -bool GUI_App::OnExceptionInMainLoop() -{ - generic_exception_handle(); - return false; -} - -#ifdef __APPLE__ -// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run -// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App -// to a G-code viewer. -void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames) -{ - size_t num_gcodes = 0; - for (const wxString &filename : fileNames) - if (is_gcode_file(into_u8(filename))) - ++ num_gcodes; - if (fileNames.size() == num_gcodes) { - // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder, - // just G-codes were passed. Switch to G-code viewer mode. - m_app_mode = EAppMode::GCodeViewer; - unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/"); - if(app_config != nullptr) - delete app_config; - app_config = nullptr; - init_app_config(); - } - wxApp::OSXStoreOpenFiles(fileNames); -} -// wxWidgets override to get an event on open files. -void GUI_App::MacOpenFiles(const wxArrayString &fileNames) -{ - std::vector files; - std::vector gcode_files; - std::vector non_gcode_files; - for (const auto& filename : fileNames) { - if (is_gcode_file(into_u8(filename))) - gcode_files.emplace_back(filename); - else { - files.emplace_back(into_u8(filename)); - non_gcode_files.emplace_back(filename); - } - } - if (m_app_mode == EAppMode::GCodeViewer) { - // Running in G-code viewer. - // Load the first G-code into the G-code viewer. - // Or if no G-codes, send other files to slicer. - if (! gcode_files.empty()) { - if (m_post_initialized) - this->plater()->load_gcode(gcode_files.front()); - else - this->init_params->input_files = { into_u8(gcode_files.front()) }; - } - if (!non_gcode_files.empty()) - start_new_slicer(non_gcode_files, true); - } else { - if (! files.empty()) { - if (m_post_initialized) { - wxArrayString input_files; - for (size_t i = 0; i < non_gcode_files.size(); ++i) - input_files.push_back(non_gcode_files[i]); - this->plater()->load_files(input_files); - } else { - for (const auto &f : non_gcode_files) - this->init_params->input_files.emplace_back(into_u8(f)); - } - } - for (const wxString &filename : gcode_files) - start_new_gcodeviewer(&filename); - } -} - -void GUI_App::MacOpenURL(const wxString& url) -{ - if (app_config && app_config->get("downloader_url_registered") != "1") - { - BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; - return; - } - start_download(boost::nowide::narrow(url)); -} - -#endif /* __APPLE */ - -Sidebar& GUI_App::sidebar() -{ - return plater_->sidebar(); -} - -ObjectManipulation* GUI_App::obj_manipul() -{ - // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) - return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr; -} - -ObjectSettings* GUI_App::obj_settings() -{ - return sidebar().obj_settings(); -} - -ObjectList* GUI_App::obj_list() -{ - return sidebar().obj_list(); -} - -ObjectLayers* GUI_App::obj_layers() -{ - return sidebar().obj_layers(); -} - -Plater* GUI_App::plater() -{ - return plater_; -} - -const Plater* GUI_App::plater() const -{ - return plater_; -} - -Model& GUI_App::model() -{ - return plater_->model(); -} -wxBookCtrlBase* GUI_App::tab_panel() const -{ - return mainframe->m_tabpanel; -} - -NotificationManager* GUI_App::notification_manager() -{ - return plater_->get_notification_manager(); -} - -GalleryDialog* GUI_App::gallery_dialog() -{ - return mainframe->gallery_dialog(); -} - -Downloader* GUI_App::downloader() -{ - return m_downloader.get(); -} - -// extruders count from selected printer preset -int GUI_App::extruders_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_selected_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -// extruders count from edited printer preset -int GUI_App::extruders_edited_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_edited_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -wxString GUI_App::current_language_code_safe() const -{ - // Translate the language code to a code, for which Prusa Research maintains translations. - const std::map mapping { - { "cs", "cs_CZ", }, - { "sk", "cs_CZ", }, - { "de", "de_DE", }, - { "es", "es_ES", }, - { "fr", "fr_FR", }, - { "it", "it_IT", }, - { "ja", "ja_JP", }, - { "ko", "ko_KR", }, - { "pl", "pl_PL", }, - { "uk", "uk_UA", }, - { "zh", "zh_CN", }, - { "ru", "ru_RU", }, - }; - wxString language_code = this->current_language_code().BeforeFirst('_'); - auto it = mapping.find(language_code); - if (it != mapping.end()) - language_code = it->second; - else - language_code = "en_US"; - return language_code; -} - -void GUI_App::open_web_page_localized(const std::string &http_address) -{ - open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); -} - -// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). -// Because of we can't to print the multi-part objects with SLA technology. -bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) -{ - if (model_has_multi_part_objects(model())) { - show_info(nullptr, - _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } - if (model_has_connectors(model())) { - show_info(nullptr, - _L("SLA technology doesn't support cut with connectors") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } - return true; -} - -bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) -{ - wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - - if (reason == ConfigWizard::RR_USER) { - // Cancel sync before starting wizard to prevent two downloads at same time - preset_updater->cancel_sync(); - if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) - return false; - } - - auto wizard = new ConfigWizard(mainframe); - const bool res = wizard->run(reason, start_page); - - if (res) { - load_current_presets(); - - // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() - if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) - may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); - } - - return res; -} - -void GUI_App::show_desktop_integration_dialog() -{ -#ifdef __linux__ - //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - DesktopIntegrationDialog dialog(mainframe); - dialog.ShowModal(); -#endif //__linux__ -} - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -void GUI_App::gcode_thumbnails_debug() -{ - const std::string BEGIN_MASK = "; thumbnail begin"; - const std::string END_MASK = "; thumbnail end"; - std::string gcode_line; - bool reading_image = false; - unsigned int width = 0; - unsigned int height = 0; - - wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dialog.ShowModal() != wxID_OK) - return; - - std::string in_filename = into_u8(dialog.GetPath()); - std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); - - boost::nowide::ifstream in_file(in_filename.c_str()); - std::vector rows; - std::string row; - if (in_file.good()) - { - while (std::getline(in_file, gcode_line)) - { - if (in_file.good()) - { - if (boost::starts_with(gcode_line, BEGIN_MASK)) - { - reading_image = true; - gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); - std::string::size_type x_pos = gcode_line.find('x'); - std::string width_str = gcode_line.substr(0, x_pos); - width = (unsigned int)::atoi(width_str.c_str()); - std::string height_str = gcode_line.substr(x_pos + 1); - height = (unsigned int)::atoi(height_str.c_str()); - row.clear(); - } - else if (reading_image && boost::starts_with(gcode_line, END_MASK)) - { - std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; - boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); - if (out_file.good()) - { - std::string decoded; - decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); - decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); - - out_file.write(decoded.c_str(), decoded.size()); - out_file.close(); - } - - reading_image = false; - width = 0; - height = 0; - rows.clear(); - } - else if (reading_image) - row += gcode_line.substr(2); - } - } - - in_file.close(); - } -} -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - WindowMetrics metrics = WindowMetrics::from_window(window); - app_config->set(config_key, metrics.serialize()); - app_config->save(); -} - -void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - if (! app_config->has(config_key)) { - window->Maximize(default_maximized); - return; - } - - auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); - if (! metrics) { - window->Maximize(default_maximized); - return; - } - - const wxRect& rect = metrics->get_rect(); - - if (app_config->get("restore_win_position") == "1") { - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); - app_config->save(); - window->SetPosition(rect.GetPosition()); - - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); - app_config->save(); - window->SetSize(rect.GetSize()); - - // revert "restore_win_position" value if application wasn't crashed - app_config->set("restore_win_position", "1"); - app_config->save(); - } - else - window->CenterOnScreen(); - - window->Maximize(metrics->get_maximized()); -} - -void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) -{ - /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window); - wxRect display; - if (display_idx == wxNOT_FOUND) { - display = wxDisplay(0u).GetClientArea(); - window->Move(display.GetTopLeft()); - } else { - display = wxDisplay(display_idx).GetClientArea(); - } - - auto metrics = WindowMetrics::from_window(window); - metrics.sanitize_for_display(display); - if (window->GetScreenRect() != metrics.get_rect()) { - window->SetSize(metrics.get_rect()); - } -} - -bool GUI_App::config_wizard_startup() -{ - if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { - run_wizard(ConfigWizard::RR_DATA_EMPTY); - return true; - } else if (get_app_config()->legacy_datadir()) { - // Looks like user has legacy pre-vendorbundle data directory, - // explain what this is and run the wizard - - MsgDataLegacy dlg; - dlg.ShowModal(); - - run_wizard(ConfigWizard::RR_DATA_LEGACY); - return true; - } - return false; -} - -bool GUI_App::check_updates(const bool verbose) -{ - PresetUpdater::UpdateResult updater_result; - try { - updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); - if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { - mainframe->Close(); - // Applicaiton is closing. - return false; - } - else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { - m_app_conf_exists = true; - } - else if (verbose && updater_result == PresetUpdater::R_NOOP) { - MsgNoUpdates dlg; - dlg.ShowModal(); - } - } - catch (const std::exception & ex) { - show_error(nullptr, ex.what()); - } - // Applicaiton will continue. - return true; -} - -bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) -{ - bool launch = true; - - // warning dialog containes a "Remember my choice" checkbox - std::string option_key = "suppress_hyperlinks"; - if (force_remember_choice || app_config->get(option_key).empty()) { - if (app_config->get(option_key).empty()) { - RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - dialog.ShowCheckBox(_L("Remember my choice")); - auto answer = dialog.ShowModal(); - launch = answer == wxID_YES; - if (dialog.IsCheckBoxChecked()) { - wxString preferences_item = _L("Suppress to open hyperlink in browser"); - wxString msg = - _L("PrusaSlicer will remember your choice.") + "\n\n" + - _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + - format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); - - MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - if (msg_dlg.ShowModal() == wxID_CANCEL) - return false; - app_config->set(option_key, answer == wxID_NO ? "1" : "0"); - } - } - if (launch) - launch = app_config->get(option_key) != "1"; - } - // warning dialog doesn't containe a "Remember my choice" checkbox - // and will be shown only when "Suppress to open hyperlink in browser" is ON. - else if (app_config->get(option_key) == "1") { - MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - launch = dialog.ShowModal() == wxID_YES; - } - - return launch && wxLaunchDefaultBrowser(url, flags); -} - -// static method accepting a wxWindow object as first parameter -// void warning_catcher{ -// my($self, $message_dialog) = @_; -// return sub{ -// my $message = shift; -// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; -// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); -// $message_dialog -// ? $message_dialog->(@params) -// : Wx::MessageDialog->new($self, @params)->ShowModal; -// }; -// } - -// Do we need this function??? -// void GUI_App::notify(message) { -// auto frame = GetTopWindow(); -// // try harder to attract user attention on OS X -// if (!frame->IsActive()) -// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); -// -// // There used to be notifier using a Growl application for OSX, but Growl is dead. -// // The notifier also supported the Linux X D - bus notifications, but that support was broken. -// //TODO use wxNotificationMessage ? -// } - - -#ifdef __WXMSW__ -void GUI_App::associate_3mf_files() -{ - associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); -} - -void GUI_App::associate_stl_files() -{ - associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); -} - -void GUI_App::associate_gcode_files() -{ - associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); -} -#endif // __WXMSW__ - -void GUI_App::on_version_read(wxCommandEvent& evt) -{ - app_config->set("version_online", into_u8(evt.GetString())); - app_config->save(); - std::string opt = app_config->get("notify_release"); - if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { - return; - } - if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { - return; - } - // notification - /* - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) - , _u8L("See Download page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } - ); - */ - // updater - // read triggered_by_user that was set when calling GUI_App::app_version_check - app_updater(m_app_updater->get_triggered_by_user()); -} - -void GUI_App::app_updater(bool from_user) -{ - DownloadAppData app_data = m_app_updater->get_app_data(); - - if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) - { - BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; - MsgNoAppUpdates no_update_dialog; - no_update_dialog.ShowModal(); - return; - - } - - assert(!app_data.url.empty()); - assert(!app_data.target_path.empty()); - - // dialog with new version info - AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); - auto dialog_result = dialog.ShowModal(); - // checkbox "do not show again" - if (dialog.disable_version_check()) { - app_config->set("notify_release", "none"); - } - // Doesn't wish to update - if (dialog_result != wxID_OK) { - return; - } - // dialog with new version download (installer or app dependent on system) including path selection - AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); - dialog_result = dwnld_dlg.ShowModal(); - // Doesn't wish to download - if (dialog_result != wxID_OK) { - return; - } - app_data.target_path =dwnld_dlg.get_download_path(); - - // start download - this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); - app_data.start_after = dwnld_dlg.run_after_download(); - m_app_updater->set_app_data(std::move(app_data)); - m_app_updater->sync_download(); -} - -void GUI_App::app_version_check(bool from_user) -{ - if (from_user) { - if (m_app_updater->get_download_ongoing()) { - MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); - if (msgdlg.ShowModal() != wxID_YES) - return; - } - } - std::string version_check_url = app_config->version_check_url(); - m_app_updater->sync_version(version_check_url, from_user); -} - -void GUI_App::start_download(std::string url) -{ - if (!plater_) { - BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr."; - return; - } - //lets always init so if the download dest folder was changed, new dest is used - boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); - if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { - std::string msg = _utf8("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard."); - BOOST_LOG_TRIVIAL(error) << msg; - show_error(nullptr, msg); - return; - } - m_downloader->init(dest_folder); - m_downloader->start_download(url); -} - -} // GUI -} //Slic3r +#include "libslic3r/Technologies.hpp" +#include "GUI_App.hpp" +#include "GUI_Init.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_Factories.hpp" +#include "format.hpp" +#include "I18N.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/I18N.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" + +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "3DScene.hpp" +#include "MainFrame.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" + +#include "../Utils/PresetUpdater.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/Process.hpp" +#include "../Utils/MacDarkMode.hpp" +#include "../Utils/AppUpdater.hpp" +#include "../Utils/WinRegistry.hpp" +#include "slic3r/Config/Snapshot.hpp" +#include "ConfigSnapshotDialog.hpp" +#include "FirmwareDialog.hpp" +#include "Preferences.hpp" +#include "Tab.hpp" +#include "SysInfoDialog.hpp" +#include "KBShortcutsDialog.hpp" +#include "UpdateDialogs.hpp" +#include "Mouse3DController.hpp" +#include "RemovableDriveManager.hpp" +#include "InstanceCheck.hpp" +#include "NotificationManager.hpp" +#include "UnsavedChangesDialog.hpp" +#include "SavePresetDialog.hpp" +#include "PrintHostDialogs.hpp" +#include "DesktopIntegrationDialog.hpp" +#include "SendSystemInfoDialog.hpp" +#include "Downloader.hpp" + +#include "BitmapCache.hpp" +#include "Notebook.hpp" + +#ifdef __WXMSW__ +#include +#include +#ifdef _MSW_DARK_MODE +#include +#endif // _MSW_DARK_MODE +#endif +#ifdef _WIN32 +#include +#endif + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG +#include +#include +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + +// Needed for forcing menu icons back under gtk2 and gtk3 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + #include +#endif + +using namespace std::literals; + +namespace Slic3r { +namespace GUI { + +class MainFrame; + +class SplashScreen : public wxSplashScreen +{ +public: + SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) + : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, +#ifdef __APPLE__ + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP +#else + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR +#endif // !__APPLE__ + ) + { + wxASSERT(bitmap.IsOk()); + +// int init_dpi = get_dpi_for_window(this); + this->SetPosition(pos); + // The size of the SplashScreen can be hanged after its moving to another display + // So, update it from a bitmap size + this->SetClientSize(bitmap.GetWidth(), bitmap.GetHeight()); + this->CenterOnScreen(); +// int new_dpi = get_dpi_for_window(this); + +// m_scale = (float)(new_dpi) / (float)(init_dpi); + m_main_bitmap = bitmap; + +// scale_bitmap(m_main_bitmap, m_scale); + + // init constant texts and scale fonts + init_constant_text(); + + // this font will be used for the action string + m_action_font = m_constant_text.credits_font.Bold(); + + // draw logo and constant info text + Decorate(m_main_bitmap); + } + + void SetText(const wxString& text) + { + set_bitmap(m_main_bitmap); + if (!text.empty()) { + wxBitmap bitmap(m_main_bitmap); + + wxMemoryDC memDC; + memDC.SelectObject(bitmap); + + memDC.SetFont(m_action_font); + memDC.SetTextForeground(wxColour(237, 107, 33)); + memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position); + + memDC.SelectObject(wxNullBitmap); + set_bitmap(bitmap); +#ifdef __WXOSX__ + // without this code splash screen wouldn't be updated under OSX + wxYield(); +#endif + } + } + + static wxBitmap MakeBitmap(wxBitmap bmp) + { + if (!bmp.IsOk()) + return wxNullBitmap; + + // create dark grey background for the splashscreen + // It will be 5/3 of the weight of the bitmap + int width = lround((double)5 / 3 * bmp.GetWidth()); + int height = bmp.GetHeight(); + + wxImage image(width, height); + unsigned char* imgdata_ = image.GetData(); + for (int i = 0; i < width * height; ++i) { + *imgdata_++ = 51; + *imgdata_++ = 51; + *imgdata_++ = 51; + } + + wxBitmap new_bmp(image); + + wxMemoryDC memDC; + memDC.SelectObject(new_bmp); + memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); + + return new_bmp; + } + + void Decorate(wxBitmap& bmp) + { + if (!bmp.IsOk()) + return; + + // draw text to the box at the left of the splashscreen. + // this box will be 2/5 of the weight of the bitmap, and be at the left. + int width = lround(bmp.GetWidth() * 0.4); + + // load bitmap for logo + BitmapCache bmp_cache; + int logo_size = lround(width * 0.25); + wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); + + wxCoord margin = int(m_scale * 20); + + wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight())); + banner_rect.Deflate(margin, 2 * margin); + + // use a memory DC to draw directly onto the bitmap + wxMemoryDC memDc(bmp); + + // draw logo + memDc.DrawBitmap(logo_bmp, margin, margin, true); + + // draw the (white) labels inside of our black box (at the left of the splashscreen) + memDc.SetTextForeground(wxColour(255, 255, 255)); + + memDc.SetFont(m_constant_text.title_font); + memDc.DrawLabel(m_constant_text.title, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); + + int title_height = memDc.GetTextExtent(m_constant_text.title).GetY(); + banner_rect.SetTop(banner_rect.GetTop() + title_height); + banner_rect.SetHeight(banner_rect.GetHeight() - title_height); + + memDc.SetFont(m_constant_text.version_font); + memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); + int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); + + memDc.SetFont(m_constant_text.credits_font); + memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); + int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); + int text_height = memDc.GetTextExtent("text").GetY(); + + // calculate position for the dynamic text + int logo_and_header_height = margin + logo_size + title_height + version_height; + m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height); + } + +private: + wxBitmap m_main_bitmap; + wxFont m_action_font; + int m_action_line_y_position; + float m_scale {1.0}; + + struct ConstantText + { + wxString title; + wxString version; + wxString credits; + + wxFont title_font; + wxFont version_font; + wxFont credits_font; + + void init(wxFont init_font) + { + // title + title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; + + // dynamically get the version to display + version = _L("Version") + " " + std::string(SLIC3R_VERSION); + + // credits infornation + credits = title + " " + + _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + + _L("Developed by Prusa Research.") + "\n\n" + + title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + ".\n\n" + + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + + _L("Artwork model by Creative Tools"); + + title_font = version_font = credits_font = init_font; + } + } + m_constant_text; + + void init_constant_text() + { + m_constant_text.init(get_default_font(this)); + + // As default we use a system font for current display. + // Scale fonts in respect to banner width + + int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins + + float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX(); + scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale); + + float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX(); + scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale); + + // The width of the credits information string doesn't respect to the banner width some times. + // So, scale credits_font in the respect to the longest string width + int longest_string_width = word_wrap_string(m_constant_text.credits); + float font_scale = (float)text_banner_width / longest_string_width; + scale_font(m_constant_text.credits_font, font_scale); + } + + void set_bitmap(wxBitmap& bmp) + { + m_window->SetBitmap(bmp); + m_window->Refresh(); + m_window->Update(); + } + + void scale_bitmap(wxBitmap& bmp, float scale) + { + if (scale == 1.0) + return; + + wxImage image = bmp.ConvertToImage(); + if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) + return; + + int width = int(scale * image.GetWidth()); + int height = int(scale * image.GetHeight()); + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + bmp = wxBitmap(std::move(image)); + } + + void scale_font(wxFont& font, float scale) + { +#ifdef __WXMSW__ + // Workaround for the font scaling in respect to the current active display, + // not for the primary display, as it's implemented in Font.cpp + // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp + // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) + wxNativeFontInfo nfi= *font.GetNativeFontInfo(); + float pointSizeNew = wxDisplay(this).GetScaleFactor() * scale * font.GetPointSize(); + nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); + nfi.pointSize = pointSizeNew; + font = wxFont(nfi); +#else + font.Scale(scale); +#endif //__WXMSW__ + } + + // wrap a string for the strings no longer then 55 symbols + // return extent of the longest string + int word_wrap_string(wxString& input) + { + size_t line_len = 55;// count of symbols in one line + int idx = -1; + size_t cur_len = 0; + + wxString longest_sub_string; + auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { + if (cur_len > longest_sub_str.Len()) + longest_sub_str = input.SubString(i - cur_len + 1, i); + }; + + for (size_t i = 0; i < input.Len(); i++) + { + cur_len++; + if (input[i] == ' ') + idx = i; + if (input[i] == '\n') + { + get_longest_sub_string(longest_sub_string, cur_len, i); + idx = -1; + cur_len = 0; + } + if (cur_len >= line_len && idx >= 0) + { + get_longest_sub_string(longest_sub_string, cur_len, i); + input[idx] = '\n'; + cur_len = i - static_cast(idx); + } + } + + return GetTextExtent(longest_sub_string).GetX(); + } +}; + + +#ifdef __linux__ +bool static check_old_linux_datadir(const wxString& app_name) { + // If we are on Linux and the datadir does not exist yet, look into the old + // location where the datadir was before version 2.3. If we find it there, + // tell the user that he might wanna migrate to the new location. + // (https://github.com/prusa3d/PrusaSlicer/issues/2911) + // To be precise, the datadir should exist, it is created when single instance + // lock happens. Instead of checking for existence, check the contents. + + namespace fs = boost::filesystem; + + std::string new_path = Slic3r::data_dir(); + + wxString dir; + if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + dir = wxFileName::GetHomeDir() + wxS("/.config"); + std::string default_path = (dir + "/" + app_name).ToUTF8().data(); + + if (new_path != default_path) { + // This happens when the user specifies a custom --datadir. + // Do not show anything in that case. + return true; + } + + fs::path data_dir = fs::path(new_path); + if (! fs::is_directory(data_dir)) + return true; // This should not happen. + + int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator()); + + if (file_count <= 1) { // just cache dir with an instance lock + std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data(); + + if (fs::is_directory(old_path)) { + wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration " + "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n" + "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, " + "an old %1% configuration directory was detected in \n%3%.\n\n" + "Consider moving the contents of the old directory to the new location in order to access " + "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old " + "location again.\n\n" + "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); + wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); + RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); + dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); + if (dlg.ShowModal() != wxID_NO) + return false; + } + } else { + // If the new directory exists, be silent. The user likely already saw the message. + } + return true; +} +#endif + +#ifdef _WIN32 +#if 0 // External Updater is replaced with AppUpdater.cpp +static bool run_updater_win() +{ + // find updater exe + boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; + // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst + std::string msg; + bool res = create_process(path_updater, L"/silent", msg); + if (!res) + BOOST_LOG_TRIVIAL(error) << msg; + return res; +} +#endif // 0 +#endif // _WIN32 + +struct FileWildcards { + std::string_view title; + std::vector file_extensions; +}; + + + +static const FileWildcards file_wildcards_by_type[FT_SIZE] = { + /* FT_STL */ { "STL files"sv, { ".stl"sv } }, + /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, + /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, + /* FT_STEP */ { "STEP files"sv, { ".stp"sv, ".step"sv } }, + /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, + /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, + /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, + /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv } }, + /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, + /* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } }, + /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, + + /* FT_INI */ { "INI files"sv, { ".ini"sv } }, + /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, + + /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, + + /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, +}; + +#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR +wxString file_wildcards(FileType file_type) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + + // Generate cumulative first item + for (const std::string_view& ext : data.file_extensions) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } + else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + mask += boost::to_upper_copy(std::string(ext)); + } + + wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); + + // Adds an item for each of the extensions + if (data.file_extensions.size() > 1) { + for (const std::string_view& ext : data.file_extensions) { + title = "*"; + title += ext; + ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); + } + } + + return ret; +} +#else +// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. +// The function accepts a custom extension parameter. If the parameter is provided, the custom extension +// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips +// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). +wxString file_wildcards(FileType file_type, const std::string &custom_extension) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + std::string custom_ext_lower; + + if (! custom_extension.empty()) { + // Generate an extension into the title mask and into the list of extensions. + custom_ext_lower = boost::to_lower_copy(custom_extension); + const std::string custom_ext_upper = boost::to_upper_copy(custom_extension); + if (custom_ext_lower == custom_extension) { + // Add a lower case version. + title = std::string("*") + custom_ext_lower; + mask = title; + // Add an upper case version. + mask += ";*"; + mask += custom_ext_upper; + } else if (custom_ext_upper == custom_extension) { + // Add an upper case version. + title = std::string("*") + custom_ext_upper; + mask = title; + // Add a lower case version. + mask += ";*"; + mask += custom_ext_lower; + } else { + // Add the mixed case version only. + title = std::string("*") + custom_extension; + mask = title; + } + } + + for (const std::string_view &ext : data.file_extensions) + // Only add an extension if it was not added first as the custom extension. + if (ext != custom_ext_lower) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + mask += boost::to_upper_copy(std::string(ext)); + } + + return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); +} +#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR + +static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } + +#ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) +static void register_win32_dpi_event() +{ + enum { WM_DPICHANGED_ = 0x02e0 }; + + wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { + const int dpi = wParam & 0xffff; + const auto rect = reinterpret_cast(lParam); + const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right)); + + DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect); + win->GetEventHandler()->AddPendingEvent(evt); + + return true; + }); +} +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN + +static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; + +static void register_win32_device_notification_event() +{ + wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. + auto main_frame = dynamic_cast(win); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); + if (plater == nullptr) + // Maybe some other top level window like a dialog or maybe a pop-up menu? + return true; + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + switch (wParam) { + case DBT_DEVICEARRIVAL: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); + else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; +// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { +// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); + if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) + plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + } + break; + case DBT_DEVICEREMOVECOMPLETE: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); + else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; +// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) +// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); + if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) + plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + } + break; + default: + break; + } + return true; + }); + + wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. + auto main_frame = dynamic_cast(win); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); + if (plater == nullptr) + // Maybe some other top level window like a dialog or maybe a pop-up menu? + return true; + wchar_t sPath[MAX_PATH]; + if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { + struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); + if (! SHGetPathFromIDList(pidl, sPath)) { + BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; + return false; + } + } + switch (lParam) { + case SHCNE_MEDIAINSERTED: + { + //printf("SHCNE_MEDIAINSERTED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); + break; + } + case SHCNE_MEDIAREMOVED: + { + //printf("SHCNE_MEDIAREMOVED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); + break; + } + default: +// printf("Unknown\n"); + break; + } + return true; + }); + + wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + auto main_frame = dynamic_cast(Slic3r::GUI::find_toplevel_parent(win)); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); +// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) { + if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) { + RAWINPUT raw; + UINT rawSize = sizeof(RAWINPUT); + ::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); + if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid)) + return true; + } + return false; + }); + + wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + COPYDATASTRUCT* copy_data_structure = { 0 }; + copy_data_structure = (COPYDATASTRUCT*)lParam; + if (copy_data_structure->dwData == 1) { + LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; + Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); + } + return true; + }); +} +#endif // WIN32 + +static void generic_exception_handle() +{ + // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage + // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0 + // + // wxLogError typically goes around exception handling and display an error dialog some time + // after an error is logged even if exception handling and OnExceptionInMainLoop() take place. + // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates + // errors if multiple have been collected and displays just one error message for all of them. + // Otherwise we would get multiple error messages for one missing png, for example. + // + // If a custom error message window (or some other solution) were to be used, it would be necessary + // to turn off wxLogError() usage in wx APIs, most notably in wxImage + // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a + + try { + throw; + } catch (const std::bad_alloc& ex) { + // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) + // and terminate the app so it is at least certain to happen now. + wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); + std::terminate(); + } catch (const boost::io::bad_format_string& ex) { + wxString errmsg = _L("PrusaSlicer has encountered a localization error. " + "Please report to PrusaSlicer team, what language was active and in which scenario " + "this issue happened. Thank you.\n\nThe application will now terminate."); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); + std::terminate(); + throw; + } catch (const std::exception& ex) { + wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what())); + BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); + throw; + } +} + +void GUI_App::post_init() +{ + assert(initialized()); + if (! this->initialized()) + throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); + + if (this->is_gcode_viewer()) { + if (! this->init_params->input_files.empty()) + this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); + } + else if (this->init_params->start_downloader) { + start_download(this->init_params->download_url); + } else { + if (! this->init_params->preset_substitutions.empty()) + show_substitutions_info(this->init_params->preset_substitutions); + +#if 0 + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + this->gui->mainframe->load_config(m_print_config); +#endif + if (! this->init_params->load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + this->mainframe->load_config_file(this->init_params->load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (!this->init_params->input_files.empty()) { + const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); + if (!res.empty() && this->init_params->input_files.size() == 1) { + // Update application titlebar when opening a project file + const std::string& filename = this->init_params->input_files.front(); + if (boost::algorithm::iends_with(filename, ".amf") || + boost::algorithm::iends_with(filename, ".amf.xml") || + boost::algorithm::iends_with(filename, ".3mf")) + this->plater()->set_project_filename(from_u8(filename)); + } + if (this->init_params->delete_after_load) { + for (const std::string& p : this->init_params->input_files) { + boost::system::error_code ec; + boost::filesystem::remove(boost::filesystem::path(p), ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << ec.message(); + } + } + } + } + if (! this->init_params->extra_config.empty()) + this->mainframe->load_config(this->init_params->extra_config); + } + + // show "Did you know" notification + if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) + plater_->get_notification_manager()->push_hint_notification(true); + + // The extra CallAfter() is needed because of Mac, where this is the only way + // to popup a modal dialog on start without screwing combo boxes. + // This is ugly but I honestly found no better way to do it. + // Neither wxShowEvent nor wxWindowCreateEvent work reliably. + if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. + if (! this->check_updates(false)) + // Configuration is not compatible and reconfigure was refused by the user. Application is closing. + return; + CallAfter([this] { + // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. + bool cw_showed = this->config_wizard_startup(); + this->preset_updater->sync(preset_bundle); + this->app_version_check(false); + if (! cw_showed) { + // The CallAfter is needed as well, without it, GL extensions did not show. + // Also, we only want to show this when the wizard does not, so the new user + // sees something else than "we want something" on the first start. + show_send_system_info_dialog_if_needed(); + } + }); + } + + // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. + app_config->set("version", SLIC3R_VERSION); + app_config->save(); + +#ifdef _WIN32 + // Sets window property to mainframe so other instances can indentify it. + OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); +#endif //WIN32 +} + +IMPLEMENT_APP(GUI_App) + +GUI_App::GUI_App(EAppMode mode) + : wxApp() + , m_app_mode(mode) + , m_em_unit(10) + , m_imgui(new ImGuiWrapper()) + , m_removable_drive_manager(std::make_unique()) + , m_other_instance_message_handler(std::make_unique()) + , m_downloader(std::make_unique()) +{ + //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp + this->init_app_config(); + // init app downloader after path to datadir is set + m_app_updater = std::make_unique(); +} + +GUI_App::~GUI_App() +{ + if (app_config != nullptr) + delete app_config; + + if (preset_bundle != nullptr) + delete preset_bundle; + + if (preset_updater != nullptr) + delete preset_updater; +} + +// If formatted for github, plaintext with OpenGL extensions enclosed into
. +// Otherwise HTML formatted for the system info dialog. +std::string GUI_App::get_gl_info(bool for_github) +{ + return OpenGLManager::get_gl_info().to_string(for_github); +} + +wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) +{ +#if ENABLE_GL_CORE_PROFILE +#if ENABLE_OPENGL_DEBUG_OPTION + return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0), + init_params != nullptr ? init_params->opengl_debug : false); +#else + return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0)); +#endif // ENABLE_OPENGL_DEBUG_OPTION +#else + return m_opengl_mgr.init_glcontext(canvas); +#endif // ENABLE_GL_CORE_PROFILE +} + +bool GUI_App::init_opengl() +{ +#ifdef __linux__ + bool status = m_opengl_mgr.init_gl(); + m_opengl_initialized = true; + return status; +#else + return m_opengl_mgr.init_gl(); +#endif +} + +// gets path to PrusaSlicer.ini, returns semver from first line comment +static boost::optional parse_semver_from_ini(std::string path) +{ + std::ifstream stream(path); + std::stringstream buffer; + buffer << stream.rdbuf(); + std::string body = buffer.str(); + size_t start = body.find("PrusaSlicer "); + if (start == std::string::npos) + return boost::none; + body = body.substr(start + 12); + size_t end = body.find_first_of(" \n"); + if (end < body.size()) + body.resize(end); + return Semver::parse(body); +} + +void GUI_App::init_app_config() +{ + // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. + +// SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_KEY "-alpha"); +// SetAppName(SLIC3R_APP_KEY "-beta"); + + +// SetAppDisplayName(SLIC3R_APP_NAME); + + // Set the Slic3r data directory at the Slic3r XS module. + // Unix: ~/ .Slic3r + // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" + // Mac : "~/Library/Application Support/Slic3r" + + if (data_dir().empty()) { + #ifndef __linux__ + set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); + #else + // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. + // https://github.com/prusa3d/PrusaSlicer/issues/2911 + wxString dir; + if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + dir = wxFileName::GetHomeDir() + wxS("/.config"); + set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); + #endif + } else { + m_datadir_redefined = true; + } + + if (!app_config) + app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); + + // load settings + m_app_conf_exists = app_config->exists(); + if (m_app_conf_exists) { + std::string error = app_config->load(); + if (!error.empty()) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + if (is_editor()) { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + else { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + } + } +} + +// returns old config path to copy from if such exists, +// returns an empty string if such config path does not exists or if it cannot be loaded. +std::string GUI_App::check_older_app_config(Semver current_version, bool backup) +{ + std::string older_data_dir_path; + + // If the config folder is redefined - do not check + if (m_datadir_redefined) + return {}; + + // find other version app config (alpha / beta / release) + std::string config_path = app_config->config_path(); + boost::filesystem::path parent_file_path(config_path); + std::string filename = parent_file_path.filename().string(); + parent_file_path.remove_filename().remove_filename(); + + std::vector candidates; + + if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); + if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); + if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); + + Semver last_semver = current_version; + for (const auto& candidate : candidates) { + if (boost::filesystem::exists(candidate)) { + // parse + boost::optionalother_semver = parse_semver_from_ini(candidate.string()); + if (other_semver && *other_semver > last_semver) { + last_semver = *other_semver; + older_data_dir_path = candidate.parent_path().string(); + } + } + } + if (older_data_dir_path.empty()) + return {}; + BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path; + // ask about using older data folder + + InfoDialog msg(nullptr + , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) + , backup ? + format_wxstr(_L( + "The active configuration was created by %1% %2%," + "\nwhile a newer configuration was found in %3%" + "\ncreated by %1% %4%." + "\n\nShall the newer configuration be imported?" + "\nIf so, your active configuration will be backed up before importing the new configuration." + ) + , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string()) + : format_wxstr(_L( + "An existing configuration was found in %3%" + "\ncreated by %1% %2%." + "\n\nShall this configuration be imported?" + ) + , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path) + , true, wxYES_NO); + + if (backup) { + msg.SetButtonLabel(wxID_YES, _L("Import")); + msg.SetButtonLabel(wxID_NO, _L("Don't import")); + } + + if (msg.ShowModal() == wxID_YES) { + std::string snapshot_id; + if (backup) { + const Config::Snapshot* snapshot{ nullptr }; + if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "", + _u8L("Continue and import newer configuration?"), &snapshot)) + return {}; + if (snapshot) { + // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail. + snapshot_id = snapshot->id; + assert(! snapshot_id.empty()); + app_config->set("on_snapshot", snapshot_id); + } else + BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot"; + } + + // load app config from older file + std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string()); + if (!error.empty()) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + if (is_editor()) { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + else { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + } + if (!snapshot_id.empty()) + app_config->set("on_snapshot", snapshot_id); + m_app_conf_exists = true; + return older_data_dir_path; + } + return {}; +} + +void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) +{ + BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; + m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); +} + +bool GUI_App::OnInit() +{ + try { + return on_init_inner(); + } catch (const std::exception&) { + generic_exception_handle(); + return false; + } +} + +bool GUI_App::on_init_inner() +{ + // Set initialization of image handlers before any UI actions - See GH issue #7469 + wxInitAllImageHandlers(); + +#if defined(_WIN32) && ! defined(_WIN64) + // Win32 32bit build. + if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { + RichMessageDialog dlg(nullptr, + _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows." + "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system." + "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/." + "\nDo you wish to continue?"), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + if (dlg.ShowModal() != wxID_YES) + return false; + } +#endif // _WIN64 + + // Forcing back menu icons under gtk2 and gtk3. Solution is based on: + // https://docs.gtk.org/gtk3/class.Settings.html + // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb + // TODO: Find workaround for GTK4 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); +#endif + + // Verify resources path + const wxString resources_dir = from_u8(Slic3r::resources_dir()); + wxCHECK_MSG(wxDirExists(resources_dir), false, + wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); + +#ifdef __linux__ + if (! check_old_linux_datadir(GetAppName())) { + std::cerr << "Quitting, user chose to move their data to new location." << std::endl; + return false; + } +#endif + + // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. +// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); + // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible + // performance when working on high resolution multi-display setups. +// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); + +// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; + + // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action. + // Like here, before the show InfoDialog in check_older_app_config() + + // If load_language() fails, the application closes. + load_language(wxString(), true); +#ifdef _MSW_DARK_MODE + bool init_dark_color_mode = app_config->get("dark_color_mode") == "1"; + bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; + NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); +#endif + // initialize label colors and fonts + init_ui_colours(); + init_fonts(); + + std::string older_data_dir_path; + if (m_app_conf_exists) { + if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION)) + // Only copying configuration if it was saved with a newer slicer than the one currently running. + older_data_dir_path = check_older_app_config(app_config->orig_version(), true); + } else { + // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration. + older_data_dir_path = check_older_app_config(Semver(), false); + } + +#ifdef _MSW_DARK_MODE + // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed + if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1"; + init_dark_color_mode != new_dark_color_mode) { + NppDarkMode::SetDarkMode(new_dark_color_mode); + init_ui_colours(); + update_ui_colours_from_appconfig(); + } + if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; + init_sys_menu_enabled != new_sys_menu_enabled) + NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled); +#endif + + if (is_editor()) { + std::string msg = Http::tls_global_init(); + std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); + bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); + + if (!msg.empty() && !ssl_accept) { + RichMessageDialog + dlg(nullptr, + wxString::Format(_L("%s\nDo you want to continue?"), msg), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + dlg.ShowCheckBox(_L("Remember my choice")); + if (dlg.ShowModal() != wxID_YES) return false; + + app_config->set("tls_cert_store_accepted", + dlg.IsCheckBoxChecked() ? "yes" : "no"); + app_config->set("tls_accepted_cert_store_location", + dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); + } + } + + SplashScreen* scrn = nullptr; + if (app_config->get("show_splash_screen") == "1") { + // make a bitmap with dark grey banner on the left side + wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); + + // Detect position (display) to show the splash screen + // Now this position is equal to the mainframe position + wxPoint splashscreen_pos = wxDefaultPosition; + bool default_splashscreen_pos = true; + if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { + auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); + default_splashscreen_pos = metrics == boost::none; + if (!default_splashscreen_pos) + splashscreen_pos = metrics->get_rect().GetPosition(); + } + + if (!default_splashscreen_pos) { + // workaround for crash related to the positioning of the window on secondary monitor + get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); + get_app_config()->save(); + } + + // create splash screen with updated bmp + scrn = new SplashScreen(bmp.IsOk() ? bmp : get_bmp_bundle("PrusaSlicer", 400)->GetPreferredBitmapSizeAtScale(1.0), + wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); + + if (!default_splashscreen_pos) + // revert "restore_win_position" value if application wasn't crashed + get_app_config()->set("restore_win_position", "1"); +#ifndef __linux__ + wxYield(); +#endif + scrn->SetText(_L("Loading configuration")+ dots); + } + + preset_bundle = new PresetBundle(); + + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory + // supplied as argument to --datadir; in that case we should still run the wizard + preset_bundle->setup_directories(); + + if (! older_data_dir_path.empty()) { + preset_bundle->import_newer_configs(older_data_dir_path); + app_config->save(); + } + + if (is_editor()) { +#ifdef __WXMSW__ + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); +#endif // __WXMSW__ + + preset_updater = new PresetUpdater(); + Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); + Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { + std::string evt_string = into_u8(evt.GetString()); + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { + auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); + this->plater_->get_notification_manager()->push_notification( notif_type + , NotificationManager::NotificationLevel::ImportantNotificationLevel + , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) + , _u8L("See Releases page.") + , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; } + ); + } + } + }); + Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { + //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); + }); + + Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); + if(!evt.GetString().IsEmpty()) + show_error(nullptr, evt.GetString()); + }); + + Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { + show_error(nullptr, evt.GetString()); + }); + + } + else { +#ifdef __WXMSW__ + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); +#endif // __WXMSW__ + } + + std::string delayed_error_load_presets; + // Suppress the '- default -' presets. + preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); + try { + // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. + // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force + // installation of a compatible system preset, thus nullifying the system preset substitutions. + init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); + } catch (const std::exception &ex) { + delayed_error_load_presets = ex.what(); + } + +#ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + register_win32_dpi_event(); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN + register_win32_device_notification_event(); +#endif // WIN32 + + // Let the libslic3r know the callback, which will translate messages on demand. + Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); + + // application frame + if (scrn && is_editor()) + scrn->SetText(_L("Preparing settings tabs") + dots); + + if (!delayed_error_load_presets.empty()) + show_error(nullptr, delayed_error_load_presets); + + mainframe = new MainFrame(); + // hide settings tabs after first Layout + if (is_editor()) + mainframe->select_tab(size_t(0)); + + sidebar().obj_list()->init_objects(); // propagate model objects to object list +// update_mode(); // !!! do that later + SetTopWindow(mainframe); + + plater_->init_notification_manager(); + + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); + + if (is_gcode_viewer()) { + mainframe->update_layout(); + if (plater_ != nullptr) + // ensure the selected technology is ptFFF + plater_->set_printer_technology(ptFFF); + } + else + load_current_presets(); + + // Save the active profiles as a "saved into project". + update_saved_preset_from_current_preset(); + + if (plater_ != nullptr) { + // Save the names of active presets and project specific config into ProjectDirtyStateManager. + plater_->reset_project_dirty_initial_presets(); + // Update Project dirty state, update application title bar. + plater_->update_project_dirty_from_presets(); + } + + mainframe->Show(true); + + obj_list()->set_min_height(); + + update_mode(); // update view mode after fix of the object_list size + +#ifdef __APPLE__ + other_instance_message_handler()->bring_instance_forward(); +#endif //__APPLE__ + + Bind(wxEVT_IDLE, [this](wxIdleEvent& event) + { + if (! plater_) + return; + + this->obj_manipul()->update_if_dirty(); + + // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT + // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. +#ifdef __linux__ + if (! m_post_initialized && m_opengl_initialized) { +#else + if (! m_post_initialized) { +#endif + m_post_initialized = true; +#ifdef WIN32 + this->mainframe->register_win32_callbacks(); +#endif + this->post_init(); + } + + if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1") + app_config->save(); + }); + + m_initialized = true; + + if (const std::string& crash_reason = app_config->get("restore_win_position"); + boost::starts_with(crash_reason,"crashed")) + { + wxString preferences_item = _L("Restore window position on start"); + InfoDialog dialog(nullptr, + _L("PrusaSlicer started after a crash"), + format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n" + "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n" + "More precise reason for the crash: \"%1%\".\n" + "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n" + "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " + "Otherwise, the application will most likely crash again next time."), + "" + from_u8(crash_reason) + "", + "#2939", + "#5573", + "" + preferences_item + ""), + true, wxYES_NO); + + dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); + dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item)); + + auto answer = dialog.ShowModal(); + if (answer == wxID_YES) + app_config->set("restore_win_position", "0"); + else if (answer == wxID_NO) + app_config->set("restore_win_position", "1"); + app_config->save(); + } + + return true; +} + +unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) +{ + double r = colour.Red(); + double g = colour.Green(); + double b = colour.Blue(); + + return std::round(std::sqrt( + r * r * .241 + + g * g * .691 + + b * b * .068 + )); +} + +bool GUI_App::dark_mode() +{ +#if __APPLE__ + // The check for dark mode returns false positive on 10.12 and 10.13, + // which allowed setting dark menu bar and dock area, which is + // is detected as dark mode. We must run on at least 10.14 where the + // proper dark mode was first introduced. + return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode(); +#else + if (wxGetApp().app_config->has("dark_color_mode")) + return wxGetApp().app_config->get("dark_color_mode") == "1"; + return check_dark_mode(); +#endif +} + +const wxColour GUI_App::get_label_default_clr_system() +{ + return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); +} + +const wxColour GUI_App::get_label_default_clr_modified() +{ + return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1); +} + +const std::vector GUI_App::get_mode_default_palette() +{ + return { "#7DF028", "#FFDC00", "#E70000" }; +} + +void GUI_App::init_ui_colours() +{ + m_color_label_modified = get_label_default_clr_modified(); + m_color_label_sys = get_label_default_clr_system(); + m_mode_palette = get_mode_default_palette(); + + bool is_dark_mode = dark_mode(); +#ifdef _WIN32 + m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); + m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); + m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); + m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); + m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); +#else + m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); +#endif + m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); +} + +void GUI_App::update_ui_colours_from_appconfig() +{ + // load label colors + if (app_config->has("label_clr_sys")) { + auto str = app_config->get("label_clr_sys"); + if (!str.empty()) + m_color_label_sys = wxColour(str); + } + + if (app_config->has("label_clr_modified")) { + auto str = app_config->get("label_clr_modified"); + if (!str.empty()) + m_color_label_modified = wxColour(str); + } + + // load mode markers colors + if (app_config->has("mode_palette")) { + const auto colors = app_config->get("mode_palette"); + if (!colors.empty()) { + m_mode_palette.clear(); + if (!unescape_strings_cstyle(colors, m_mode_palette)) + m_mode_palette = get_mode_default_palette(); + } + } +} + +void GUI_App::update_label_colours() +{ + for (Tab* tab : tabs_list) + tab->update_label_colours(); +} + +#ifdef _WIN32 +static bool is_focused(HWND hWnd) +{ + HWND hFocusedWnd = ::GetFocus(); + return hFocusedWnd && hWnd == hFocusedWnd; +} + +static bool is_default(wxWindow* win) +{ + wxTopLevelWindow* tlw = find_toplevel_parent(win); + if (!tlw) + return false; + + return win == tlw->GetDefaultItem(); +} +#endif + +void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) +{ +#ifdef _WIN32 + bool is_focused_button = false; + bool is_default_button = false; + if (wxButton* btn = dynamic_cast(window)) { + if (!(btn->GetWindowStyle() & wxNO_BORDER)) { + btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); + highlited = true; + } + // button marking + { + auto mark_button = [this, btn, highlited](const bool mark) { + if (btn->GetLabel().IsEmpty()) + btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); + else + btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); + btn->Refresh(); + btn->Update(); + }; + + // hovering + btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); + btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); + // focusing + btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); + btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); + + is_focused_button = is_focused(btn->GetHWND()); + is_default_button = is_default(btn); + if (is_focused_button || is_default_button) + mark_button(is_focused_button); + } + } + else if (wxTextCtrl* text = dynamic_cast(window)) { + if (text->GetBorder() != wxBORDER_SIMPLE) + text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); + } + else if (wxCheckListBox* list = dynamic_cast(window)) { + list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); + list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + for (size_t i = 0; i < list->GetCount(); i++) + if (wxOwnerDrawn* item = list->GetItem(i)) { + item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + item->SetTextColour(m_color_label_default); + } + return; + } + else if (dynamic_cast(window)) + window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); + + if (!just_font) + window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + if (!is_focused_button && !is_default_button) + window->SetForegroundColour(m_color_label_default); +#endif +} + +// recursive function for scaling fonts for all controls in Window +#ifdef _WIN32 +static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false) +{ + bool is_btn = dynamic_cast(window) != nullptr; + if (!(just_buttons_update && !is_btn)) + wxGetApp().UpdateDarkUI(window, is_btn); + + auto children = window->GetChildren(); + for (auto child : children) { + update_dark_children_ui(child); + } +} +#endif + +// Note: Don't use this function for Dialog contains ScalableButtons +void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/) +{ +#ifdef _WIN32 + update_dark_children_ui(dlg, just_buttons_update); +#endif +} +void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) +{ +#ifdef _WIN32 + UpdateDarkUI(dvc, highlited ? dark_mode() : false); +#ifdef _MSW_DARK_MODE + dvc->RefreshHeaderDarkMode(&m_normal_font); +#endif //_MSW_DARK_MODE + if (dvc->HasFlag(wxDV_ROW_LINES)) + dvc->SetAlternateRowColour(m_color_highlight_default); + if (dvc->GetBorder() != wxBORDER_SIMPLE) + dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE); +#endif +} + +void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) +{ +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(parent); + + auto children = parent->GetChildren(); + for (auto child : children) { + if (dynamic_cast(child)) + child->SetForegroundColour(m_color_label_default); + } +#endif +} + +void GUI_App::init_fonts() +{ + m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); + m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + +#ifdef __WXMAC__ + m_small_font.SetPointSize(11); + m_bold_font.SetPointSize(13); +#endif /*__WXMAC__*/ + + // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as + // DEFAULT in wxGtk. Use the TELETYPE family as a work-around + m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); +} + +void GUI_App::update_fonts(const MainFrame *main_frame) +{ + /* Only normal and bold fonts are used for an application rescale, + * because of under MSW small and normal fonts are the same. + * To avoid same rescaling twice, just fill this values + * from rescaled MainFrame + */ + if (main_frame == nullptr) + main_frame = this->mainframe; + m_normal_font = main_frame->normal_font(); + m_small_font = m_normal_font; + m_bold_font = main_frame->normal_font().Bold(); + m_link_font = m_bold_font.Underlined(); + m_em_unit = main_frame->em_unit(); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); +} + +void GUI_App::set_label_clr_modified(const wxColour& clr) +{ + if (m_color_label_modified == clr) + return; + m_color_label_modified = clr; + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + app_config->set("label_clr_modified", str); + app_config->save(); +} + +void GUI_App::set_label_clr_sys(const wxColour& clr) +{ + if (m_color_label_sys == clr) + return; + m_color_label_sys = clr; + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + app_config->set("label_clr_sys", str); + app_config->save(); +} + +const std::string& GUI_App::get_mode_btn_color(int mode_id) +{ + assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size()); + return m_mode_palette[mode_id]; +} + +std::vector GUI_App::get_mode_palette() +{ + return { wxColor(m_mode_palette[0]), + wxColor(m_mode_palette[1]), + wxColor(m_mode_palette[2]) }; +} + +void GUI_App::set_mode_palette(const std::vector& palette) +{ + bool save = false; + + for (size_t mode = 0; mode < palette.size(); ++mode) { + const wxColour& clr = palette[mode]; + std::string color_str = clr == wxTransparentColour ? std::string("") : encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + if (m_mode_palette[mode] != color_str) { + m_mode_palette[mode] = color_str; + save = true; + } + } + + if (save) { + mainframe->update_mode_markers(); + app_config->set("mode_palette", escape_strings_cstyle(m_mode_palette)); + app_config->save(); + } +} + +bool GUI_App::tabs_as_menu() const +{ + return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); +} + +wxSize GUI_App::get_min_size() const +{ + return wxSize(76*m_em_unit, 49 * m_em_unit); +} + +float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const +{ +#ifdef __APPLE__ + const float icon_sc = 1.0f; // for Retina display will be used its own scale +#else + const float icon_sc = m_em_unit*0.1f; +#endif // __APPLE__ + + const std::string& use_val = app_config->get("use_custom_toolbar_size"); + const std::string& val = app_config->get("custom_toolbar_size"); + const std::string& auto_val = app_config->get("auto_toolbar_size"); + + if (val.empty() || auto_val.empty() || use_val.empty()) + return icon_sc; + + int int_val = use_val == "0" ? 100 : atoi(val.c_str()); + // correct value in respect to auto_toolbar_size + int_val = std::min(atoi(auto_val.c_str()), int_val); + + if (is_limited && int_val < 50) + int_val = 50; + + return 0.01f * int_val * icon_sc; +} + +void GUI_App::set_auto_toolbar_icon_scale(float scale) const +{ +#ifdef __APPLE__ + const float icon_sc = 1.0f; // for Retina display will be used its own scale +#else + const float icon_sc = m_em_unit * 0.1f; +#endif // __APPLE__ + + long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); + std::string val = std::to_string(int_val); + + app_config->set("auto_toolbar_size", val); +} + +// check user printer_presets for the containing information about "Print Host upload" +void GUI_App::check_printer_presets() +{ + std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); + if (preset_names.empty()) + return; + + wxString msg_text = _L("You have the following presets with saved options for \"Print Host upload\"") + ":"; + for (const std::string& preset_name : preset_names) + msg_text += "\n \"" + from_u8(preset_name) + "\","; + msg_text.RemoveLast(); + msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" + "Settings will be available in physical printers settings.") + "\n\n" + + _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" + "Note: This name can be changed later from the physical printers settings"); + + //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); + MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); + + preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); +} + +void GUI_App::recreate_GUI(const wxString& msg_name) +{ + m_is_recreating_gui = true; + + mainframe->shutdown(); + + wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); + dlg.Pulse(); + dlg.Update(10, _L("Recreating") + dots); + + MainFrame *old_main_frame = mainframe; + mainframe = new MainFrame(); + if (is_editor()) + // hide settings tabs after first Layout + mainframe->select_tab(size_t(0)); + // Propagate model objects to object list. + sidebar().obj_list()->init_objects(); + SetTopWindow(mainframe); + + dlg.Update(30, _L("Recreating") + dots); + old_main_frame->Destroy(); + + dlg.Update(80, _L("Loading of current presets") + dots); + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); + load_current_presets(); + mainframe->Show(true); + + dlg.Update(90, _L("Loading of a mode view") + dots); + + obj_list()->set_min_height(); + update_mode(); + + // #ys_FIXME_delete_after_testing Do we still need this ? +// CallAfter([]() { +// // Run the config wizard, don't offer the "reset user profile" checkbox. +// config_wizard_startup(true); +// }); + + m_is_recreating_gui = false; +} + +void GUI_App::system_info() +{ + SysInfoDialog dlg; + dlg.ShowModal(); +} + +void GUI_App::keyboard_shortcuts() +{ + KBShortcutsDialog dlg; + dlg.ShowModal(); +} + +// static method accepting a wxWindow object as first parameter +bool GUI_App::catch_error(std::function cb, + // wxMessageDialog* message_dialog, + const std::string& err /*= ""*/) +{ + if (!err.empty()) { + if (cb) + cb(); + // if (message_dialog) + // message_dialog->(err, "Error", wxOK | wxICON_ERROR); + show_error(/*this*/nullptr, err); + return true; + } + return false; +} + +// static method accepting a wxWindow object as first parameter +void fatal_error(wxWindow* parent) +{ + show_error(parent, ""); + // exit 1; // #ys_FIXME +} + +#ifdef _WIN32 + +#ifdef _MSW_DARK_MODE +static void update_scrolls(wxWindow* window) +{ + wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst(); + while (node) + { + wxWindow* win = node->GetData(); + if (dynamic_cast(win) || + dynamic_cast(win) || + dynamic_cast(win)) + NppDarkMode::SetDarkExplorerTheme(win->GetHWND()); + + update_scrolls(win); + node = node->GetNext(); + } +} +#endif //_MSW_DARK_MODE + + +#ifdef _MSW_DARK_MODE +void GUI_App::force_menu_update() +{ + NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1"); +} +#endif //_MSW_DARK_MODE + +void GUI_App::force_colors_update() +{ +#ifdef _MSW_DARK_MODE + NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); + if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl()) + NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND); + NppDarkMode::SetDarkTitleBar(mainframe->GetHWND()); + NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND()); +#endif //_MSW_DARK_MODE + m_force_colors_update = true; +} +#endif //_WIN32 + +// Called after the Preferences dialog is closed and the program settings are saved. +// Update the UI based on the current preferences. +void GUI_App::update_ui_from_settings() +{ + update_label_colours(); +#ifdef _WIN32 + // Upadte UI colors before Update UI from settings + if (m_force_colors_update) { + m_force_colors_update = false; + mainframe->force_color_changed(); + mainframe->diff_dialog.force_color_changed(); + mainframe->preferences_dialog->force_color_changed(); + mainframe->printhost_queue_dlg()->force_color_changed(); +#ifdef _MSW_DARK_MODE + update_scrolls(mainframe); + if (mainframe->is_dlg_layout()) { + // update for tabs bar + UpdateDarkUI(&mainframe->m_settings_dialog); + mainframe->m_settings_dialog.Fit(); + mainframe->m_settings_dialog.Refresh(); + // update scrollbars + update_scrolls(&mainframe->m_settings_dialog); + } +#endif //_MSW_DARK_MODE + } +#endif + mainframe->update_ui_from_settings(); +} + +void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) +{ + const std::string name = into_u8(window->GetName()); + + window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { + window_pos_save(window, name); + event.Skip(); + }); + + window_pos_restore(window, name, default_maximized); + + on_window_geometry(window, [=]() { + window_pos_sanitize(window); + }); +} + +void GUI_App::load_project(wxWindow *parent, wxString& input_file) const +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one file (3MF/AMF):"), + app_config->get_last_dir(), "", + file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const +{ + input_files.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one or more files (STL/3MF/STEP/OBJ/AMF/PRUSA):"), + from_u8(app_config->get_last_dir()), "", + file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + dialog.GetPaths(input_files); +} + +void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"), + app_config->get_last_dir(), "", + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +bool GUI_App::switch_language() +{ + if (select_language()) { + recreate_GUI(_L("Changing of an application language") + dots); + return true; + } else { + return false; + } +} + +#ifdef __linux__ +static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, + const wxLanguageInfo* system_language) +{ + constexpr size_t max_len = 50; + char path[max_len] = ""; + std::vector locales; + const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); + + // Call locale -a so we can parse the output to get the list of available locales + // We expect lines such as "en_US.utf8". Pick ones starting with the language code + // we are switching to. Lines with different formatting will be removed later. + FILE* fp = popen("locale -a", "r"); + if (fp != NULL) { + while (fgets(path, max_len, fp) != NULL) { + std::string line(path); + line = line.substr(0, line.find('\n')); + if (boost::starts_with(line, lang_prefix)) + locales.push_back(line); + } + pclose(fp); + } + + // locales now contain all candidates for this language. + // Sort them so ones containing anything about UTF-8 are at the end. + std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) + { + auto has_utf8 = [](const std::string & s) { + auto S = boost::to_upper_copy(s); + return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; + }; + return ! has_utf8(a) && has_utf8(b); + }); + + // Remove the suffix behind a dot, if there is one. + for (std::string& s : locales) + s = s.substr(0, s.find(".")); + + // We just hope that dear Linux "locale -a" returns country codes + // in ISO 3166-1 alpha-2 code (two letter) format. + // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes + // To be sure, remove anything not looking as expected + // (any number of lowercase letters, underscore, two uppercase letters). + locales.erase(std::remove_if(locales.begin(), + locales.end(), + [](const std::string& s) { + return ! std::regex_match(s, + std::regex("^[a-z]+_[A-Z]{2}$")); + }), + locales.end()); + + if (system_language) { + // Is there a candidate matching a country code of a system language? Move it to the end, + // while maintaining the order of matches, so that the best match ends up at the very end. + std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); + int cnt = locales.size(); + for (int i = 0; i < cnt; ++i) + if (locales[i].find(system_country) != std::string::npos) { + locales.emplace_back(std::move(locales[i])); + locales[i].clear(); + } + } + + // Now try them one by one. + for (auto it = locales.rbegin(); it != locales.rend(); ++ it) + if (! it->empty()) { + const std::string &locale = *it; + const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); + if (wxLocale::IsAvailable(lang->Language)) + return lang; + } + return language; +} +#endif + +int GUI_App::GetSingleChoiceIndex(const wxString& message, + const wxString& caption, + const wxArrayString& choices, + int initialSelection) +{ +#ifdef _WIN32 + wxSingleChoiceDialog dialog(nullptr, message, caption, choices); + wxGetApp().UpdateDlgDarkUI(&dialog); + + dialog.SetSelection(initialSelection); + return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1; +#else + return wxGetSingleChoiceIndex(message, caption, choices, initialSelection); +#endif +} + +// select language from the list of installed languages +bool GUI_App::select_language() +{ + wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); + std::vector language_infos; + language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); + for (size_t i = 0; i < translations.GetCount(); ++ i) { + const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); + if (langinfo != nullptr) + language_infos.emplace_back(langinfo); + } + sort_remove_duplicates(language_infos); + std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); + + wxArrayString names; + names.Alloc(language_infos.size()); + + // Some valid language should be selected since the application start up. + const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage()); + int init_selection = -1; + int init_selection_alt = -1; + int init_selection_default = -1; + for (size_t i = 0; i < language_infos.size(); ++ i) { + if (wxLanguage(language_infos[i]->Language) == current_language) + // The dictionary matches the active language and country. + init_selection = i; + else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) || + // if the active language is Slovak, mark the Czech language as active. + (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk")) + // The dictionary matches the active language, it does not necessarily match the country. + init_selection_alt = i; + if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") + // This will be the default selection if the active language does not match any dictionary. + init_selection_default = i; + names.Add(language_infos[i]->Description); + } + if (init_selection == -1) + // This is the dictionary matching the active language. + init_selection = init_selection_alt; + if (init_selection != -1) + // This is the language to highlight in the choice dialog initially. + init_selection_default = init_selection; + + const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); + // Try to load a new language. + if (index != -1 && (init_selection == -1 || init_selection != index)) { + const wxLanguageInfo *new_language_info = language_infos[index]; + if (this->load_language(new_language_info->CanonicalName, false)) { + // Save language at application config. + // Which language to save as the selected dictionary language? + // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its + // stability in the future: + // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); + // 2) Current locale language may not match the dictionary name, see GH issue #3901 + // m_wxLocale->GetCanonicalName() + // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. + app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data()); + app_config->save(); + return true; + } + } + + return false; +} + +// Load gettext translation files and activate them at the start of the application, +// based on the "translation_language" key stored in the application config. +bool GUI_App::load_language(wxString language, bool initial) +{ + if (initial) { + // There is a static list of lookup path prefixes in wxWidgets. Add ours. + wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir())); + // Get the active language from PrusaSlicer.ini, or empty string if the key does not exist. + language = app_config->get("translation_language"); + if (! language.empty()) + BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language; + + // Get the system language. + { + const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); + if (lang_system != wxLANGUAGE_UNKNOWN) { + m_language_info_system = wxLocale::GetLanguageInfo(lang_system); + BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data(); + } + } + { + // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. + wxLocale temp_locale; +#ifdef __WXOSX__ + // ysFIXME - temporary workaround till it isn't fixed in wxWidgets: + // Use English as an initial language, because of under OSX it try to load "inappropriate" language for wxLANGUAGE_DEFAULT. + // For example in our case it's trying to load "en_CZ" and as a result PrusaSlicer catch warning message. + // But wxWidgets guys work on it. + temp_locale.Init(wxLANGUAGE_ENGLISH); +#else + temp_locale.Init(); +#endif // __WXOSX__ + // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). + wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); + // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer + // and try to match them with the system specific "preferred languages". + // There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage(). + // The last parameter gets added to the list of detected dictionaries. This is a workaround + // for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way. + wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); + if (! best_language.IsEmpty()) { + m_language_info_best = wxLocale::FindLanguageInfo(best_language); + BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data(); + } + #ifdef __linux__ + wxString lc_all; + if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) { + // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL. + // Disregard the "best" suggestion in case LC_ALL is provided. + m_language_info_best = nullptr; + } + #endif + } + } + + const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language); + if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) { + // Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI). + language_info = nullptr; + BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data(); + } + + if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) { + BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data(); + language_info = nullptr; + } + + if (language_info == nullptr) { + // PrusaSlicer does not support the Right to Left languages yet. + if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft) + language_info = m_language_info_system; + if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft) + language_info = m_language_info_best; + if (language_info == nullptr) + language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US); + } + + BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data(); + + // Alternate language code. + wxLanguage language_dict = wxLanguage(language_info->Language); + if (language_info->CanonicalName.BeforeFirst('_') == "sk") { + // Slovaks understand Czech well. Give them the Czech translation. + language_dict = wxLANGUAGE_CZECH; + BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language"; + } + + // Select language for locales. This language may be different from the language of the dictionary. + if (language_info == m_language_info_best || language_info == m_language_info_system) { + // The current language matches user's default profile exactly. That's great. + } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) { + // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language. + // This allows a Swiss guy to use a German dictionary without forcing him to German locales. + language_info = m_language_info_best; + } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_')) + language_info = m_language_info_system; + +#ifdef __linux__ + // If we can't find this locale , try to use different one for the language + // instead of just reporting that it is impossible to switch. + if (! wxLocale::IsAvailable(language_info->Language)) { + std::string original_lang = into_u8(language_info->CanonicalName); + language_info = linux_get_existing_locale_language(language_info, m_language_info_system); + BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.") + % original_lang % language_info->CanonicalName.ToUTF8().data(); + } +#endif + + if (! wxLocale::IsAvailable(language_info->Language)) { + // Loading the language dictionary failed. + wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed."; +#if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux system + message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"; +#endif + if (initial) + message + "\n\nApplication will close."; + wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); + if (initial) + std::exit(EXIT_FAILURE); + else + return false; + } + + // Release the old locales, create new locales. + //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now. + m_wxLocale.release(); + m_wxLocale = Slic3r::make_unique(); + m_wxLocale->Init(language_info->Language); + // Override language at the active wxTranslations class (which is stored in the active m_wxLocale) + // to load possibly different dictionary, for example, load Czech dictionary for Slovak language. + wxTranslations::Get()->SetLanguage(language_dict); + { + // UKR Localization specific workaround till the wxWidgets doesn't fixed: + // From wxWidgets 3.1.6 calls setlocation(0, wxInfoLanguage->LocaleTag), see (https://github.com/prusa3d/wxWidgets/commit/deef116a09748796711d1e3509965ee208dcdf0b#diff-7de25e9a71c4dce61bbf76492c589623d5b93fd1bb105ceaf0662075d15f4472), + // where LocaleTag is a Tag of locale in BCP 47 - like notation. + // For Ukrainian Language LocaleTag == "uk". + // But setlocale(0, "uk") returns "English_United Kingdom.1252" instead of "uk", + // and, as a result, locales are set to English_United Kingdom + + if (language_info->CanonicalName == "uk") + setlocale(0, language_info->GetCanonicalWithRegion().data()); + } + m_wxLocale->AddCatalog(SLIC3R_APP_KEY); + m_imgui->set_language(into_u8(language_info->CanonicalName)); + //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. + //wxSetlocale(LC_NUMERIC, "C"); + Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); + return true; +} + +Tab* GUI_App::get_tab(Preset::Type type) +{ + for (Tab* tab: tabs_list) + if (tab->type() == type) + return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab + return nullptr; +} + +ConfigOptionMode GUI_App::get_mode() +{ + if (!app_config->has("view_mode")) + return comSimple; + + const auto mode = app_config->get("view_mode"); + return mode == "expert" ? comExpert : + mode == "simple" ? comSimple : comAdvanced; +} + +void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) +{ + const std::string mode_str = mode == comExpert ? "expert" : + mode == comSimple ? "simple" : "advanced"; + app_config->set("view_mode", mode_str); + app_config->save(); + update_mode(); +} + +// Update view mode according to selected menu +void GUI_App::update_mode() +{ + sidebar().update_mode(); + +#ifdef _WIN32 //_MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); +#endif + + for (auto tab : tabs_list) + tab->update_mode(); + + plater()->update_menus(); + plater()->canvas3D()->update_gizmos_on_off_state(); +} + +void GUI_App::add_config_menu(wxMenuBar *menu) +{ + auto local_menu = new wxMenu(); + wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); + + const auto config_wizard_name = _(ConfigWizard::name(true)); + const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str()); + // Cmd+, is standard on OS X - what about other operating systems? + if (is_editor()) { + local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); + local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); + local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); + local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + //if (DesktopIntegrationDialog::integration_possible()) + local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); +#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + local_menu->AppendSeparator(); + } + local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + +#ifdef __APPLE__ + "\tCtrl+,", +#else + "\tCtrl+P", +#endif + _L("Application preferences")); + wxMenu* mode_menu = nullptr; + if (is_editor()) { + local_menu->AppendSeparator(); + mode_menu = new wxMenu(); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); +// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode")); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); + + local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); + } + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); + if (is_editor()) { + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer")); + // TODO: for when we're able to flash dictionaries + // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a Prusa printer")); + } + + local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { + switch (event.GetId() - config_id_base) { + case ConfigMenuWizard: + run_wizard(ConfigWizard::RR_USER); + break; + case ConfigMenuUpdateConf: + check_updates(true); + break; + case ConfigMenuUpdateApp: + app_version_check(true); + break; +#ifdef __linux__ + case ConfigMenuDesktopIntegration: + show_desktop_integration_dialog(); + break; +#endif + case ConfigMenuTakeSnapshot: + // Take a configuration snapshot. + if (wxString action_name = _L("Taking a configuration snapshot"); + check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { + wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); + UpdateDlgDarkUI(&dlg); + + // set current normal font for dialog children, + // because of just dlg.SetFont(normal_font()) has no result; + for (auto child : dlg.GetChildren()) + child->SetFont(normal_font()); + + if (dlg.ShowModal() == wxID_OK) + if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( + *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); + snapshot != nullptr) + app_config->set("on_snapshot", snapshot->id); + } + break; + case ConfigMenuSnapshots: + if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { + std::string on_snapshot; + if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) + on_snapshot = app_config->get("on_snapshot"); + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); + dlg.ShowModal(); + if (!dlg.snapshot_to_activate().empty()) { + if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && + ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", + GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) + break; + try { + app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); + // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system + // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users + // are known to be creative and mess with the config files in various ways. + if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); + ! all_substitutions.empty()) + show_substitutions_info(all_substitutions); + + // Load the currently selected preset into the GUI, update the preset selection box. + load_current_presets(); + } catch (std::exception &ex) { + GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); + } + } + } + break; + case ConfigMenuPreferences: + { + open_preferences(); + break; + } + case ConfigMenuLanguage: + { + /* Before change application language, let's check unsaved changes on 3D-Scene + * and draw user's attention to the application restarting after a language change + */ + { + // the dialog needs to be destroyed before the call to switch_language() + // or sometimes the application crashes into wxDialogBase() destructor + // so we put it into an inner scope + wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); + title += " - " + _L("Language selection"); + //wxMessageDialog dialog(nullptr, + MessageDialog dialog(nullptr, + _L("Switching the language will trigger application restart.\n" + "You will lose content of the plater.") + "\n\n" + + _L("Do you want to proceed?"), + title, + wxICON_QUESTION | wxOK | wxCANCEL); + if (dialog.ShowModal() == wxID_CANCEL) + return; + } + + switch_language(); + break; + } + case ConfigMenuFlashFirmware: + FirmwareDialog::run(mainframe); + break; + default: + break; + } + }); + + using std::placeholders::_1; + + if (mode_menu != nullptr) { + auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); + } + + menu->Append(local_menu, _L("&Configuration")); +} + +void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) +{ + mainframe->preferences_dialog->show(highlight_option, tab_name); + + if (mainframe->preferences_dialog->recreate_GUI()) + recreate_GUI(_L("Restart application") + dots); + +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) +#else + if (mainframe->preferences_dialog->seq_top_layer_only_changed()) +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + this->plater_->refresh_print(); + +#ifdef _WIN32 + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); + } + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); + } +#endif // _WIN32 + + if (mainframe->preferences_dialog->settings_layout_changed()) { + // hide full main_sizer for mainFrame + mainframe->GetSizer()->Show(false); + mainframe->update_layout(); + mainframe->select_tab(size_t(0)); + } +} + +bool GUI_App::has_unsaved_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) + return true; + } + return false; +} + +bool GUI_App::has_current_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + return true; + } + return false; +} + +void GUI_App::update_saved_preset_from_current_preset() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->update_saved_preset_from_current_preset(); + } +} + +std::vector GUI_App::get_active_preset_collections() const +{ + std::vector ret; + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* tab : tabs_list) + if (tab->supports_printer_technology(printer_technology)) + ret.push_back(tab->get_presets()); + return ret; +} + +// To notify the user whether he is aware that some preset changes will be lost, +// UnsavedChangesDialog: "Discard / Save / Cancel" +// This is called when: +// - Close Application & Current project isn't saved +// - Load Project & Current project isn't saved +// - Undo / Redo with change of print technologie +// - Loading snapshot +// - Loading config_file/bundle +// UnsavedChangesDialog: "Don't save / Save / Cancel" +// This is called when: +// - Exporting config_bundle +// - Taking snapshot +bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) +{ + if (has_current_preset_changes()) { + const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; + int act_buttons = ActionButtons::SAVE; + if (dont_save_insted_of_discard) + act_buttons |= ActionButtons::DONT_SAVE; + UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) + return false; + + if (dlg.save_preset()) // save selected changes + { + for (const std::pair& nt : dlg.get_names_and_types()) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + load_current_presets(false); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + + MessageDialog(nullptr, dlg.msg_success_saved_modifications(dlg.get_names_and_types().size())).ShowModal(); + } + } + + return true; +} + +void GUI_App::apply_keeped_preset_modifications() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->apply_config_from_cache(); + } + load_current_presets(false); +} + +// This is called when creating new project or load another project +// OR close ConfigWizard +// to ask the user what should we do with unsaved changes for presets. +// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" +// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed +bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) +{ + if (has_current_preset_changes()) { + bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; + + const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; + UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) + return false; + + auto reset_modifications = [this, is_called_from_configwizard]() { + if (is_called_from_configwizard) + return; // no need to discared changes. It will be done fromConfigWizard closing + + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + tab->m_presets->discard_current_changes(); + } + load_current_presets(false); + }; + + if (dlg.discard()) + reset_modifications(); + else // save selected changes + { + const auto& preset_names_and_types = dlg.get_names_and_types(); + if (dlg.save_preset()) { + for (const std::pair& nt : preset_names_and_types) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + + wxString text = dlg.msg_success_saved_modifications(preset_names_and_types.size()); + if (!is_called_from_configwizard) + text += "\n\n" + _L("For new project all modifications will be reseted"); + + MessageDialog(nullptr, text).ShowModal(); + reset_modifications(); + } + else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { + // execute this part of code only if not all modifications are keeping to the new project + // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected + for (const std::pair& nt : preset_names_and_types) { + Preset::Type type = nt.second; + Tab* tab = get_tab(type); + std::vector selected_options = dlg.get_selected_options(type); + if (type == Preset::TYPE_PRINTER) { + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + static_cast(tab)->cache_extruder_cnt(); + } + } + tab->cache_config_diff(selected_options); + if (!is_called_from_configwizard) + tab->m_presets->discard_current_changes(); + } + if (is_called_from_configwizard) + *postponed_apply_of_keeped_changes = true; + else + apply_keeped_preset_modifications(); + } + } + } + + return true; +} + +bool GUI_App::can_load_project() +{ + int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); + if (saved_project == wxID_CANCEL || + (plater()->is_project_dirty() && saved_project == wxID_NO && + !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved.")))) + return false; + return true; +} + +bool GUI_App::check_print_host_queue() +{ + wxString dirty; + std::vector> jobs; + // Get ongoing jobs from dialog + mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); + if (jobs.empty()) + return true; + // Show dialog + wxString job_string = wxString(); + for (const auto& job : jobs) { + job_string += format_wxstr(" %1% : %2% \n", job.first, job.second); + } + wxString message; + message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); + //wxMessageDialog dialog(mainframe, + MessageDialog dialog(mainframe, + message, + wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), + wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + if (dialog.ShowModal() == wxID_YES) + return true; + + // TODO: If already shown, bring forward + mainframe->m_printhost_queue_dlg->Show(); + return false; +} + +bool GUI_App::checked_tab(Tab* tab) +{ + bool ret = true; + if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) + ret = false; + return ret; +} + +// Update UI / Tabs to reflect changes in the currently loaded presets +void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) +{ + // check printer_presets for the containing information about "Print Host upload" + // and create physical printer from it, if any exists + if (check_printer_presets_) + check_printer_presets(); + + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + this->plater()->set_printer_technology(printer_technology); + for (Tab *tab : tabs_list) + if (tab->supports_printer_technology(printer_technology)) { + if (tab->type() == Preset::TYPE_PRINTER) { + static_cast(tab)->update_pages(); + // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). + this->plater()->force_print_bed_update(); + } + tab->load_current_preset(); + } +} + +bool GUI_App::OnExceptionInMainLoop() +{ + generic_exception_handle(); + return false; +} + +#ifdef __APPLE__ +// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run +// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App +// to a G-code viewer. +void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames) +{ + size_t num_gcodes = 0; + for (const wxString &filename : fileNames) + if (is_gcode_file(into_u8(filename))) + ++ num_gcodes; + if (fileNames.size() == num_gcodes) { + // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder, + // just G-codes were passed. Switch to G-code viewer mode. + m_app_mode = EAppMode::GCodeViewer; + unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/"); + if(app_config != nullptr) + delete app_config; + app_config = nullptr; + init_app_config(); + } + wxApp::OSXStoreOpenFiles(fileNames); +} +// wxWidgets override to get an event on open files. +void GUI_App::MacOpenFiles(const wxArrayString &fileNames) +{ + std::vector files; + std::vector gcode_files; + std::vector non_gcode_files; + for (const auto& filename : fileNames) { + if (is_gcode_file(into_u8(filename))) + gcode_files.emplace_back(filename); + else { + files.emplace_back(into_u8(filename)); + non_gcode_files.emplace_back(filename); + } + } + if (m_app_mode == EAppMode::GCodeViewer) { + // Running in G-code viewer. + // Load the first G-code into the G-code viewer. + // Or if no G-codes, send other files to slicer. + if (! gcode_files.empty()) { + if (m_post_initialized) + this->plater()->load_gcode(gcode_files.front()); + else + this->init_params->input_files = { into_u8(gcode_files.front()) }; + } + if (!non_gcode_files.empty()) + start_new_slicer(non_gcode_files, true); + } else { + if (! files.empty()) { + if (m_post_initialized) { + wxArrayString input_files; + for (size_t i = 0; i < non_gcode_files.size(); ++i) + input_files.push_back(non_gcode_files[i]); + this->plater()->load_files(input_files); + } else { + for (const auto &f : non_gcode_files) + this->init_params->input_files.emplace_back(into_u8(f)); + } + } + for (const wxString &filename : gcode_files) + start_new_gcodeviewer(&filename); + } +} + +void GUI_App::MacOpenURL(const wxString& url) +{ + if (app_config && app_config->get("downloader_url_registered") != "1") + { + BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; + return; + } + start_download(boost::nowide::narrow(url)); +} + +#endif /* __APPLE */ + +Sidebar& GUI_App::sidebar() +{ + return plater_->sidebar(); +} + +ObjectManipulation* GUI_App::obj_manipul() +{ + // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) + return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr; +} + +ObjectSettings* GUI_App::obj_settings() +{ + return sidebar().obj_settings(); +} + +ObjectList* GUI_App::obj_list() +{ + return sidebar().obj_list(); +} + +ObjectLayers* GUI_App::obj_layers() +{ + return sidebar().obj_layers(); +} + +Plater* GUI_App::plater() +{ + return plater_; +} + +const Plater* GUI_App::plater() const +{ + return plater_; +} + +Model& GUI_App::model() +{ + return plater_->model(); +} +wxBookCtrlBase* GUI_App::tab_panel() const +{ + return mainframe->m_tabpanel; +} + +NotificationManager* GUI_App::notification_manager() +{ + return plater_->get_notification_manager(); +} + +GalleryDialog* GUI_App::gallery_dialog() +{ + return mainframe->gallery_dialog(); +} + +Downloader* GUI_App::downloader() +{ + return m_downloader.get(); +} + +// extruders count from selected printer preset +int GUI_App::extruders_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_selected_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + +// extruders count from edited printer preset +int GUI_App::extruders_edited_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_edited_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + +wxString GUI_App::current_language_code_safe() const +{ + // Translate the language code to a code, for which Prusa Research maintains translations. + const std::map mapping { + { "cs", "cs_CZ", }, + { "sk", "cs_CZ", }, + { "de", "de_DE", }, + { "es", "es_ES", }, + { "fr", "fr_FR", }, + { "it", "it_IT", }, + { "ja", "ja_JP", }, + { "ko", "ko_KR", }, + { "pl", "pl_PL", }, + { "uk", "uk_UA", }, + { "zh", "zh_CN", }, + { "ru", "ru_RU", }, + }; + wxString language_code = this->current_language_code().BeforeFirst('_'); + auto it = mapping.find(language_code); + if (it != mapping.end()) + language_code = it->second; + else + language_code = "en_US"; + return language_code; +} + +void GUI_App::open_web_page_localized(const std::string &http_address) +{ + open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); +} + +// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). +// Because of we can't to print the multi-part objects with SLA technology. +bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) +{ + if (model_has_multi_part_objects(model())) { + show_info(nullptr, + _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } + if (model_has_connectors(model())) { + show_info(nullptr, + _L("SLA technology doesn't support cut with connectors") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } + return true; +} + +bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) +{ + wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + + if (reason == ConfigWizard::RR_USER) { + // Cancel sync before starting wizard to prevent two downloads at same time + preset_updater->cancel_sync(); + preset_updater->update_index_db(); + if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) + return false; + } + + auto wizard = new ConfigWizard(mainframe); + const bool res = wizard->run(reason, start_page); + + if (res) { + load_current_presets(); + + // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() + if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); + } + + return res; +} + +void GUI_App::show_desktop_integration_dialog() +{ +#ifdef __linux__ + //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + DesktopIntegrationDialog dialog(mainframe); + dialog.ShowModal(); +#endif //__linux__ +} + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG +void GUI_App::gcode_thumbnails_debug() +{ + const std::string BEGIN_MASK = "; thumbnail begin"; + const std::string END_MASK = "; thumbnail end"; + std::string gcode_line; + bool reading_image = false; + unsigned int width = 0; + unsigned int height = 0; + + wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK) + return; + + std::string in_filename = into_u8(dialog.GetPath()); + std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); + + boost::nowide::ifstream in_file(in_filename.c_str()); + std::vector rows; + std::string row; + if (in_file.good()) + { + while (std::getline(in_file, gcode_line)) + { + if (in_file.good()) + { + if (boost::starts_with(gcode_line, BEGIN_MASK)) + { + reading_image = true; + gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); + std::string::size_type x_pos = gcode_line.find('x'); + std::string width_str = gcode_line.substr(0, x_pos); + width = (unsigned int)::atoi(width_str.c_str()); + std::string height_str = gcode_line.substr(x_pos + 1); + height = (unsigned int)::atoi(height_str.c_str()); + row.clear(); + } + else if (reading_image && boost::starts_with(gcode_line, END_MASK)) + { + std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; + boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); + if (out_file.good()) + { + std::string decoded; + decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); + decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); + + out_file.write(decoded.c_str(), decoded.size()); + out_file.close(); + } + + reading_image = false; + width = 0; + height = 0; + rows.clear(); + } + else if (reading_image) + row += gcode_line.substr(2); + } + } + + in_file.close(); + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + +void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + WindowMetrics metrics = WindowMetrics::from_window(window); + app_config->set(config_key, metrics.serialize()); + app_config->save(); +} + +void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + if (! app_config->has(config_key)) { + window->Maximize(default_maximized); + return; + } + + auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); + if (! metrics) { + window->Maximize(default_maximized); + return; + } + + const wxRect& rect = metrics->get_rect(); + + if (app_config->get("restore_win_position") == "1") { + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); + app_config->save(); + window->SetPosition(rect.GetPosition()); + + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); + app_config->save(); + window->SetSize(rect.GetSize()); + + // revert "restore_win_position" value if application wasn't crashed + app_config->set("restore_win_position", "1"); + app_config->save(); + } + else + window->CenterOnScreen(); + + window->Maximize(metrics->get_maximized()); +} + +void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) +{ + /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window); + wxRect display; + if (display_idx == wxNOT_FOUND) { + display = wxDisplay(0u).GetClientArea(); + window->Move(display.GetTopLeft()); + } else { + display = wxDisplay(display_idx).GetClientArea(); + } + + auto metrics = WindowMetrics::from_window(window); + metrics.sanitize_for_display(display); + if (window->GetScreenRect() != metrics.get_rect()) { + window->SetSize(metrics.get_rect()); + } +} + +bool GUI_App::config_wizard_startup() +{ + if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { + run_wizard(ConfigWizard::RR_DATA_EMPTY); + return true; + } else if (get_app_config()->legacy_datadir()) { + // Looks like user has legacy pre-vendorbundle data directory, + // explain what this is and run the wizard + + MsgDataLegacy dlg; + dlg.ShowModal(); + + run_wizard(ConfigWizard::RR_DATA_LEGACY); + return true; + } + return false; +} + +bool GUI_App::check_updates(const bool verbose) +{ + PresetUpdater::UpdateResult updater_result; + try { + preset_updater->update_index_db(); + updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); + if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { + mainframe->Close(); + // Applicaiton is closing. + return false; + } + else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { + m_app_conf_exists = true; + } + else if (verbose && updater_result == PresetUpdater::R_NOOP) { + MsgNoUpdates dlg; + dlg.ShowModal(); + } + } + catch (const std::exception & ex) { + show_error(nullptr, ex.what()); + } + // Applicaiton will continue. + return true; +} + +bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) +{ + bool launch = true; + + // warning dialog containes a "Remember my choice" checkbox + std::string option_key = "suppress_hyperlinks"; + if (force_remember_choice || app_config->get(option_key).empty()) { + if (app_config->get(option_key).empty()) { + RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + dialog.ShowCheckBox(_L("Remember my choice")); + auto answer = dialog.ShowModal(); + launch = answer == wxID_YES; + if (dialog.IsCheckBoxChecked()) { + wxString preferences_item = _L("Suppress to open hyperlink in browser"); + wxString msg = + _L("PrusaSlicer will remember your choice.") + "\n\n" + + _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + + format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); + + MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); + if (msg_dlg.ShowModal() == wxID_CANCEL) + return false; + app_config->set(option_key, answer == wxID_NO ? "1" : "0"); + } + } + if (launch) + launch = app_config->get(option_key) != "1"; + } + // warning dialog doesn't containe a "Remember my choice" checkbox + // and will be shown only when "Suppress to open hyperlink in browser" is ON. + else if (app_config->get(option_key) == "1") { + MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + launch = dialog.ShowModal() == wxID_YES; + } + + return launch && wxLaunchDefaultBrowser(url, flags); +} + +// static method accepting a wxWindow object as first parameter +// void warning_catcher{ +// my($self, $message_dialog) = @_; +// return sub{ +// my $message = shift; +// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; +// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); +// $message_dialog +// ? $message_dialog->(@params) +// : Wx::MessageDialog->new($self, @params)->ShowModal; +// }; +// } + +// Do we need this function??? +// void GUI_App::notify(message) { +// auto frame = GetTopWindow(); +// // try harder to attract user attention on OS X +// if (!frame->IsActive()) +// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); +// +// // There used to be notifier using a Growl application for OSX, but Growl is dead. +// // The notifier also supported the Linux X D - bus notifications, but that support was broken. +// //TODO use wxNotificationMessage ? +// } + + +#ifdef __WXMSW__ +void GUI_App::associate_3mf_files() +{ + associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); +} + +void GUI_App::associate_stl_files() +{ + associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); +} + +void GUI_App::associate_gcode_files() +{ + associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); +} +#endif // __WXMSW__ + +void GUI_App::on_version_read(wxCommandEvent& evt) +{ + app_config->set("version_online", into_u8(evt.GetString())); + app_config->save(); + std::string opt = app_config->get("notify_release"); + if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { + return; + } + if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { + return; + } + // notification + /* + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable + , NotificationManager::NotificationLevel::ImportantNotificationLevel + , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) + , _u8L("See Download page.") + , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } + ); + */ + // updater + // read triggered_by_user that was set when calling GUI_App::app_version_check + app_updater(m_app_updater->get_triggered_by_user()); +} + +void GUI_App::app_updater(bool from_user) +{ + DownloadAppData app_data = m_app_updater->get_app_data(); + + if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) + { + BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; + MsgNoAppUpdates no_update_dialog; + no_update_dialog.ShowModal(); + return; + + } + + assert(!app_data.url.empty()); + assert(!app_data.target_path.empty()); + + // dialog with new version info + AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); + auto dialog_result = dialog.ShowModal(); + // checkbox "do not show again" + if (dialog.disable_version_check()) { + app_config->set("notify_release", "none"); + } + // Doesn't wish to update + if (dialog_result != wxID_OK) { + return; + } + // dialog with new version download (installer or app dependent on system) including path selection + AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); + dialog_result = dwnld_dlg.ShowModal(); + // Doesn't wish to download + if (dialog_result != wxID_OK) { + return; + } + app_data.target_path =dwnld_dlg.get_download_path(); + + // start download + this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); + app_data.start_after = dwnld_dlg.run_after_download(); + m_app_updater->set_app_data(std::move(app_data)); + m_app_updater->sync_download(); +} + +void GUI_App::app_version_check(bool from_user) +{ + if (from_user) { + if (m_app_updater->get_download_ongoing()) { + MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); + if (msgdlg.ShowModal() != wxID_YES) + return; + } + } + std::string version_check_url = app_config->version_check_url(); + m_app_updater->sync_version(version_check_url, from_user); +} + +void GUI_App::start_download(std::string url) +{ + if (!plater_) { + BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr."; + return; + } + //lets always init so if the download dest folder was changed, new dest is used + boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); + if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { + std::string msg = _utf8("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard."); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return; + } + m_downloader->init(dest_folder); + m_downloader->start_download(url); +} + +} // GUI +} //Slic3r diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index b76e903cd..d25f5adfa 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -798,7 +798,7 @@ void PlaterPresetComboBox::show_edit_menu() wxString PlaterPresetComboBox::get_preset_name(const Preset& preset) { - std::string name = preset.alias.empty() ? preset.name : preset.alias; + std::string name = preset.alias.empty() ? preset.name : (preset.vendor && preset.vendor->templates_profile ? preset.name : preset.alias); return from_u8(name + suffix(preset)); } @@ -909,14 +909,7 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } - const AppConfig* app_config = wxGetApp().app_config; - if (!template_presets.empty() && app_config->get("no_templates") == "0") { - set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { - Append(it->first, *it->second); - validate_selection(it->first == selected_user_preset); - } - } + if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); @@ -926,6 +919,15 @@ void PlaterPresetComboBox::update() } } + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + Append(it->first, *it->second); + validate_selection(it->first == selected_user_preset); + } + } + if (m_type == Preset::TYPE_PRINTER) { // add Physical printers, if any exists @@ -1122,17 +1124,7 @@ void TabPresetComboBox::update() if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } - const AppConfig* app_config = wxGetApp().app_config; - if (!template_presets.empty() && app_config->get("no_templates") == "0") { - set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - bool is_enabled = it->second.second; - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); - } - } + if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); @@ -1144,7 +1136,19 @@ void TabPresetComboBox::update() validate_selection(it->first == selected); } } - + + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } + } + if (m_type == Preset::TYPE_PRINTER) { // add Physical printers, if any exists diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 756fcb43e..cd3935ea0 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -12,9 +12,11 @@ #include #include #include +#include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/format.hpp" @@ -50,7 +52,7 @@ namespace Slic3r { static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; - +namespace { void copy_file_fix(const fs::path &source, const fs::path &target) { BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target); @@ -67,7 +69,21 @@ void copy_file_fix(const fs::path &source, const fs::path &target) static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; fs::permissions(target, perms); } - +std::string escape_string_url(const std::string& unescaped) +{ + std::string ret_val; + CURL* curl = curl_easy_init(); + if (curl) { + char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size()); + if (decoded) { + ret_val = std::string(decoded); + curl_free(decoded); + } + curl_easy_cleanup(curl); + } + return ret_val; +} +} struct Update { fs::path source; @@ -160,12 +176,17 @@ struct PresetUpdater::priv void set_download_prefs(const AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; - void sync_config(const VendorMap vendors, const std::string& profile_archive_url); + void sync_config(const VendorMap vendors, const std::string& index_archive_url); void check_install_indices() const; Updates get_config_updates(const Semver& old_slic3r_version) const; bool perform_updates(Updates &&updates, bool snapshot = true) const; void set_waiting_updates(Updates u); + // checks existence and downloads resource to cache + void get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const; + // checks existence and downloads resource to vendor or copy from cache to vendor + void get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const; + void update_index_db(); }; PresetUpdater::priv::priv() @@ -182,6 +203,11 @@ PresetUpdater::priv::priv() index_db = Index::load_db(); } +void PresetUpdater::priv::update_index_db() +{ + index_db = Index::load_db(); +} + // Pull relevant preferences from AppConfig void PresetUpdater::priv::set_download_prefs(const AppConfig *app_config) { @@ -235,9 +261,91 @@ void PresetUpdater::priv::prune_tmps() const } } +void PresetUpdater::priv::get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const +{ + if (filename.empty() || vendor.empty()) + return; + + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + + std::string escaped_filename = escape_string_url(filename); + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); + + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in vendor folder. No need to download."; + return; + } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download."; + return; + } + if (fs::exists(file_in_cache)) { // In cache/venodr_name/ dir. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in cache folder. No need to download."; + return; + } + + BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url + + if (!fs::exists(file_in_cache.parent_path())) + fs::create_directory(file_in_cache.parent_path()); + + get_file(resource_url, file_in_cache); + return; +} + +void PresetUpdater::priv::get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const +{ + if (filename.empty() || vendor.empty()) + return; + + std::string escaped_filename = escape_string_url(filename); + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); + + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in vendor folder. No need to download."; + return; + } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download."; + return; + } + if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir. + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url + + if (!fs::exists(file_in_vendor.parent_path())) + fs::create_directory(file_in_vendor.parent_path()); + + get_file(resource_url, file_in_vendor); + return; + } + + if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor + fs::create_directory(file_in_vendor.parent_path()); + + BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor; + copy_file_fix(file_in_cache, file_in_vendor); +} + // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& profile_archive_url) +void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& index_archive_url) { BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; @@ -246,21 +354,20 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string // Download profiles archive zip // dk: Do we want to return here on error? Or skip archive dwnld and unzip and work with previous run state cache / vendor? I think return. // Any error here also doesnt show any info in UI. Do we want maybe notification? - fs::path archive_path(cache_path / "Archive.zip"); - if (profile_archive_url.empty()) { + fs::path archive_path(cache_path / "vendor_indices.zip"); + if (index_archive_url.empty()) { BOOST_LOG_TRIVIAL(error) << "Downloading profile archive failed - url has no value."; return; } - BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip."; + BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip from " << index_archive_url; //check if idx_url is leading to our site - if (!boost::starts_with(profile_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - !boost::starts_with(profile_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + if (!boost::starts_with(index_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(index_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) { BOOST_LOG_TRIVIAL(error) << "Unsafe url path for vedor profiles archive zip. Download is rejected."; - // TODO: this return must be uncommented when correct address is in use - //return; + return; } - if (!get_file(profile_archive_url, archive_path)) { + if (!get_file(index_archive_url, archive_path)) { BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; return; } @@ -268,8 +375,16 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string return; } + enum class VendorStatus + { + IN_ARCHIVE, + IN_CACHE, + NEW_VERSION, + INSTALLED + }; + + std::vector> vendors_with_status; // Unzip archive to cache / vendor - std::vector vendors_only_in_archive; mz_zip_archive archive; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, archive_path.string())) { @@ -291,69 +406,59 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string } // create file from buffer fs::path tmp_path(cache_vendor_path / (name + ".tmp")); + if (!fs::exists(tmp_path.parent_path())) { + BOOST_LOG_TRIVIAL(error) << "Failed to unzip file " << name << ". Directories are not supported. Skipping file."; + continue; + } fs::path target_path(cache_vendor_path / name); fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); file.write(buffer.c_str(), buffer.size()); file.close(); - fs::rename(tmp_path, target_path); - - if (name.substr(name.size() - 3) == "ini") - vendors_only_in_archive.push_back(name); + boost::system::error_code ec; + bool exists = fs::exists(tmp_path, ec); + if(!exists || ec) { + BOOST_LOG_TRIVIAL(error) << "Failed to find unzipped file at " << tmp_path << ". Terminating Preset updater synchorinzation." ; + close_zip_reader(&archive); + return; + } + fs::rename(tmp_path, target_path, ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << "Failed to rename unzipped file at " << tmp_path << ". Terminating Preset updater synchorinzation. Error message: " << ec.message(); + close_zip_reader(&archive); + return; + } + // TODO: what if unexpected happens here (folder inside zip) - crash! + + if (name.substr(name.size() - 3) == "idx") + vendors_with_status.emplace_back(name.substr(0, name.size() - 4), VendorStatus::IN_ARCHIVE); // asume for now its only in archive - if not, it will change later. } } } close_zip_reader(&archive); } - auto get_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, - const std::string& url, const fs::path& vendor_path, - const fs::path& rsrc_path, const fs::path& cache_path) - { - if (filename.empty() || vendor.empty()) - return; - - if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) - { - throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); - } - - const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); - const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); - const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); - - if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. - return; - } - if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. - return; - } - - BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - - const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url - - if (!fs::exists(file_in_cache.parent_path())) - fs::create_directory(file_in_cache.parent_path()); - - self.get_file(resource_url, file_in_cache); - return; - }; - // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { if (cancel) { return; } + auto archive_it = std::find_if(vendors_with_status.begin(), vendors_with_status.end(), + [&index](const std::pair& element) { return element.first == index.vendor(); }); + //assert(archive_it != vendors_with_status.end()); // this would mean there is a index for vendor that is missing in recently downloaded archive const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { // Not installed vendor yet we need to check missing thumbnails (of new printers) BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor(); + if (archive_it != vendors_with_status.end()) + archive_it->second = VendorStatus::IN_CACHE; continue; } + if (archive_it != vendors_with_status.end()) + archive_it->second = VendorStatus::INSTALLED; + const VendorProfile &vendor = vendor_it->second; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); @@ -368,7 +473,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string continue; } if (new_index.version() < index.version()) { - BOOST_LOG_TRIVIAL(warning) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); + BOOST_LOG_TRIVIAL(info) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); continue; } copy_file_fix(idx_path_temp, idx_path); @@ -401,23 +506,28 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string recommended.to_string()); if (vendor.config_version >= recommended) { continue; } - - const auto path_in_archive = cache_vendor_path / (vendor.id + ".ini"); - const auto path_in_cache = cache_path / (vendor.id + ".ini"); - // Check version - if (!boost::filesystem::exists(path_in_archive)) - continue; - // vp is fully loaded to get all resources - auto vp = VendorProfile::from_ini(path_in_archive, true); - if (vp.config_version != recommended) - continue; - copy_file_fix(path_in_archive, path_in_cache); - // vendors that are checked here, doesnt need to be checked again later - const auto archive_it = std::find(vendors_only_in_archive.begin(), vendors_only_in_archive.end(), index.vendor() + ".ini"); - if (archive_it != vendors_only_in_archive.end()) { - vendors_only_in_archive.erase(archive_it); - } + // vendors that are checked here, doesnt need to be checked again later + if (archive_it != vendors_with_status.end()) + archive_it->second = VendorStatus::NEW_VERSION; + + // Download recomended ini to cache + const auto path_in_cache = cache_path / (vendor.id + ".ini"); + BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; + const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string()); + const auto bundle_path = cache_path / (vendor.id + ".ini"); + if (!get_file(bundle_url, bundle_path)) + continue; + if (cancel) + return; + // vp is fully loaded to get all resources + VendorProfile vp; + try { + vp = VendorProfile::from_ini(bundle_path, true); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.id, bundle_path, e.what()); + continue; + } // check the fresh bundle for missing resources // for that, the ini file must be parsed (done above) for (const auto& model : vp.models) { @@ -425,9 +535,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string if (! res.empty()) { try { - // for debug (wont pass check inside function) - //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; - get_missing_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + get_missing_resource(vp.id, res, vendor.config_update_url); } catch (const std::exception& e) { @@ -441,28 +549,222 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string } } // Download missing thumbnails for not-installed vendors. - for (const std::string& vendor : vendors_only_in_archive) - { - BOOST_LOG_TRIVIAL(error) << vendor; - const auto path_in_archive = cache_vendor_path / vendor; - assert(boost::filesystem::exists(path_in_archive)); - auto vp = VendorProfile::from_ini(path_in_archive, true); - for (const auto& model : vp.models) { - if (!model.thumbnail.empty()) { - try - { - // for debug (wont pass check inside function) - //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; - get_missing_resource(vp.id, model.thumbnail, vp.config_update_url, vendor_path, rsrc_path, cache_path); + //for (const std::string& vendor : vendors_only_in_archive) + for (const std::pair& vendor : vendors_with_status) { + if (vendor.second == VendorStatus::IN_ARCHIVE) { + // index in archive and not in cache and not installed vendor + + const auto idx_path_in_archive = cache_vendor_path / (vendor.first + ".idx"); + const auto ini_path_in_archive = cache_vendor_path / (vendor.first + ".ini"); + if (!fs::exists(idx_path_in_archive)) + continue; + Index index; + try { + index.load(idx_path_in_archive); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_archive, vendor.first); + continue; + } + const auto recommended_it = index.recommended(); + if (recommended_it == index.end()) { + BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_archive); + continue; + } + const auto recommended = recommended_it->config_version; + if (!fs::exists(ini_path_in_archive)){ + // Download recommneded to vendor - we do not have any existing ini file so we have to use hardcoded url. + const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url(); + const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) + continue; + } else { + // check existing ini version + // then download recommneded to vendor if needed + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, true); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; } - catch (const std::exception& e) - { - BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + if (vp.config_version != recommended) { + const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url(); + const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) + continue; } } - if (cancel) - return; + // check missing thumbnails + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; + } + for (const auto& model : vp.models) { + if (!model.thumbnail.empty()) { + try + { + get_missing_resource(vp.id, model.thumbnail, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + } + } else if (vendor.second == VendorStatus::IN_CACHE) { + // find those where archive index recommends other version than index in cache and get it if not present + const auto idx_path_in_archive = cache_vendor_path / (vendor.first + ".idx"); + const auto ini_path_in_archive = cache_vendor_path / (vendor.first + ".ini"); + const auto idx_path_in_cache = cache_path / (vendor.first + ".idx"); + + if (!fs::exists(idx_path_in_archive) || !fs::exists(idx_path_in_cache)) + continue; + + // Compare index in cache and recetly downloaded one as part of zip archive + Index index_cache; + try { + index_cache.load(idx_path_in_cache); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_cache, vendor.first); + continue; + } + const auto recommended_it_cache = index_cache.recommended(); + if (recommended_it_cache == index_cache.end()) { + BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_cache); + continue; + } + const auto recommended_cache = recommended_it_cache->config_version; + + Index index_archive; + try { + index_archive.load(idx_path_in_archive); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_archive, vendor.first); + continue; + } + const auto recommended_it_archive = index_archive.recommended(); + if (recommended_it_archive == index_archive.end()) { + BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_archive); + continue; + } + const auto recommended_archive = recommended_it_archive->config_version; + if (recommended_archive <= recommended_cache) { + // There isn't more recent recomended version online. This vendor is also not istalled. + // Thus only .ini is in resources and came with installation. + // And we expect all resources are present. + continue; + } + + // Download new .ini if needed. So next time user runs Wizard, most recent profiles are shown & installed. + if (!fs::exists(ini_path_in_archive) || fs::is_empty(ini_path_in_archive)) { + // download recommneded to vendor + const fs::path ini_path_in_rsrc = rsrc_path / (vendor.first + ".ini"); + if (!fs::exists(ini_path_in_rsrc)) { + // THIS SHOULD NOT HAPPEN + continue; + } + // Get download path from existing ini. + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_rsrc, false); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_rsrc, e.what()); + continue; + } + const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) { + BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_rsrc); + continue; + } + } else { + // Check existing ini version. + // Then download recommneded to vendor if needed. + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, false); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; + } + if (vp.config_version != recommended_archive) { + const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) { + BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_archive); + continue; + } + } + } + + if (!fs::exists(ini_path_in_archive)) { + BOOST_LOG_TRIVIAL(error) << "Resources check failed to find ini file for vendor: " << vendor.first; + continue; + } + // check missing thumbnails + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; + } + for (const auto& model : vp.models) { + if (!model.thumbnail.empty()) { + try + { + get_missing_resource(vp.id, model.thumbnail, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + } + } else if (vendor.second == VendorStatus::INSTALLED || vendor.second == VendorStatus::NEW_VERSION) { + // Installed vendors need to check that no resource is missing. Do this only for files in vendor folder (not in resorces) + // VendorStatus::NEW_VERSION might seem like a mistake here since files are downloaded when preparing update higher in this function. + // But this is a check for ini file in vendor where resources might be still missing since last update. + const auto path_in_vendor = vendor_path / (vendor.first + ".ini"); + if(!fs::exists(path_in_vendor)) + continue; + VendorProfile vp; + try { + vp = VendorProfile::from_ini(path_in_vendor, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, path_in_vendor, e.what()); + continue; + } + for (const auto& model : vp.models) { + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail }) { + if (!model.thumbnail.empty()) { + try + { + get_or_copy_missing_resource(vp.id, res, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + } + } } } } @@ -513,8 +815,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version } // Perform a basic load and check the version of the installed preset bundle. - auto vp = VendorProfile::from_ini(bundle_path, false); - + VendorProfile vp; + try { + vp = VendorProfile::from_ini(bundle_path, false); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", idx.vendor(), bundle_path, e.what()); + continue; + } // Getting a recommended version from the latest index, wich may have been downloaded // from the internet, or installed / updated from the installation resources. auto recommended = idx.recommended(); @@ -590,18 +898,13 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version if (new_vp.config_version == recommended->config_version) { // The config bundle from the cache directory matches the recommended version of the index from the cache directory. // This is the newest known recommended config. Use it. - if (PresetUtils::vendor_profile_has_all_resources(new_vp)) { - // All resources for the profile in cache dir are existing (either in resources or data_dir/vendor or waiting to be copied to vendor from cache) - // This final check is not performed for updates from resources dir below. - new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); - // and install the config index from the cache into vendor's directory. - bundle_path_idx_to_install = idx.path(); - found = true; - } else { - // Resource missing - treat as if the INI file was corrupted. - throw Slic3r::CriticalException("Some resources are missing."); + if (!PresetUtils::vendor_profile_has_all_resources(new_vp)) { + BOOST_LOG_TRIVIAL(warning) << "Some resources are missing for update of vedor " << new_vp.id; } - + new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); + // and install the config index from the cache into vendor's directory. + bundle_path_idx_to_install = idx.path(); + found = true; } } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(info) << format("Failed to load the config bundle `%1%`: %2%", path_in_cache.string(), ex.what()); @@ -715,7 +1018,7 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size()); - wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles")); + wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles") , 100, nullptr, wxPD_AUTO_HIDE); progress_dialog.Pulse(); for (const auto &update : updates.updates) { @@ -755,50 +1058,16 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_prints) { obsolete_remover("sla_print", name); } for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } - - auto get_and_copy_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, - const fs::path& vendor_path, const fs::path& rsrc_path, - const fs::path& cache_path, const std::string& url) - { - if (filename.empty() || vendor.empty()) - return; - - const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); - const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); - const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); - - if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. - return; - } - if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. - return; - } - if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir. - if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) - { - throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); - } - BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename; - - const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url - - if (!fs::exists(file_in_vendor.parent_path())) - fs::create_directory(file_in_vendor.parent_path()); - - self.get_file(resource_url, file_in_vendor); - return; - } - - if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor - fs::create_directory(file_in_vendor.parent_path()); - - BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor; - copy_file_fix(file_in_cache, file_in_vendor); - }; - + // check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/ - auto vp = VendorProfile::from_ini(update.target, true); + VendorProfile vp; + try { + vp = VendorProfile::from_ini(update.target, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", update.target, e.what()); + continue; + } progress_dialog.Update(1, GUI::format_wxstr(_L("Downloading resources for %1%."),vp.id)); progress_dialog.Pulse(); for (const auto& model : vp.models) { @@ -807,17 +1076,14 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons continue; try { - // for debug (wont pass check inside function) - //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; - get_and_copy_missing_resource(vp.id, resource, vendor_path, rsrc_path, cache_path, vp.config_update_url); + get_or_copy_missing_resource(vp.id, resource, vp.config_update_url); } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(error) << "Failed to prepare " << resource << " for " << vp.id << " " << model.id << ": " << e.what(); } } - } - + } } progress_dialog.Destroy(); @@ -825,7 +1091,7 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons return true; } - + void PresetUpdater::priv::set_waiting_updates(Updates u) { waiting_updates = u; @@ -858,11 +1124,11 @@ void PresetUpdater::sync(const PresetBundle *preset_bundle) // Unfortunatelly as of C++11, it needs to be copied again // into the closure (but perhaps the compiler can elide this). VendorMap vendors = preset_bundle->vendors; - std::string profile_archive_url =GUI::wxGetApp().app_config->profile_archive_url(); + std::string index_archive_url = GUI::wxGetApp().app_config->index_archive_url(); - p->thread = std::thread([this, vendors, profile_archive_url]() { + p->thread = std::thread([this, vendors, index_archive_url]() { this->p->prune_tmps(); - this->p->sync_config(std::move(vendors), profile_archive_url); + this->p->sync_config(std::move(vendors), index_archive_url); }); } @@ -1052,16 +1318,40 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vectorvendor_path / bundle).replace_extension(".ini"); bool is_in_rsrc = fs::exists(path_in_rsrc); - bool is_in_cache_vendor = fs::exists(path_in_cache_vendor); + bool is_in_cache_vendor = fs::exists(path_in_cache_vendor) && !fs::is_empty(path_in_cache_vendor); // find if in cache vendor is newer version than in resources if (is_in_cache_vendor) { - auto version_cache = VendorProfile::from_ini(path_in_cache_vendor, false).config_version; - auto version_rsrc = !is_in_rsrc ? Semver::zero() : VendorProfile::from_ini(path_in_rsrc, false).config_version; + Semver version_cache = Semver::zero(); + try { + auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); + version_cache = vp_cache.config_version; + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_cache_vendor, e.what()); + // lets use file in resources + if (is_in_rsrc) { + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } + continue; + } + + Semver version_rsrc = Semver::zero(); + try { + if (is_in_rsrc) { + auto vp = VendorProfile::from_ini(path_in_rsrc, false); + version_rsrc = vp.config_version; + } + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_rsrc, e.what()); + continue; + } if (!is_in_rsrc || version_cache > version_rsrc) { // in case we are installing from cache / vendor. we should also copy index to cache // This needs to be done now bcs the current one would be missing this version on next start + // dk: Should we copy it to vendor dir too? auto path_idx_cache_vendor(path_in_cache_vendor); path_idx_cache_vendor.replace_extension(".idx"); auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); @@ -1121,4 +1411,9 @@ bool PresetUpdater::version_check_enabled() const return p->enabled_version_check; } +void PresetUpdater::update_index_db() +{ + p->update_index_db(); +} + } diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 5bcba615b..85875e5a3 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -53,6 +53,8 @@ public: // Providing old slic3r version upgrade profiles on upgrade of an application even in case // that the config index installed from the Internet is equal to the index contained in the installation package. UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const; + + void update_index_db(); // "Update" a list of bundles from resources or cache/vendor (behaves like an online update). bool install_bundles_rsrc_or_cache_vendor(std::vector bundles, bool snapshot = true) const; From b403ba10c126c45ff8c38ab69fb62455081507ef Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 18 Jan 2023 11:20:22 +0100 Subject: [PATCH 144/206] Fix mirroring inside volumes trafos Fix leftover after successive slicing runs --- src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp | 3 ++- src/libslic3r/SLAPrintSteps.cpp | 15 ++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index f81a44541..aabe9a2de 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -26,7 +26,8 @@ MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart) MeshBoolean::cgal::CGALMeshPtr ret; indexed_triangle_set m = *its; - its_transform(m, get_transform(csgpart)); + auto tr = get_transform(csgpart); + its_transform(m, get_transform(csgpart), true); try { ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 269195bc0..92260deec 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -150,7 +150,7 @@ static indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh) const indexed_triangle_set * pmesh = csg::get_mesh(csgpart); if (pmesh && op == csg::CSGType::Union) { indexed_triangle_set mcpy = *pmesh; - its_transform(mcpy, csg::get_transform(csgpart)); + its_transform(mcpy, csg::get_transform(csgpart), true); its_merge(m, mcpy); } } @@ -292,9 +292,8 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st m = generate_preview_vdb(po, step); } - if (!m.empty()) - po.m_preview_meshes[step] = - std::make_shared(std::move(m)); + po.m_preview_meshes[step] = + std::make_shared(std::move(m)); for (size_t i = size_t(step) + 1; i < slaposCount; ++i) { @@ -408,13 +407,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) csg_inserter{po.m_mesh_to_slice, slaposDrillHoles}, csg::mpartsDrillHoles); - auto r = po.m_mesh_to_slice.equal_range(slaposDrillHoles); - - // update preview mesh - if (r.first != r.second) - generate_preview(po, slaposDrillHoles); - else - po.m_preview_meshes[slaposDrillHoles] = po.get_mesh_to_print(); + generate_preview(po, slaposDrillHoles); // Release the data, won't be needed anymore, takes huge amount of ram if (po.m_hollowing_data && po.m_hollowing_data->interior) From b70571cd79ac74597682203c1aedd30b3b203bf5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 18 Jan 2023 12:20:21 +0100 Subject: [PATCH 145/206] Fixed Layer::sort_perimeters_into_islands() for fuzzy skin Follow-up to 52ea2edf842e81e0f81fcd8303b69d288d903ae3 1) There was a bug in accessing the "perimeter is external" property, ExtrusionCollection returns "mixed", the embedded ExtrusionPath has to be queried directly. 2) The search bounding box has to be extended by the maximum offset introduced by fuzzy skin algorithm. For Arachne the fuzzy skin algorithm observes fuzzy_skin_point_dist. --- src/libslic3r/Layer.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 44fc18f91..9b0004e98 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -577,12 +577,22 @@ void Layer::sort_perimeters_into_islands( } if (! sample_set) { // If there is no infill, take a sample of some inner perimeter. - for (uint32_t iperimeter : extrusions.first) - if (const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter]; ! ee.role().is_external()) { - sample = ee.first_point(); + for (uint32_t iperimeter : extrusions.first) { + const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter]; + if (ee.is_collection()) { + for (const ExtrusionEntity *ee2 : dynamic_cast(ee).entities) + if (! ee2->role().is_external()) { + sample = ee2->first_point(); + sample_set = true; + goto loop_end; + } + } else if (! ee.role().is_external()) { + sample = ee.first_point(); sample_set = true; break; } + } + loop_end: if (! sample_set) { if (! extrusions.second.empty()) { // If there is no inner perimeter, take a sample of some gap fill extrusion. @@ -752,7 +762,9 @@ void Layer::sort_perimeters_into_islands( const PrintRegionConfig ®ion_config = this_layer_region.region().config(); const auto bbox_eps = scaled( EPSILON + print_config.gcode_resolution.value + - (region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value)); + (region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value + //FIXME it looks as if Arachne could extend open lines by fuzzy_skin_point_dist, which does not seem right. + + region_config.fuzzy_skin_point_dist.value)); auto point_inside_surface_dist2 = [&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps] (const size_t lslice_idx, const Point &point) { From c3330c119b5c051ad1b67237416267c7422407f1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 18 Jan 2023 13:55:15 +0100 Subject: [PATCH 146/206] Prevent rare support strut and pinhead collisions By sending a ray through the center of each strut and increasing the ray count on the surface of the struts --- src/libslic3r/SLA/SupportTreeUtils.hpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 93f370c32..aebd50950 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -31,7 +31,7 @@ using Slic3r::Geometry::spheric_to_dir; // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space template class PointRing { - std::array m_phis; + std::array m_phis; // Two vectors that will be perpendicular to each other and to the // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a @@ -51,7 +51,7 @@ class PointRing { public: - PointRing(const Vec3d &n) : m_phis{linspace_array(0., 2 * PI)} + PointRing(const Vec3d &n) : m_phis{linspace_array(0., 2 * PI)} { // We have to address the case when the direction vector v (same as // dir) is coincident with one of the world axes. In this case two of @@ -70,7 +70,10 @@ public: Vec3d get(size_t idx, const Vec3d &src, double r) const { - double phi = m_phis[idx]; + if (idx == 0) + return src; + + double phi = m_phis[idx - 1]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -137,9 +140,9 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface {} }; -using Beam = Beam_<8>; +using Beam = Beam_<>; -template +template Hit beam_mesh_hit(Ex policy, const AABBMesh &mesh, const Beam_ &beam, @@ -196,7 +199,10 @@ Hit pinhead_mesh_hit(Ex ex, double width, double sd) { - static const size_t SAMPLES = 8; + // Support tree generation speed depends heavily on this value. 8 is almost + // ok, but to prevent rare cases of collision, 16 is necessary, which makes + // the algorithm run about 60% longer. + static const size_t SAMPLES = 16; // Move away slightly from the touching point to avoid raycasting on the // inner surface of the mesh. From ee15fe62382a08842f2c105d25c069a19d971354 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 18 Jan 2023 15:15:01 +0100 Subject: [PATCH 147/206] Fix crash with cut gizmo --- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index b33b0b202..a8dc16c55 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -259,9 +259,11 @@ void Raycaster::on_update() bool force_raycaster_regeneration = false; if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) { // For sla printers we use the mesh generated by the backend + std::shared_ptr preview_mesh_ptr; const SLAPrintObject* po = get_pool()->selection_info()->print_object(); - assert(po != nullptr); - std::shared_ptr preview_mesh_ptr = po->get_mesh_to_print(); + if (po) + preview_mesh_ptr = po->get_mesh_to_print(); + if (preview_mesh_ptr) m_sla_mesh_cache = TriangleMesh{*preview_mesh_ptr}; @@ -323,12 +325,13 @@ void ObjectClipper::on_update() if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) { // For sla printers we use the mesh generated by the backend const SLAPrintObject* po = get_pool()->selection_info()->print_object(); - assert(po != nullptr); - auto partstoslice = po->get_parts_to_slice(); - if (! partstoslice.empty()) { - mc = std::make_unique(); - mc->set_mesh(range(partstoslice)); - mc_tr = Geometry::Transformation{po->trafo().inverse().cast()}; + if (po) { + auto partstoslice = po->get_parts_to_slice(); + if (! partstoslice.empty()) { + mc = std::make_unique(); + mc->set_mesh(range(partstoslice)); + mc_tr = Geometry::Transformation{po->trafo().inverse().cast()}; + } } } From 96762a2119e60aacec45e68fb52c1b9576498849 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 18 Jan 2023 15:29:55 +0100 Subject: [PATCH 148/206] No new version available notification --- src/slic3r/GUI/GUI_App.cpp | 15 ++++++++++++++- src/slic3r/GUI/NotificationManager.cpp | 23 +++++++++++++++++++++++ src/slic3r/GUI/NotificationManager.hpp | 4 ++++ src/slic3r/Utils/AppUpdater.cpp | 5 +++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3e561d04e..b901e7f10 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1249,7 +1249,7 @@ bool GUI_App::on_init_inner() std::string evt_string = into_u8(evt.GetString()); if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); - this->plater_->get_notification_manager()->push_notification( notif_type + this->plater_->get_notification_manager()->push_version_notification( notif_type , NotificationManager::NotificationLevel::ImportantNotificationLevel , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) , _u8L("See Releases page.") @@ -3300,6 +3300,19 @@ void GUI_App::on_version_read(wxCommandEvent& evt) return; } if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { + if (m_app_updater->get_triggered_by_user()) + { + std::string text = (*Semver::parse(into_u8(evt.GetString())) == Semver()) + ? Slic3r::format(_u8L("Check for application update has failed.")) + : Slic3r::format(_u8L("No new version is available. Latest release version is %1%."), evt.GetString()); + + this->plater_->get_notification_manager()->push_version_notification(NotificationType::NoNewReleaseAvailable + , NotificationManager::NotificationLevel::RegularNotificationLevel + , text + , std::string() + , std::function() + ); + } return; } // notification diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index e6f9d952a..e8fd68c81 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -2246,6 +2246,29 @@ void NotificationManager::push_simplify_suggestion_notification(const std::stri notification->object_id = object_id; push_notification_data(std::move(notification), 0); } +void NotificationManager::push_version_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext, std::function callback) +{ + assert (type == NotificationType::NewAlphaAvailable + || type == NotificationType::NewBetaAvailable + || type == NotificationType::NoNewReleaseAvailable); + + for (std::unique_ptr& notification : m_pop_notifications) { + // NoNewReleaseAvailable must not show if alfa / beta is on. + NotificationType nttype = notification->get_type(); + if (type == NotificationType::NoNewReleaseAvailable + && (notification->get_type() == NotificationType::NewAlphaAvailable + || notification->get_type() == NotificationType::NewBetaAvailable)) { + return; + } + // NoNewReleaseAvailable must close if alfa / beta is being push. + if (notification->get_type() == NotificationType::NoNewReleaseAvailable + && (type == NotificationType::NewAlphaAvailable + || type == NotificationType::NewBetaAvailable)) { + notification->close(); + } + } + push_notification(type, level, text, hypertext, callback); +} void NotificationManager::close_notification_of_type(const NotificationType type) { for (std::unique_ptr ¬ification : m_pop_notifications) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 7cd77a304..b3a39a936 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -56,6 +56,7 @@ enum class NotificationType // Like NewAppAvailable but with text and link for alpha / bet release NewAlphaAvailable, NewBetaAvailable, + NoNewReleaseAvailable, // Notification on the start of PrusaSlicer, when updates of system profiles are detected. // Contains a hyperlink to execute installation of the new system profiles. PresetUpdateAvailable, @@ -191,6 +192,9 @@ public: // Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning. void push_simplify_suggestion_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", std::function callback = std::function()); + // Could be either NewAlphaAvailable, NewBetaAvailable or NoNewReleaseAvailable - this function only makes sure only 1 is visible. + void push_version_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext, + std::function callback); // Close object warnings, whose ObjectID is not in the list. // living_oids is expected to be sorted. void remove_simplify_suggestion_of_released_objects(const std::vector& living_oids); diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index a17adea8a..bd71e86ec 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -323,6 +323,11 @@ void AppUpdater::priv::parse_version_string(const std::string& body) return; #endif // 0 BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Checking for application update has failed."; + // Lets send event with current version, this way if user triggered this check, it will notify him about no new version online. + std::string version = Semver().to_string(); + wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE); + evt->SetString(GUI::from_u8(version)); + GUI::wxGetApp().QueueEvent(evt); return; } std::string tree_string = body.substr(start); From bdcb7732026aa2e6d71108434f892ed6f4b5ce3c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 18 Jan 2023 16:01:09 +0100 Subject: [PATCH 149/206] replace triangulation in SupportSpotGenerator with triangle formula and winding number Use the same apporach in computation of polygon area principal components --- src/libslic3r/BridgeDetector.hpp | 7 +- src/libslic3r/PrincipalComponents2D.cpp | 132 ++++++++++++++++-------- src/libslic3r/PrincipalComponents2D.hpp | 9 +- src/libslic3r/SupportSpotsGenerator.cpp | 105 ++++++------------- 4 files changed, 133 insertions(+), 120 deletions(-) diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index b11736417..bc5da9712 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -82,12 +82,11 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co if (floating_polylines.empty()) { // consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges - //use 3mm resolution (should be quite fast, and rough estimation should not cause any problems here) - auto [pc1, pc2] = compute_principal_components(overhang_area, 3.0); - if (pc2 == Vec2d::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok + auto [pc1, pc2] = compute_principal_components(overhang_area); + if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok return {Vec2d{1.0,0.0}, 0.0}; } else { - return {pc2.normalized(), 0.0}; + return {pc2.normalized().cast(), 0.0}; } } diff --git a/src/libslic3r/PrincipalComponents2D.cpp b/src/libslic3r/PrincipalComponents2D.cpp index 4b7c3a1da..7bdf79315 100644 --- a/src/libslic3r/PrincipalComponents2D.cpp +++ b/src/libslic3r/PrincipalComponents2D.cpp @@ -3,53 +3,97 @@ namespace Slic3r { -// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first -std::tuple compute_principal_components(const Polygons &polys, const double unscaled_resolution) + + +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c) { - // USING UNSCALED VALUES - const Vec2d pixel_size = Vec2d(unscaled_resolution, unscaled_resolution); - const auto bb = get_extents(polys); - const Vec2i pixel_count = unscaled(bb.size()).cwiseQuotient(pixel_size).cast() + Vec2i::Ones(); + // based on the following guide: + // Denote the vertices of S by a, b, c. Then the map + // g:(u,v)↦a+u(b−a)+v(c−a) , + // which in coordinates appears as + // g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(1) + // obviously maps S′ bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain + // ∫Sf(x,y)d(x,y)=∫S′f(x(u,v),y(u,v))∣∣Jg(u,v)∣∣ d(u,v) . + // In the case at hand the Jacobian determinant is a constant: From (1) we obtain + // Jg(u,v)=det[xuyuxvyv]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) . + // Therefore we can write + // ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(u,v) dv du , + // where f~ denotes the pullback of f to S′: + // f~(u,v):=f(x(u,v),y(u,v)) . + // Don't forget taking the absolute value of Jg! - std::vector lines{}; - for (Line l : to_lines(polys)) { lines.emplace_back(unscaled(l.a), unscaled(l.b)); } - AABBTreeIndirect::Tree<2, double> tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); - auto is_inside = [&](const Vec2d &point) { - size_t nearest_line_index_out = 0; - Vec2d nearest_point_out = Vec2d::Zero(); - auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, point, nearest_line_index_out, nearest_point_out); - if (distance < 0) return false; - const Linef &line = lines[nearest_line_index_out]; - Vec2d v1 = line.b - line.a; - Vec2d v2 = point - line.a; - if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { return true; } - return false; - }; + float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y())); - double pixel_area = pixel_size.x() * pixel_size.y(); - Vec2d centroid_accumulator = Vec2d::Zero(); - Vec2d second_moment_of_area_accumulator = Vec2d::Zero(); - double second_moment_of_area_covariance_accumulator = 0.0; - double area = 0.0; + // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x) + // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y) + // second moment of area for x: f(x, y) = x^2; + // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form) - for (int x = 0; x < pixel_count.x(); x++) { - for (int y = 0; y < pixel_count.y(); y++) { - Vec2d position = unscaled(bb.min) + pixel_size.cwiseProduct(Vec2d{x, y}); - if (is_inside(position)) { - area += pixel_area; - centroid_accumulator += pixel_area * position; - second_moment_of_area_accumulator += pixel_area * position.cwiseProduct(position); - second_moment_of_area_covariance_accumulator += pixel_area * position.x() * position.y(); - } + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2) + + Vec2f second_moment_of_area_xy = jacobian_determinant_abs * + (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) + + a.cwiseProduct(b + c)) / + 12.0f; + // second moment of area covariance : f(x, y) = x*y; + // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form) + //(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2)) + // == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) + + // intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv = + // 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 + // b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - + // 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du = + // 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2) + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) * + (a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() + + b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y()); + + float area = jacobian_determinant_abs * 0.5f; + + Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f; + + return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance}; +}; + +// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first +std::tuple compute_principal_components(const Polygons &polys) +{ + Vec2f centroid_accumulator = Vec2f::Zero(); + Vec2f second_moment_of_area_accumulator = Vec2f::Zero(); + float second_moment_of_area_covariance_accumulator = 0.0f; + float area = 0.0f; + + for (const Polygon &poly : polys) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [triangle_area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + area += sign * triangle_area; + centroid_accumulator += sign * first_moment_of_area; + second_moment_of_area_accumulator += sign * second_moment_area; + second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; } } if (area <= 0.0) { - return {Vec2d::Zero(), Vec2d::Zero()}; + return {Vec2f::Zero(), Vec2f::Zero()}; } - Vec2d centroid = centroid_accumulator / area; - Vec2d variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid); + Vec2f centroid = centroid_accumulator / area; + Vec2f variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid); double covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); #if 0 std::cout << "area : " << area << std::endl; @@ -58,7 +102,7 @@ std::tuple compute_principal_components(const Polygons &polys, con std::cout << "covariance : " << covariance << std::endl; #endif if (abs(covariance) < EPSILON) { - std::tuple result{Vec2d{variance.x(), 0.0}, Vec2d{0.0, variance.y()}}; + std::tuple result{Vec2f{variance.x(), 0.0}, Vec2f{0.0, variance.y()}}; if (variance.y() > variance.x()) { return {std::get<1>(result), std::get<0>(result)}; } else @@ -72,12 +116,12 @@ std::tuple compute_principal_components(const Polygons &polys, con // Eigenvalues are solutions to det(C - lI) = 0, where l is the eigenvalue and I unit matrix // Eigenvector for eigenvalue l is any vector v such that Cv = lv - double eigenvalue_a = 0.5 * (variance.x() + variance.y() + - sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance)); - double eigenvalue_b = 0.5 * (variance.x() + variance.y() - - sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance)); - Vec2d eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0}; - Vec2d eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0}; + float eigenvalue_a = 0.5f * (variance.x() + variance.y() + + sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance)); + float eigenvalue_b = 0.5f * (variance.x() + variance.y() - + sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance)); + Vec2f eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0f}; + Vec2f eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0f}; #if 0 std::cout << "eigenvalue_a: " << eigenvalue_a << std::endl; diff --git a/src/libslic3r/PrincipalComponents2D.hpp b/src/libslic3r/PrincipalComponents2D.hpp index 0eccdfcc5..dc8897a7a 100644 --- a/src/libslic3r/PrincipalComponents2D.hpp +++ b/src/libslic3r/PrincipalComponents2D.hpp @@ -9,8 +9,15 @@ namespace Slic3r { +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c); + // returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first -std::tuple compute_principal_components(const Polygons &polys, const double unscaled_resolution); +std::tuple compute_principal_components(const Polygons &polys); } diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index eaf7dd57d..e881f7bba 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -1,5 +1,6 @@ #include "SupportSpotsGenerator.hpp" +#include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" @@ -7,6 +8,7 @@ #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" +#include "PrincipalComponents2D.hpp" #include "Print.hpp" #include "PrintBase.hpp" #include "Tesselate.hpp" @@ -117,13 +119,14 @@ public: size_t to_cell_index(const Vec3i &cell_coords) const { +#ifdef DETAILED_DEBUG_LOGS assert(cell_coords.x() >= 0); assert(cell_coords.x() < cell_count.x()); assert(cell_coords.y() >= 0); assert(cell_coords.y() < cell_count.y()); assert(cell_coords.z() >= 0); assert(cell_coords.z() < cell_count.z()); - +#endif return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x(); } @@ -244,6 +247,7 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit const float flow_width = get_flow_width(layer_region, entity->role()); + // Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, prev_layer_lines, flow_width, params.bridge_distance); @@ -262,6 +266,7 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : ExtrusionLine{}; + // correctify the distance sign using slice polygons float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; curr_point.distance *= sign; @@ -297,84 +302,39 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit } } -// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance -// none of the values is divided/normalized by area. -// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) -// f(x,y) = x^2 for second moment of area -// and f(x,y) = x*y for second moment of area covariance -std::tuple compute_triangle_moments_of_area(const Vec2f &a, const Vec2f &b, const Vec2f &c) -{ - // based on the following guide: - // Denote the vertices of S by a, b, c. Then the map - // g:(u,v)↦a+u(b−a)+v(c−a) , - // which in coordinates appears as - // g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(1) - // obviously maps S′ bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain - // ∫Sf(x,y)d(x,y)=∫S′f(x(u,v),y(u,v))∣∣Jg(u,v)∣∣ d(u,v) . - // In the case at hand the Jacobian determinant is a constant: From (1) we obtain - // Jg(u,v)=det[xuyuxvyv]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) . - // Therefore we can write - // ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(u,v) dv du , - // where f~ denotes the pullback of f to S′: - // f~(u,v):=f(x(u,v),y(u,v)) . - // Don't forget taking the absolute value of Jg! - - float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y())); - - // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x) - // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y) - // second moment of area for x: f(x, y) = x^2; - // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form) - - // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du - // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2) - - Vec2f second_moment_of_area_xy = jacobian_determinant_abs * - (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) + - a.cwiseProduct(b + c)) / - 12.0f; - // second moment of area covariance : f(x, y) = x*y; - // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form) - //(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2)) - // == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) - - // intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv = - // 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 - // b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - - // 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du = - // 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2) - // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du - float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) * - (a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() + - b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y()); - - float area = jacobian_determinant_abs * 0.5f; - - Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f; - - return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance}; -}; - SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) { SliceConnection connection; const LayerSlice &slice = layer->lslices_ex[slice_idx]; - ExPolygon slice_poly = layer->lslices[slice_idx]; + Polygons slice_polys = to_polygons(layer->lslices[slice_idx]); + BoundingBox slice_bb = get_extents(slice_polys); const Layer *lower_layer = layer->lower_layer; - ExPolygons below_polys{}; - for (const auto &link : slice.overlaps_below) { below_polys.push_back(lower_layer->lslices[link.slice_idx]); } - ExPolygons overlap = intersection_ex({slice_poly}, below_polys); + ExPolygons below{}; + for (const auto &link : slice.overlaps_below) { below.push_back(lower_layer->lslices[link.slice_idx]); } + Polygons below_polys = to_polygons(below); - std::vector triangles = triangulate_expolygons_2f(overlap); - for (size_t idx = 0; idx < triangles.size(); idx += 3) { - auto [area, first_moment_of_area, second_moment_area, - second_moment_of_area_covariance] = compute_triangle_moments_of_area(triangles[idx], triangles[idx + 1], triangles[idx + 2]); - connection.area += area; - connection.centroid_accumulator += Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); - connection.second_moment_of_area_accumulator += second_moment_area; - connection.second_moment_of_area_covariance_accumulator += second_moment_of_area_covariance; + BoundingBox below_bb = get_extents(below_polys); + + Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb), + ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb)); + + for (const Polygon &poly : overlap) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + connection.area += sign * area; + connection.centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); + connection.second_moment_of_area_accumulator += sign * second_moment_area; + connection.second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; + } } return connection; @@ -973,6 +933,9 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) std::vector current_layer_lines; for (const LayerRegion *layer_region : l->regions()) { for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) { + + if (!extrusion->role().is_external_perimeter()) continue; + Points extrusion_pts; extrusion->collect_points(extrusion_pts); float flow_width = get_flow_width(layer_region, extrusion->role()); From ba2cd8f3a7cbb485434ee06b7bc138dcc2084279 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 18 Jan 2023 16:48:37 +0100 Subject: [PATCH 150/206] Fix of "exporting." notification not disappearing after error. --- src/slic3r/GUI/Plater.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0da9e5b83..4abc75ebd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4407,6 +4407,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) } else { show_error(q, message.first, message.second); notification_manager->set_slicing_progress_hidden(); + notification_manager->stop_delayed_notifications_of_type(NotificationType::ExportOngoing); } } else notification_manager->push_slicing_error_notification(message.first); From ce2659141afbecb3ae757fea9a32c140937682ee Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 18 Jan 2023 16:56:33 +0100 Subject: [PATCH 151/206] Fix sidebar support combobox in SLA --- src/slic3r/GUI/Plater.cpp | 8 ++++++-- src/slic3r/GUI/Tab.cpp | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 673c3892f..69a848496 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -555,10 +555,14 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : std::string treetype = get_sla_suptree_prefix(new_conf); - if (selection == _("Everywhere")) + if (selection == _("Everywhere")) { new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(false)); - else if (selection == _("Support on build plate only")) + new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); + } + else if (selection == _("Support on build plate only")) { new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(true)); + new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); + } else if (selection == _("For support enforcers only")) { new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(true)); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e519a52e3..6e43afc3c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1046,7 +1046,7 @@ static wxString support_combo_value_for_config(const DynamicPrintConfig &config, return ! config.opt_bool(support) ? _("None") : - (is_fff && !config.opt_bool("support_material_auto")) ? + ((is_fff && !config.opt_bool("support_material_auto")) || (!is_fff && config.opt_bool("support_enforcers_only"))) ? _("For support enforcers only") : (config.opt_bool(buildplate_only) ? _("Support on build plate only") : _("Everywhere")); @@ -1085,7 +1085,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (is_fff ? (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : - (opt_key == "supports_enable" || opt_key == "support_tree_type" || opt_key == get_sla_suptree_prefix(*m_config) + "support_buildplate_only")) + (opt_key == "supports_enable" || opt_key == "support_tree_type" || opt_key == get_sla_suptree_prefix(*m_config) + "support_buildplate_only" || opt_key == "support_enforcers_only")) og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object")) From ecc3211c1838ff4f725a4769bf2729d054b12b2c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 18 Jan 2023 20:19:47 +0100 Subject: [PATCH 152/206] ObjectList: Add "Text" marker only where it's needed --- src/libslic3r/Model.cpp | 5 ++ src/libslic3r/Model.hpp | 5 +- src/slic3r/GUI/GUI_ObjectList.cpp | 71 ++++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 2 +- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 40a87cb69..b823eb4fa 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -794,6 +794,11 @@ bool ModelObject::is_mm_painted() const return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); } +bool ModelObject::is_text() const +{ + return this->volumes.size() == 1 && this->volumes[0]->is_text(); +} + void ModelObject::sort_volumes(bool full_sort) { // sort volumes inside the object to order "Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 4c6eaed1c..abbd835fe 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -383,7 +383,9 @@ public: bool is_seam_painted() const; // Checks if any of object volume is painted using the multi-material painting gizmo. bool is_mm_painted() const; - + // Checks if object contains just one volume and it's a text + bool is_text() const; + ModelInstance* add_instance(); ModelInstance* add_instance(const ModelInstance &instance); ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror); @@ -797,6 +799,7 @@ public: bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } + bool is_text() const { return text_configuration.has_value(); } t_model_material_id material_id() const { return m_material_id; } void reset_extra_facets(); void apply_tolerance(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5529c34b9..54fa624bc 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -226,6 +226,21 @@ ObjectList::ObjectList(wxWindow* parent) : wxDataViewItem item; wxDataViewColumn* col; this->HitTest(this->get_mouse_position_in_control(), item, col); + + // if there is text item to editing, than edit just a name without Text marker + if (auto type = m_objects_model->GetItemType(item); + type & (itObject | itVolume) && col->GetModelColumn() == colName) { + if (ModelObject* obj = object(m_objects_model->GetObjectIdByItem(item))) { + if (type == itObject && obj->is_text()) + m_objects_model->SetName(from_u8(obj->name), item); + else if (type == itVolume && obj->volumes[m_objects_model->GetVolumeIdByItem(item)]->is_text()) { + // we cant rename text parts + event.StopPropagation(); + return; + } + } + } + this->EditItem(item, col); event.StopPropagation(); }); @@ -640,6 +655,11 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) wxGetApp().plater()->update(); } +static wxString get_item_name(const std::string& name, const bool is_text_volume) +{ + return (is_text_volume ? _L("Text") + " - " : "") + from_u8(name); +} + void ObjectList::update_name_in_model(const wxDataViewItem& item) const { const int obj_idx = m_objects_model->GetObjectIdByItem(item); @@ -652,8 +672,11 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const if (m_objects_model->GetItemType(item) & itObject) { obj->name = into_u8(m_objects_model->GetName(item)); // if object has just one volume, rename this volume too - if (obj->volumes.size() == 1 && !obj->volumes[0]->text_configuration.has_value()) + if (obj->is_text()) { obj->volumes[0]->name = obj->name; + //update object name with text marker in ObjectList + m_objects_model->SetName(get_item_name(obj->name, true), item); + } return; } @@ -662,28 +685,32 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const // Renaming of the text volume is suppressed // So, revert the name in object list - if (obj->volumes[volume_id]->text_configuration.has_value()) { - m_objects_model->SetName(from_u8(obj->volumes[volume_id]->name), item); + if (obj->volumes[volume_id]->is_text()) { + m_objects_model->SetName(get_item_name(obj->volumes[volume_id]->name, true), item); return; } - obj->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); + obj->volumes[volume_id]->name = into_u8(m_objects_model->GetName(item)); } void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const { if (obj_idx < 0) return; wxDataViewItem item = GetSelection(); - if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject))) + auto type = m_objects_model->GetItemType(item); + if (!item || !(type & (itVolume | itObject))) return; - wxString new_name = from_u8(object(obj_idx)->volumes[vol_idx]->name); + ModelObject* obj = object(obj_idx); + const bool is_text_volume = type == itVolume ? obj->volumes[vol_idx]->is_text() : obj->is_text(); + const wxString new_name = get_item_name(object(obj_idx)->volumes[vol_idx]->name, is_text_volume); + if (new_name.IsEmpty() || m_objects_model->GetName(item) == new_name) return; m_objects_model->SetName(new_name, item); // if object has just one volume, rename object too - if (ModelObject* obj = object(obj_idx); obj->volumes.size() == 1) + if (obj->volumes.size() == 1) obj->name = obj->volumes.front()->name; } @@ -2089,13 +2116,13 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con object->delete_volume(idx); if (object->volumes.size() == 1) { + wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); const auto last_volume = object->volumes[0]; if (!last_volume->config.empty()) { object->config.apply(last_volume->config); last_volume->config.reset(); // update extruder color in ObjectList - wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); if (obj_item) { wxString extruder = object->config.has("extruder") ? wxString::Format("%d", object->config.extruder()) : _L("default"); m_objects_model->SetExtruder(extruder, obj_item); @@ -2103,6 +2130,9 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con // add settings to the object, if it has them add_settings_item(obj_item, &object->config.get()); } + + if (last_volume->is_text()) + m_objects_model->SetName(get_item_name(/*last_volume*/object->name, true), obj_item); } } else if (type == itInstance) { @@ -3005,6 +3035,12 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st const ModelObject* object = (*m_objects)[obj_idx]; // add volumes to the object if (can_add_volumes_to_object(object)) { + if (object->volumes.size() > 1) { + wxString obj_item_name = from_u8(object->name); + if (m_objects_model->GetName(object_item) != obj_item_name) + m_objects_model->SetName(obj_item_name, object_item); + } + int volume_idx{ -1 }; for (const ModelVolume* volume : object->volumes) { ++volume_idx; @@ -3012,10 +3048,10 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st (printer_technology() == ptSLA && volume->type() == ModelVolumeType::PARAMETER_MODIFIER)) continue; const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, - from_u8(volume->name), + get_item_name(volume->name, volume->is_text()), volume_idx, volume->type(), - volume->text_configuration.has_value(), + volume->is_text(), get_warning_icon_name(volume->mesh().stats()), extruder2str(volume->config.has("extruder") ? volume->config.extruder() : 0)); add_settings_item(vol_item, &volume->config.get()); @@ -3033,7 +3069,7 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; - const wxString& item_name = from_u8(model_object->name); + const wxString& item_name = get_item_name(model_object->name, model_object->is_text()); const auto item = m_objects_model->AddObject(item_name, extruder2str(model_object->config.has("extruder") ? model_object->config.extruder() : 0), get_warning_icon_name(model_object->mesh().stats()), @@ -4560,11 +4596,18 @@ void ObjectList::split_instances() void ObjectList::rename_item() { const wxDataViewItem item = GetSelection(); - if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject))) + auto type = m_objects_model->GetItemType(item); + if (!item || !(type & (itVolume | itObject))) return ; - const wxString new_name = wxGetTextFromUser(_(L("Enter new name"))+":", _(L("Renaming")), - m_objects_model->GetName(item), this); + wxString input_name = m_objects_model->GetName(item); + if (ModelObject* obj = object(m_objects_model->GetObjectIdByItem(item))) { + // if there is text item to editing, than edit just a name without Text marker + if (type == itObject && obj->is_text()) + input_name = from_u8(obj->name); + } + + const wxString new_name = wxGetTextFromUser(_L("Enter new name")+":", _L("Renaming"), input_name, this); if (new_name.IsEmpty()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 4d7685eeb..62d3c9a8c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -3664,7 +3664,7 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager& st text_fixed = text; // copy std::replace(text_fixed.begin(), text_fixed.end(), '\n', ' '); } - return _u8L("Text") + " - " + ((contain_enter) ? text_fixed : text); + return ((contain_enter) ? text_fixed : text); }; auto create_configuration = [&]() -> TextConfiguration { From eee4453993ba38f3f11c8f89c56cefa50eaaf74b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 Jan 2023 09:26:47 +0100 Subject: [PATCH 153/206] Revert of 971f2a08e2758ea1d91e75af9b1443d14b41f895 - Fix mipmap of compressed textures on AMD Radeon graphics cards by forcing the use of squared power of two textures Changed nanosvg library from https://github.com/memononen/nanosvg to https://github.com/fltk/nanosvg which contains the definition of the new function nsvgRasterizeXY() --- deps/NanoSVG/NanoSVG.cmake | 9 +++- src/slic3r/GUI/GLTexture.cpp | 72 ++++++++++++++++++++------------ src/slic3r/GUI/OpenGLManager.cpp | 16 ++++--- src/slic3r/GUI/OpenGLManager.hpp | 4 +- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/deps/NanoSVG/NanoSVG.cmake b/deps/NanoSVG/NanoSVG.cmake index 9623d3226..1b0fb9960 100644 --- a/deps/NanoSVG/NanoSVG.cmake +++ b/deps/NanoSVG/NanoSVG.cmake @@ -1,4 +1,9 @@ +# In PrusaSlicer 2.6.0 we switched from https://github.com/memononen/nanosvg to its fork https://github.com/fltk/nanosvg +# because this last implements the new function nsvgRasterizeXY() which we now use in GLTexture::load_from_svg() +# for rasterizing svg files from their original size to a squared power of two texture on Windows systems using +# AMD Radeon graphics cards + prusaslicer_add_cmake_project(NanoSVG - URL https://github.com/memononen/nanosvg/archive/4c8f0139b62c6e7faa3b67ce1fbe6e63590ed148.zip - URL_HASH SHA256=584e084af1a75bf633f79753ce2f6f6ec8686002ca27f35f1037c25675fecfb6 + URL https://github.com/fltk/nanosvg/archive/abcd277ea45e9098bed752cf9c6875b533c0892f.zip + URL_HASH SHA256=e859938fbaee4b351bd8a8b3d3c7a75b40c36885ce00b73faa1ce0b98aa0ad34 ) \ No newline at end of file diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index ef9a8ef20..93e96a642 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -375,10 +375,29 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, glsafe(::glDisable(GL_BLEND)); } +static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h) +{ + auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; }; + auto upper_power_of_two = [](int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }; + + int new_w = std::max(w, h); + if (!is_power_of_two(new_w)) + new_w = upper_power_of_two(new_w); + + while (new_w > max_size_px) { + new_w /= 2; + } + + const int new_h = new_w; + const bool ret = (new_w != w || new_h != h); + w = new_w; + h = new_h; + return ret; +} + bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) { const bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported(); - const bool use_compressor = compression_enabled && OpenGLManager::use_manually_generated_mipmaps(); // Load a PNG with an alpha channel. wxImage image; @@ -392,6 +411,11 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo bool requires_rescale = false; + if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) { + if (to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), OpenGLManager::get_gl_info().get_max_tex_size(), m_width, m_height)) + requires_rescale = true; + } + if (compression_enabled && compression_type == MultiThreaded) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 int width_rem = m_width % 4; @@ -448,7 +472,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo } if (compression_enabled) { - if (compression_type == SingleThreaded || !use_compressor) + if (compression_type == SingleThreaded) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else { // initializes the texture on GPU @@ -460,7 +484,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) { + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards int lod_w = m_width; int lod_h = m_height; @@ -507,10 +531,6 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } } - else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { - glsafe(::glGenerateMipmap(GL_TEXTURE_2D)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); - } else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -522,7 +542,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo m_source = filename; - if (use_compressor && compression_type == MultiThreaded) + if (compression_type == MultiThreaded) // start asynchronous compression m_compressor.start_compressing(); @@ -532,7 +552,6 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { const bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported(); - const bool use_compressor = compression_enabled && OpenGLManager::use_manually_generated_mipmaps(); NSVGimage* image = BitmapCache::nsvgParseFromFileWithReplace(filename.c_str(), "px", 96.0f, {}); if (image == nullptr) { @@ -540,11 +559,17 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo return false; } - float scale = (float)max_size_px / std::max(image->width, image->height); + const float scale = (float)max_size_px / std::max(image->width, image->height); m_width = (int)(scale * image->width); m_height = (int)(scale * image->height); + if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) + to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), max_size_px, m_width, m_height); + + float scale_w = (float)m_width / image->width; + float scale_h = (float)m_height / image->height; + if (compression_enabled) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 int width_rem = m_width % 4; @@ -574,7 +599,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo // creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps std::vector data(n_pixels * 4, 0); - nsvgRasterize(rast, image, 0, 0, scale, data.data(), m_width, m_height, m_width * 4); + nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), m_width, m_height, m_width * 4); // sends data to gpu glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); @@ -588,19 +613,15 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo } if (compression_enabled) { - if (use_compressor) { - // initializes the texture on GPU - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - // and send the uncompressed data to the compressor - m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); - } - else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); } else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) { + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards int lod_w = m_width; int lod_h = m_height; @@ -610,11 +631,12 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo lod_w = std::max(lod_w / 2, 1); lod_h = std::max(lod_h / 2, 1); - scale /= 2.0f; + scale_w /= 2.0f; + scale_h /= 2.0f; data.resize(lod_w * lod_h * 4); - nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); + nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), lod_w, lod_h, lod_w * 4); if (compression_enabled) { // initializes the texture on GPU glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); @@ -630,10 +652,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } } - else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { - glsafe(::glGenerateMipmap(GL_TEXTURE_2D)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); - } else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -645,7 +663,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo m_source = filename; - if (use_compressor) + if (compression_enabled) // start asynchronous compression m_compressor.start_compressing(); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 8959553e6..ecf0c5790 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -200,7 +200,6 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; out << b_start << "Textures compression: " << b_end << (are_compressed_textures_supported() ? "Enabled" : "Disabled") << line_end; - out << b_start << "Textures mipmap generation: " << b_end << (use_manually_generated_mipmaps() ? "Manual" : "Automatic") << line_end; { #if ENABLE_GL_CORE_PROFILE @@ -257,7 +256,7 @@ std::vector OpenGLManager::GLInfo::get_extensions_list() const OpenGLManager::GLInfo OpenGLManager::s_gl_info; bool OpenGLManager::s_compressed_textures_supported = false; -bool OpenGLManager::s_use_manually_generated_mipmaps = true; +bool OpenGLManager::s_force_power_of_two_textures = false; OpenGLManager::EMultisampleState OpenGLManager::s_multisample = OpenGLManager::EMultisampleState::Unknown; OpenGLManager::EFramebufferType OpenGLManager::s_framebuffers_type = OpenGLManager::EFramebufferType::Unknown; @@ -415,17 +414,16 @@ bool OpenGLManager::init_gl() // texture of the bed (see: https://github.com/prusa3d/PrusaSlicer/issues/8417). // It seems that this issue only triggers when mipmaps are generated manually // (combined with a texture compression) with texture size not being power of two. - // When mipmaps are generated through OpenGL function glGenerateMipmap() the driver works fine. + // When mipmaps are generated through OpenGL function glGenerateMipmap() the driver works fine, + // but the mipmap generation is quite slow on some machines. // There is no an easy way to detect the driver version without using Win32 API because the strings returned by OpenGL // have no standardized format, only some of them contain the driver version. - // Until we do not know that driver will be fixed (if ever) we force the use of glGenerateMipmap() on all cards + // Until we do not know that driver will be fixed (if ever) we force the use of power of two textures on all cards // containing the string 'Radeon' in the string returned by glGetString(GL_RENDERER) const auto& gl_info = OpenGLManager::get_gl_info(); - if (boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.") && boost::contains(gl_info.get_renderer(), "Radeon")) { - s_use_manually_generated_mipmaps = false; - BOOST_LOG_TRIVIAL(debug) << "Mipmapping through OpenGL was enabled."; - } -#endif + if (boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.") && boost::contains(gl_info.get_renderer(), "Radeon")) + s_force_power_of_two_textures = true; +#endif // _WIN32 } return true; diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index 6b947deb5..5e2bdc316 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -110,7 +110,7 @@ private: static OSInfo s_os_info; #endif //__APPLE__ static bool s_compressed_textures_supported; - static bool s_use_manually_generated_mipmaps; + static bool s_force_power_of_two_textures; static EMultisampleState s_multisample; static EFramebufferType s_framebuffers_type; @@ -139,7 +139,7 @@ public: static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } static wxGLCanvas* create_wxglcanvas(wxWindow& parent); static const GLInfo& get_gl_info() { return s_gl_info; } - static bool use_manually_generated_mipmaps() { return s_use_manually_generated_mipmaps; } + static bool force_power_of_two_textures() { return s_force_power_of_two_textures; } private: #if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES From d681b99c104c1462e0d9b90935511d4ee24c0978 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 19 Jan 2023 10:10:41 +0100 Subject: [PATCH 154/206] Disable "Search"/"Arrange options" ImGui dialog, when some of gizmos is active --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 81194038e..41bd3a5c5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4745,7 +4745,7 @@ bool GLCanvas3D::_init_main_toolbar() }; item.left.action_callback = GLToolbarItem::Default_Action_Callback; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; + item.enabling_callback = [this]()->bool { return m_gizmos.get_current_type() == GLGizmosManager::Undefined; }; if (!m_main_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6e63c5634..1b2198111 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5015,8 +5015,7 @@ bool Plater::priv::can_split_to_volumes() const bool Plater::priv::can_arrange() const { if (model.objects.empty() || !m_worker.is_idle()) return false; - if (q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss) return false; - return true; + return q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Undefined; } bool Plater::priv::can_layers_editing() const From 70879d2d8cb6f4f137bd417788cee856e8822cc4 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 19 Jan 2023 13:01:41 +0100 Subject: [PATCH 155/206] Fixes of AppUdpater fix of bug (1) - order of dialogs fix of bug (2) - show new version dialog when triggered by user fix of bug (3) - refresh value in preferences combobox when opening preferences fix of bug (4) - Use fwrite instead of stream to speedup saving. Do not show checkbox when triggered by user. --- src/slic3r/GUI/GUI_App.cpp | 14 ++++++++------ src/slic3r/GUI/Preferences.cpp | 7 +++++++ src/slic3r/GUI/UpdateDialogs.cpp | 10 +++++++--- src/slic3r/GUI/UpdateDialogs.hpp | 4 ++-- src/slic3r/Utils/AppUpdater.cpp | 17 +++++++++-------- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d5b267927..12f3a82f0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -811,13 +811,14 @@ void GUI_App::post_init() // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. bool cw_showed = this->config_wizard_startup(); this->preset_updater->sync(preset_bundle); - this->app_version_check(false); if (! cw_showed) { // The CallAfter is needed as well, without it, GL extensions did not show. // Also, we only want to show this when the wizard does not, so the new user // sees something else than "we want something" on the first start. - show_send_system_info_dialog_if_needed(); - } + show_send_system_info_dialog_if_needed(); + } + // app version check is asynchronous and triggers blocking dialog window, better call it last + this->app_version_check(false); }); } @@ -1245,7 +1246,7 @@ bool GUI_App::on_init_inner() preset_updater = new PresetUpdater(); Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { + if (this->plater_ != nullptr && (m_app_updater->get_triggered_by_user() || app_config->get("notify_release") == "all")) { std::string evt_string = into_u8(evt.GetString()); if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); @@ -3305,7 +3306,8 @@ void GUI_App::on_version_read(wxCommandEvent& evt) app_config->set("version_online", into_u8(evt.GetString())); app_config->save(); std::string opt = app_config->get("notify_release"); - if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { + if (this->plater_ == nullptr || (!m_app_updater->get_triggered_by_user() && opt != "all" && opt != "release")) { + BOOST_LOG_TRIVIAL(info) << "Version online: " << evt.GetString() << ". User does not wish to be notified."; return; } if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { @@ -3355,7 +3357,7 @@ void GUI_App::app_updater(bool from_user) assert(!app_data.target_path.empty()); // dialog with new version info - AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); + AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version, from_user); auto dialog_result = dialog.ShowModal(); // checkbox "do not show again" if (dialog.disable_version_check()) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index cc57bcffd..fc9ea1867 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -89,6 +89,13 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; + // set Field for notify_release to its value + if (m_optgroup_gui && m_optgroup_gui->get_field("notify_release") != nullptr) { + boost::any val = s_keys_map_NotifyReleaseMode.at(wxGetApp().app_config->get("notify_release")); + m_optgroup_gui->get_field("notify_release")->set_value(val, false); + } + + if (wxGetApp().is_editor()) { auto app_config = get_app_config(); diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index e8edd5798..258a9c784 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -93,7 +93,7 @@ bool MsgUpdateSlic3r::disable_version_check() const wxSize AppUpdateAvailableDialog::AUAD_size; // AppUpdater -AppUpdateAvailableDialog::AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online) +AppUpdateAvailableDialog::AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online, bool from_user) : MsgDialog(nullptr, _(L("App Update available")), wxString::Format(_(L("New version of %s is available.\nDo you wish to download it?")), SLIC3R_APP_NAME)) { auto* versions = new wxFlexGridSizer(1, 0, VERT_SPACING); @@ -104,8 +104,10 @@ AppUpdateAvailableDialog::AppUpdateAvailableDialog(const Semver& ver_current, co content_sizer->Add(versions); content_sizer->AddSpacer(VERT_SPACING); - cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more"))); - content_sizer->Add(cbox); + if(!from_user) { + cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more"))); + content_sizer->Add(cbox); + } content_sizer->AddSpacer(VERT_SPACING); AUAD_size = content_sizer->GetSize(); @@ -125,6 +127,8 @@ AppUpdateAvailableDialog::~AppUpdateAvailableDialog() {} bool AppUpdateAvailableDialog::disable_version_check() const { + if (!cbox) + return false; return cbox->GetValue(); } diff --git a/src/slic3r/GUI/UpdateDialogs.hpp b/src/slic3r/GUI/UpdateDialogs.hpp index 2eb4ff8d4..0f5e1d6fb 100644 --- a/src/slic3r/GUI/UpdateDialogs.hpp +++ b/src/slic3r/GUI/UpdateDialogs.hpp @@ -42,7 +42,7 @@ private: class AppUpdateAvailableDialog : public MsgDialog { public: - AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online); + AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online, bool from_user); AppUpdateAvailableDialog(AppUpdateAvailableDialog&&) = delete; AppUpdateAvailableDialog(const AppUpdateAvailableDialog&) = delete; AppUpdateAvailableDialog& operator=(AppUpdateAvailableDialog&&) = delete; @@ -53,7 +53,7 @@ public: bool disable_version_check() const; static wxSize AUAD_size; private: - wxCheckBox* cbox; + wxCheckBox* cbox {nullptr}; }; class AppUpdateDownloadDialog : public MsgDialog diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index bd71e86ec..aa3339f1e 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -221,7 +221,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d } // progress event size_t gui_progress = progress.dltotal > 0 ? 100 * progress.dlnow / progress.dltotal : 0; - BOOST_LOG_TRIVIAL(error) << "App download " << gui_progress << "% " << progress.dlnow << " of " << progress.dltotal; + BOOST_LOG_TRIVIAL(debug) << "App download " << gui_progress << "% " << progress.dlnow << " of " << progress.dltotal; if (last_gui_progress < gui_progress && (last_gui_progress != 0 || gui_progress != 100)) { last_gui_progress = gui_progress; wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS); @@ -243,14 +243,15 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d tmp_path += format(".%1%%2%", get_current_pid(), ".download"); try { - boost::nowide::fstream file(tmp_path.string(), std::ios::out | std::ios::binary | std::ios::trunc); - file.write(body.c_str(), body.size()); - file.close(); + FILE* file; + file = fopen(tmp_path.string().c_str(), "wb"); + fwrite(body.c_str(), 1, body.size(), file); + fclose(file); boost::filesystem::rename(tmp_path, dest_path); } - catch (const std::exception&) + catch (const std::exception& e) { - error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path); + error_message = GUI::format("Failed to write and move %1% to %2%:/n%3%", tmp_path, dest_path, e.what()); return false; } return true; @@ -363,10 +364,10 @@ void AppUpdater::priv::parse_version_string(const std::string& body) if (data.first == "url") { new_data.url = data.second.data(); new_data.target_path = m_default_dest_folder / AppUpdater::get_filename_from_url(new_data.url); - BOOST_LOG_TRIVIAL(error) << format("parsing version string: url: %1%", new_data.url); + BOOST_LOG_TRIVIAL(info) << format("parsing version string: url: %1%", new_data.url); } else if (data.first == "size"){ new_data.size = std::stoi(data.second.data()); - BOOST_LOG_TRIVIAL(error) << format("parsing version string: expected size: %1%", new_data.size); + BOOST_LOG_TRIVIAL(info) << format("parsing version string: expected size: %1%", new_data.size); } } } From be61ab37f4d7073d09c66fa988227495767fba96 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 19 Jan 2023 15:07:06 +0100 Subject: [PATCH 156/206] Document extensively the generated SupportPoint structure, fix wrong estimation of weight torque for connections --- src/libslic3r/SupportSpotsGenerator.cpp | 19 +++++++++++++------ src/libslic3r/SupportSpotsGenerator.hpp | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index e881f7bba..7c1c4f3c6 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -463,7 +463,7 @@ public: #ifdef DETAILED_DEBUG_LOGS BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); BOOST_LOG_TRIVIAL(debug) << "SSG: bed_yield_torque: " << bed_yield_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm_len; BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_torque: " << bed_weight_torque; BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm; BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque; @@ -492,7 +492,7 @@ public: params.material_yield_strength; float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); - float conn_weight_torque = conn_weight_arm * weight * (conn_centroid.z() / layer_z); + float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z); float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); float conn_movement_torque = movement_force * conn_movement_arm; @@ -732,17 +732,24 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance // and the support presence grid and add the point to the issues. auto reckon_new_support_point = [&part, &weakest_conn, &supp_points, &supports_presence_grid, ¶ms, &layer_idx](const Vec3f &support_point, float force, const Vec2f &dir) { + // if position is taken and point is for global stability (force > 0) or we are too close to the bed, do not add + // This allows local support points (e.g. bridging) to be generated densely if ((supports_presence_grid.position_taken(support_point) && force > 0) || layer_idx <= 1) { return; } + float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); - part.add_support_point(support_point, area); + // add the stability effect of the point only if the spot is not taken, so that the densely created local support points do not add + // unrealistic amount of stability to the object (due to overlaping of local support points) + if (!(supports_presence_grid.position_taken(support_point))) { + part.add_support_point(support_point, area); + } float radius = params.support_points_interface_radius; supp_points.emplace_back(support_point, force, radius, dir); - if (force > 0) { - supports_presence_grid.take_position(support_point); - } + supports_presence_grid.take_position(support_point); + + // The support point also increases the stability of the weakest connection of the object, which should be reflected if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist weakest_conn.area += area; weakest_conn.centroid_accumulator += support_point * area; diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 1c07c1381..8e8983ac1 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -63,11 +63,33 @@ struct Params { } }; +// The support points are generated for two reasons: +// 1. Local extrusion support for extrusions that are printed in the air and would not +// withstand on their own (too long bridges, sharp turns in large overhang, concave bridge holes, etc.) +// These points have negative force (-EPSILON) and Vec2f::Zero() direction +// The algorithm still expects that these points will be supported and accounts for them in the global stability check +// 2. Global stability support points are generated at each spot, where the algorithm detects that extruding the current line +// may cause separation of the object part from the bed and/or its support spots or crack in the weak connection of the object parts +// The generated point's direction is the estimated falling direction of the object part, and the force is equal to te difference +// between forces that destabilize the object (extruder conflicts with curled filament, weight if instable center of mass, bed movements etc) +// and forces that stabilize the object (bed adhesion, other support spots adhesion, weight if stable center of mass) +// Note that the force is only the difference - the amount needed to stabilize the object again. struct SupportPoint { SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction); + bool is_local_extrusion_support() const { return force < 0; } + bool is_global_object_support() const { return !is_local_extrusion_support(); } + + //position is in unscaled coords. The z coordinate is aligned with the layers bottom_z coordiantes Vec3f position; + // force that destabilizes the object to the point of falling/breaking. It is in g*mm/s^2 units + // values gathered from large XL print: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103 + // For reference 18713800 is weight of 1.8 Kg object, 329103 is weight of 0.03 Kg + // The final printed object weight was approx 0.5 Kg float force; + // Expected spot size. The support point strength is calculated from the area defined by this value. + // Currently equal to the support_points_interface_radius parameter above float spot_radius; + // direction of the fall of the object (z part is neglected) Vec2f direction; }; From 443f5c18213740d1625f9615c2ceae50a1b0e7d0 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 19 Jan 2023 16:21:35 +0100 Subject: [PATCH 157/206] Version file url reverted to original name --- src/libslic3r/AppConfig.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 0d9888d40..9c2fe7f15 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -34,8 +34,9 @@ static const std::string MODEL_PREFIX = "model:"; // to show this notification. On the other hand, we would like PrusaSlicer 2.3.2 to show an update notification of the upcoming PrusaSlicer 2.4.0. // Thus we will let PrusaSlicer 2.3.2 and couple of follow-up versions to download the version number from an alternate file until the PrusaSlicer 2.3.0/2.3.1 // are phased out, then we will revert to the original name. -//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; -static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; +// For 2.6.0-alpha1 we have switched back to the original. The file should contain data for AppUpdater.cpp +static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; +//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; // Url to index archive zip that contains latest indicies static const std::string INDEX_ARCHIVE_URL= "https://files.prusa3d.com/wp-content/uploads/repository/vendor_indices.zip"; // Url to folder with vendor profile files. Used when downloading new profiles that are not in resources folder. From 88ba9ab1c8f3914d51a0f96df222b1a8f97ef853 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 19 Jan 2023 17:12:14 +0100 Subject: [PATCH 158/206] Tree supports: Added the most important tree support parameters to parameter layer. --- src/libslic3r/Preset.cpp | 4 +- src/libslic3r/PrintConfig.cpp | 65 ++++++++++++++++++++++++++++++ src/libslic3r/PrintConfig.hpp | 8 ++++ src/libslic3r/PrintObject.cpp | 6 +++ src/libslic3r/TreeModelVolumes.cpp | 9 +++++ src/libslic3r/TreeModelVolumes.hpp | 5 +-- src/slic3r/GUI/Tab.cpp | 8 ++++ 7 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 53eca40c6..d2f5256f9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -450,7 +450,9 @@ static std::vector s_Preset_print_options { "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_bottom_contact_distance", - "support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", + "support_material_buildplate_only", + "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_tip_diameter", + "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c19722a14..c6f0eeab5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2876,6 +2876,71 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("support_tree_angle", coFloat); + def->label = L("Tree Support Maximum Branch Angle"); + def->category = L("Support material"); + def->tooltip = L("The maximum angle of the branches, when the branches have to avoid the model. " + "Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."); + def->sidetext = L("°"); + def->min = 0; + def->max = 85; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(40)); + + def = this->add("support_tree_angle_slow", coFloat); + def->label = L("Tree Support Preferred Branch Angle"); + def->category = L("Support material"); + def->tooltip = L("The preferred angle of the branches, when they do not have to avoid the model. " + "Use a lower angle to make them more vertical and more stable. Use a higher angle for branches to merge faster."); + def->sidetext = L("°"); + def->min = 10; + def->max = 85; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(25)); + + def = this->add("support_tree_tip_diameter", coFloat); + def->label = L("Tree Support Tip Diameter"); + def->category = L("Support material"); + def->tooltip = L("The diameter of the top of the tip of the branches of tree support."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.8)); + + def = this->add("support_tree_branch_diameter", coFloat); + def->label = L("Tree Support Branch Diameter"); + def->category = L("Support material"); + def->tooltip = L("The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. " + "Branches towards the base will be thicker than this."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2)); + + def = this->add("support_tree_branch_diameter_angle", coFloat); + def->label = L("Tree Support Branch Diameter Angle"); + def->category = L("Support material"); + def->tooltip = L("The angle of the branches' diameter as they gradually become thicker towards the bottom. " + "An angle of 0 will cause the branches to have uniform thickness over their length. " + "A bit of an angle can increase stability of the tree support."); + def->sidetext = L("°"); + def->min = 0; + def->max = 15; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5)); + + def = this->add("support_tree_top_rate", coPercent); + def->label = L("Tree Support Branch Density"); + def->category = L("Support material"); + def->tooltip = L("Adjusts the density of the support structure used to generate the tips of the branches. " + "A higher value results in better overhangs, but the supports are harder to remove. " + "Use Support Roof for very high values or ensure support density is similarly high at the top."); + def->sidetext = L("%"); + def->min = 5; + def->max_literal = 35; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(30)); + def = this->add("temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Nozzle temperature for layers after the first one. Set this to zero to disable " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 85c2ca794..eed5df626 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -547,6 +547,14 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, support_material_threshold)) ((ConfigOptionBool, support_material_with_sheath)) ((ConfigOptionFloatOrPercent, support_material_xy_spacing)) + // Tree supports + ((ConfigOptionFloat, support_tree_angle)) + ((ConfigOptionFloat, support_tree_angle_slow)) + ((ConfigOptionFloat, support_tree_branch_diameter)) + ((ConfigOptionFloat, support_tree_branch_diameter_angle)) + ((ConfigOptionPercent, support_tree_top_rate)) + ((ConfigOptionFloat, support_tree_tip_diameter)) + // The rest ((ConfigOptionBool, thick_bridges)) ((ConfigOptionFloat, xy_size_compensation)) ((ConfigOptionBool, wipe_into_objects)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index abbf0fb93..dc75fb8a3 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -657,6 +657,12 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_synchronize_layers" || opt_key == "support_material_threshold" || opt_key == "support_material_with_sheath" + || opt_key == "support_tree_angle" + || opt_key == "support_tree_angle_slow" + || opt_key == "support_tree_branch_diameter" + || opt_key == "support_tree_branch_diameter_angle" + || opt_key == "support_tree_top_rate" + || opt_key == "support_tree_tip_diameter" || opt_key == "raft_expansion" || opt_key == "raft_first_layer_density" || opt_key == "raft_first_layer_expansion" diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index e8ab377d7..bcd071767 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -54,6 +54,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr this->layer_height = scaled(config.layer_height.value); this->resolution = scaled(print_config.gcode_resolution.value); + // Arache feature this->min_feature_size = scaled(config.min_feature_size.value); // +1 makes the threshold inclusive this->support_angle = 0.5 * M_PI - std::clamp((config.support_material_threshold + 1) * M_PI / 180., 0., 0.5 * M_PI); @@ -88,6 +89,14 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr // this->minimum_support_area = // this->minimum_bottom_area = // this->support_offset = +// this->support_tree_branch_distance = 2.5 * line_width ?? + this->support_tree_angle = std::clamp(config.support_tree_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_angle_slow = std::clamp(config.support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); + this->support_tree_branch_diameter = scaled(config.support_tree_branch_diameter.value); + this->support_tree_branch_diameter_angle = std::clamp(config.support_tree_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_top_rate = config.support_tree_top_rate.value; // percent +// this->support_tree_tip_diameter = this->support_line_width; + this->support_tree_tip_diameter = std::clamp(scaled(config.support_tree_tip_diameter.value), 0, this->support_tree_branch_diameter); } //FIXME Machine border is currently ignored. diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 0ced2f422..99f5797d2 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -11,7 +11,6 @@ #include #include -#include #include @@ -43,7 +42,7 @@ struct TreeSupportMeshGroupSettings { // the print will be less accurate, but the g-code will be smaller. Maximum Deviation is a limit for Maximum Resolution, // so if the two conflict the Maximum Deviation will always be held true. coord_t resolution { scaled(0.025) }; - // Minimum Feature Size (aka minimum line width) + // Minimum Feature Size (aka minimum line width) - Arachne specific // Minimum thickness of thin features. Model features that are thinner than this value will not be printed, while features thicker // than the Minimum Feature Size will be widened to the Minimum Wall Line Width. coord_t min_feature_size { scaled(0.1) }; @@ -183,7 +182,7 @@ struct TreeSupportMeshGroupSettings { // 5%-35% double support_tree_top_rate { 15. }; // Tree Support Tip Diameter - // The diameter of the top of the tip of the branches of tree support." + // The diameter of the top of the tip of the branches of tree support. // minimum: min_wall_line_width, minimum warning: min_wall_line_width+0.05, maximum_value: support_tree_branch_diameter, value: support_line_width coord_t support_tree_tip_diameter { scaled(0.4) }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6e43afc3c..7d91f689d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1526,6 +1526,14 @@ void TabPrint::build() optgroup->append_single_option_line("dont_support_bridges", category_path + "dont-support-bridges"); optgroup->append_single_option_line("support_material_synchronize_layers", category_path + "synchronize-with-object-layers"); + optgroup = page->new_optgroup(L("Tree supports")); + optgroup->append_single_option_line("support_tree_angle", category_path + "tree_angle"); + optgroup->append_single_option_line("support_tree_angle_slow", category_path + "tree_angle_slow"); + optgroup->append_single_option_line("support_tree_branch_diameter", category_path + "tree_branch_diameter"); + optgroup->append_single_option_line("support_tree_branch_diameter_angle", category_path + "tree_branch_diameter_angle"); + optgroup->append_single_option_line("support_tree_tip_diameter", category_path + "tree_tip_diameter"); + optgroup->append_single_option_line("support_tree_top_rate", category_path + "tree_top_rate"); + page = add_options_page(L("Speed"), "time"); optgroup = page->new_optgroup(L("Speed for print moves")); optgroup->append_single_option_line("perimeter_speed"); From 77c521eabb2db39749ecd8d5ee9e1eef62ba17c9 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 19 Jan 2023 17:20:18 +0100 Subject: [PATCH 159/206] Tree supports: Disabled some more error reporting. --- src/libslic3r/TreeSupport.cpp | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 5a9e2f646..7f5f497e5 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -40,6 +40,10 @@ #include #include +#if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32) + #define TREE_SUPPORT_SHOW_ERRORS_WIN32 +#endif + namespace Slic3r { @@ -104,16 +108,16 @@ static inline void validate_range(const LineInformations &lines) static inline void check_self_intersections(const Polygons &polygons, const std::string_view message) { -#ifdef _WIN32 +#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 if (!intersecting_edges(polygons).empty()) ::MessageBoxA(nullptr, (std::string("TreeSupport infill self intersections: ") + std::string(message)).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // _WIN32 +#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 } static inline void check_self_intersections(const ExPolygon &expoly, const std::string_view message) { -#ifdef _WIN32 +#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 check_self_intersections(to_polygons(expoly), message); -#endif // _WIN32 +#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 } static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); @@ -197,7 +201,7 @@ static bool inline g_showed_performance_warning = false; void tree_supports_show_error(std::string_view message, bool critical) { // todo Remove! ONLY FOR PUBLIC BETA!! -#if defined(_WIN32) && defined(TREE_SUPPORT_SHOW_ERRORS) +#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 static bool showed_critical = false; static bool showed_performance = false; auto bugtype = std::string(critical ? " This is a critical bug. It may cause missing or malformed branches.\n" : "This bug should only decrease performance.\n"); @@ -206,7 +210,7 @@ void tree_supports_show_error(std::string_view message, bool critical) if (show) MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // WIN32 +#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 } [[nodiscard]] static const std::vector generate_overhangs(const PrintObject &print_object) @@ -644,14 +648,6 @@ static std::optional> polyline_sample_next_point_at_dis append(lines, to_polylines(polygons)); return lines; #else -#ifdef _WIN32 - // Max dimensions for MK3 -// if (! BoundingBox(Point::new_scale(-170., -170.), Point::new_scale(170., 170.)).contains(get_extents(polygon))) - // Max dimensions for XL - if (! BoundingBox(Point::new_scale(-250., -250.), Point::new_scale(250., 250.)).contains(get_extents(polygon))) - ::MessageBoxA(nullptr, "TreeSupport infill kravsky", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // _WIN32 - const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow; std::unique_ptr filler = std::unique_ptr(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern)); FillParams fill_params; @@ -670,20 +666,20 @@ static std::optional> polyline_sample_next_point_at_dis for (ExPolygon &expoly : union_ex(polygon)) { // The surface type does not matter. assert(area(expoly) > 0.); -#ifdef _WIN32 +#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 if (area(expoly) <= 0.) ::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // _WIN32 +#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 assert(intersecting_edges(to_polygons(expoly)).empty()); check_self_intersections(expoly, "generate_support_infill_lines"); Surface surface(stInternal, std::move(expoly)); try { Polylines pl = filler->fill_surface(&surface, fill_params); assert(pl.empty() || get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl))); -#ifdef _WIN32 +#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 if (! pl.empty() && ! get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl))) ::MessageBoxA(nullptr, "TreeSupport infill failure", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // _WIN32 +#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 append(out, std::move(pl)); } catch (InfillFailedException &) { } @@ -3520,7 +3516,7 @@ static void draw_branches( pts[i].y() += nudge_v.y(); } } - printf("iteration: %d, moved: %d\n", int(iter), int(num_moved)); +// printf("iteration: %d, moved: %d\n", int(iter), int(num_moved)); if (num_moved == 0) break; } From 3f69799047a3244717bfe6fd2e5cebb1f09d7260 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 20 Jan 2023 08:40:54 +0100 Subject: [PATCH 160/206] App updater download directory path cehecking and selection --- src/slic3r/GUI/GUI_App.cpp | 4 ++++ src/slic3r/GUI/UpdateDialogs.cpp | 39 ++++++++++++++++++++++++-------- src/slic3r/GUI/UpdateDialogs.hpp | 1 + src/slic3r/Utils/AppUpdater.cpp | 1 + 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 12f3a82f0..56b2d2eb1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3374,6 +3374,10 @@ void GUI_App::app_updater(bool from_user) if (dialog_result != wxID_OK) { return; } + if (dwnld_dlg.get_download_path().parent_path().empty() || !boost::filesystem::exists(dwnld_dlg.get_download_path().parent_path())) { + show_error(nullptr,GUI::format_wxstr(_L("Download can't proceed. Target directory doesn't exists: %1%"), dwnld_dlg.get_download_path().parent_path().string())); + return; + } app_data.target_path =dwnld_dlg.get_download_path(); // start download diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 258a9c784..81e9d6a34 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -147,13 +147,14 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos #endif content_sizer->AddSpacer(VERT_SPACING); content_sizer->AddSpacer(VERT_SPACING); - content_sizer->Add(new wxStaticText(this, wxID_ANY, _(L("Target path:")))); + content_sizer->Add(new wxStaticText(this, wxID_ANY, _(L("Target directory:")))); content_sizer->AddSpacer(VERT_SPACING); - txtctrl_path = new wxTextCtrl(this, wxID_ANY, path.wstring()); + txtctrl_path = new wxTextCtrl(this, wxID_ANY, GUI::format_wxstr(path.parent_path().string())); + filename = GUI::format_wxstr(path.filename().string()); content_sizer->Add(txtctrl_path, 1, wxEXPAND); content_sizer->AddSpacer(VERT_SPACING); - wxButton* btn = new wxButton(this, wxID_ANY, _L("Select path")); + wxButton* btn = new wxButton(this, wxID_ANY, _L("Select directory")); content_sizer->Add(btn/*, 1, wxEXPAND*/); // button to open file dialog @@ -165,13 +166,14 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos wxString wxext = boost::nowide::widen(extension); wildcard = GUI::format_wxstr("%1% Files (*.%2%)|*.%2%", wxext.Upper(), wxext); } - wxFileDialog save_dlg( + wxDirDialog save_dlg( this - , _L("Save as:") + , _L("Select directory:") , txtctrl_path->GetValue() - , boost::nowide::widen(AppUpdater::get_filename_from_url(txtctrl_path->GetValue().ToUTF8().data())) + /* + , filename //boost::nowide::widen(AppUpdater::get_filename_from_url(txtctrl_path->GetValue().ToUTF8().data())) , wildcard - , wxFD_SAVE | wxFD_OVERWRITE_PROMPT + , wxFD_SAVE | wxFD_OVERWRITE_PROMPT*/ ); if (save_dlg.ShowModal() == wxID_OK) { txtctrl_path->SetValue(save_dlg.GetPath()); @@ -185,8 +187,25 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) { btn_ok->SetLabel(_L("Download")); btn_ok->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e){ - if (boost::filesystem::exists(boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()))) { - MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("File %1% already exists. Do you wish to overwrite it?"), txtctrl_path->GetValue()),_L("Notice"), wxYES_NO); + boost::filesystem::path path =boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename); + boost::system::error_code ec; + if (path.parent_path().string().empty()) { + MessageDialog msgdlg(nullptr, _L("Directory path is empty."), _L("Notice"), wxYES_NO); + return; + } + ec.clear(); + if (!boost::filesystem::exists(path.parent_path(), ec) || !boost::filesystem::is_directory(path.parent_path(),ec) || ec) { + MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Directory %1% doesn't exists. Do you wish to create it?"), GUI::format_wxstr(path.parent_path().string())), _L("Notice"), wxYES_NO); + if (msgdlg.ShowModal() != wxID_YES) + return; + if(!boost::filesystem::create_directory(path.parent_path())) + { + MessageDialog msgdlg(nullptr, _L("Failed to creeate directory."), _L("Notice"), wxYES_NO); + return; + } + } + if (boost::filesystem::exists(path)) { + MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("File %1% already exists. Do you wish to overwrite it?"), GUI::format_wxstr(path.string())),_L("Notice"), wxYES_NO); if (msgdlg.ShowModal() != wxID_YES) return; } @@ -211,7 +230,7 @@ bool AppUpdateDownloadDialog::run_after_download() const boost::filesystem::path AppUpdateDownloadDialog::get_download_path() const { - return boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()); + return boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename); } // MsgUpdateConfig diff --git a/src/slic3r/GUI/UpdateDialogs.hpp b/src/slic3r/GUI/UpdateDialogs.hpp index 0f5e1d6fb..deedd9a50 100644 --- a/src/slic3r/GUI/UpdateDialogs.hpp +++ b/src/slic3r/GUI/UpdateDialogs.hpp @@ -73,6 +73,7 @@ public: private: wxCheckBox* cbox_run; wxTextCtrl* txtctrl_path; + wxString filename; }; // Confirmation dialog informing about configuration update. Lists updated bundles & their versions. diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index aa3339f1e..fc847ec53 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -203,6 +203,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d BOOST_LOG_TRIVIAL(error) << "Download from " << data.url << " could not start. Destination path is empty."; return boost::filesystem::path(); } + std::string error_message; bool res = http_get_file(data.url, 130 * 1024 * 1024 //2.4.0 windows installer is 65MB //lm:I don't know, but larger. The binaries will grow. // dk: changed to 130, to have 100% more space. We should put this information into version file. // on_progress From d047969d3dd69399d2bd160e9a1f8dc1d42cb27d Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 20 Jan 2023 08:53:10 +0100 Subject: [PATCH 161/206] missing include --- src/slic3r/GUI/UpdateDialogs.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 81e9d6a34..ccf8f17f7 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" From dae9538eaf0c0dc81e6733b5f5d882a0afed7cfa Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 20 Jan 2023 09:51:40 +0100 Subject: [PATCH 162/206] followup of af0e312542dd7ef98ea7957611f7fe9abe1a1fb4 - reverted wrong exception catching. --- src/libslic3r/PresetBundle.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 81de20716..899d24ecc 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1308,22 +1308,13 @@ std::pair PresetBundle::load_configbundle( try { pt::read_ini(ifs, tree); } catch (const boost::property_tree::ini_parser::ini_parser_error &err) { - // This throw was uncatched. While other similar problems later are just returning empty pair. - //throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); - BOOST_LOG_TRIVIAL(error) << format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str(); - return std::make_pair(PresetsConfigSubstitutions{}, 0); + throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); } } const VendorProfile *vendor_profile = nullptr; if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { - VendorProfile vp; - try { - vp = VendorProfile::from_ini(tree, path); - } catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Failed to open profile file.") % path; - return std::make_pair(PresetsConfigSubstitutions{}, 0); - } + VendorProfile vp = VendorProfile::from_ini(tree, path); if (vp.models.size() == 0 && !vp.templates_profile) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; return std::make_pair(PresetsConfigSubstitutions{}, 0); From 617747acb3ab4ff70093fe0b7ad9d1787589da38 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 20 Jan 2023 11:09:36 +0100 Subject: [PATCH 163/206] followup of 3f69799047a3244717bfe6fd2e5cebb1f09d7260 - improved path checks --- src/slic3r/GUI/UpdateDialogs.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index ccf8f17f7..9c4f12eaf 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -188,20 +188,30 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) { btn_ok->SetLabel(_L("Download")); btn_ok->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e){ - boost::filesystem::path path =boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename); + boost::filesystem::path path = boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename); boost::system::error_code ec; if (path.parent_path().string().empty()) { - MessageDialog msgdlg(nullptr, _L("Directory path is empty."), _L("Notice"), wxYES_NO); + MessageDialog msgdlg(nullptr, _L("Directory path is empty."), _L("Notice"), wxOK); + msgdlg.ShowModal(); return; } ec.clear(); if (!boost::filesystem::exists(path.parent_path(), ec) || !boost::filesystem::is_directory(path.parent_path(),ec) || ec) { + ec.clear(); + if (!boost::filesystem::exists(path.parent_path().parent_path(), ec) || !boost::filesystem::is_directory(path.parent_path().parent_path(), ec) || ec) { + MessageDialog msgdlg(nullptr, _L("Directory path is incorrect."), _L("Notice"), wxOK); + msgdlg.ShowModal(); + return; + } + MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Directory %1% doesn't exists. Do you wish to create it?"), GUI::format_wxstr(path.parent_path().string())), _L("Notice"), wxYES_NO); if (msgdlg.ShowModal() != wxID_YES) return; - if(!boost::filesystem::create_directory(path.parent_path())) + ec.clear(); + if(!boost::filesystem::create_directory(path.parent_path(), ec) || ec) { - MessageDialog msgdlg(nullptr, _L("Failed to creeate directory."), _L("Notice"), wxYES_NO); + MessageDialog msgdlg(nullptr, _L("Failed to create directory."), _L("Notice"), wxOK); + msgdlg.ShowModal(); return; } } From f7f763300e7d21d2f44d7d92d2bf46b4b00ecaf9 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 6 Jan 2023 18:31:48 +0100 Subject: [PATCH 164/206] Experiment: Added a rectilinear monotonic infill without perimeter connection lines for top / bottom infill patterns. Co-authored-by: lane.wei --- src/libslic3r/Fill/Fill.cpp | 9 +++++++-- src/libslic3r/Fill/FillBase.cpp | 1 + src/libslic3r/Fill/FillRectilinear.cpp | 13 ++++++++++++- src/libslic3r/Fill/FillRectilinear.hpp | 9 +++++++++ src/libslic3r/PrintConfig.cpp | 26 ++++++++++++-------------- src/libslic3r/PrintConfig.hpp | 2 +- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index e0e3bdc5d..082cf93cc 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -110,6 +110,11 @@ struct SurfaceFill { SurfaceFillParams params; }; +static inline bool fill_type_monotonic(InfillPattern pattern) +{ + return pattern == ipMonotonic || pattern == ipMonotonicLines; +} + std::vector group_fills(const Layer &layer) { std::vector surface_fills; @@ -138,7 +143,7 @@ std::vector group_fills(const Layer &layer) //FIXME for non-thick bridges, shall we allow a bottom surface pattern? params.pattern = (surface.is_external() && ! is_bridge) ? (surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) : - region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; + fill_type_monotonic(region_config.top_fill_pattern) ? ipMonotonic : ipRectilinear; } else if (params.density <= 0) continue; @@ -279,7 +284,7 @@ std::vector group_fills(const Layer &layer) if (internal_solid_fill == nullptr) { // Produce another solid fill. params.extruder = layerm.region().extruder(frSolidInfill); - params.pattern = layerm.region().config().top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; + params.pattern = fill_type_monotonic(layerm.region().config().top_fill_pattern) ? ipMonotonic : ipRectilinear; params.density = 100.f; params.extrusion_role = ExtrusionRole::InternalInfill; params.angle = float(Geometry::deg2rad(layerm.region().config().fill_angle.value)); diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index b38c5c9f8..6a0f41afb 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -37,6 +37,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipRectilinear: return new FillRectilinear(); case ipAlignedRectilinear: return new FillAlignedRectilinear(); case ipMonotonic: return new FillMonotonic(); + case ipMonotonicLines: return new FillMonotonicLines(); case ipLine: return new FillLine(); case ipGrid: return new FillGrid(); case ipTriangles: return new FillTriangles(); diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index bb93d824b..e27017b03 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -2970,7 +2970,18 @@ Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams & params2.monotonic = true; Polylines polylines_out; if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) - BOOST_LOG_TRIVIAL(error) << "FillMonotonous::fill_surface() failed to fill a region."; + BOOST_LOG_TRIVIAL(error) << "FillMonotonic::fill_surface() failed to fill a region."; + return polylines_out; +} + +Polylines FillMonotonicLines::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + FillParams params2 = params; + params2.monotonic = true; + params2.anchor_length_max = 0.0f; + Polylines polylines_out; + if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillMonotonicLines::fill_surface() failed to fill a region."; return polylines_out; } diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 0a6c976ad..f5228440d 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -49,6 +49,15 @@ public: bool no_sort() const override { return true; } }; +class FillMonotonicLines : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillMonotonicLines(*this); } + ~FillMonotonicLines() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool no_sort() const override { return true; } +}; + class FillGrid : public FillRectilinear { public: diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c6f0eeab5..333ad6cd0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -96,6 +96,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) static const t_config_enum_values s_keys_map_InfillPattern { { "rectilinear", ipRectilinear }, { "monotonic", ipMonotonic }, + { "monotoniclines", ipMonotonicLines }, { "alignedrectilinear", ipAlignedRectilinear }, { "grid", ipGrid }, { "triangles", ipTriangles }, @@ -769,20 +770,17 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells."); def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("rectilinear"); - def->enum_values.push_back("monotonic"); - def->enum_values.push_back("alignedrectilinear"); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("hilbertcurve"); - def->enum_values.push_back("archimedeanchords"); - def->enum_values.push_back("octagramspiral"); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Monotonic")); - def->enum_labels.push_back(L("Aligned Rectilinear")); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Hilbert Curve")); - def->enum_labels.push_back(L("Archimedean Chords")); - def->enum_labels.push_back(L("Octagram Spiral")); + def->set_enum_values({ + { "rectilinear", L("Rectilinear") }, + { "monotonic", L("Monotonic") }, + { "monotoniclines", L("Monotonic Lines") }, + { "alignedrectilinear", L("Aligned Rectilinear") }, + { "concentric", L("Concentric") }, + { "hilbertcurve", L("Hilbert Curve") }, + { "archimedeanchords", L("Archimedean Chords") }, + { "octagramspiral", L("Octagram Spiral") } + }); + // solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern. def->aliases = { "solid_fill_pattern", "external_fill_pattern" }; def->set_default_value(new ConfigOptionEnum(ipMonotonic)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index eed5df626..42ac9c0e2 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -57,7 +57,7 @@ enum class FuzzySkinType { }; enum InfillPattern : int { - ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, + ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, ipCount, From fdecb3066494d97ddd16d8ec0328e8aa89d807e0 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 20 Jan 2023 18:01:58 +0100 Subject: [PATCH 165/206] Tree Supports: Refactoring of RadiusLayerPolygonCache for speed. --- src/libslic3r/TreeModelVolumes.cpp | 27 +++++++--- src/libslic3r/TreeModelVolumes.hpp | 80 +++++++++++++++++++----------- src/libslic3r/Utils.hpp | 15 ++++++ 3 files changed, 87 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index bcd071767..3c7cd8af3 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -574,7 +574,8 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vector(0, max_layer + 1, keys.size()), [&](const tbb::blocked_range &range) { - RadiusLayerPolygonCacheData data; + std::vector> data; + data.reserve(range.size() * keys.size()); for (LayerIndex layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { for (RadiusLayerPair key : keys) if (layer_idx <= key.second) { @@ -585,10 +586,10 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vector 0); // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. - data[RadiusLayerPair(radius, layer_idx)] = polygons_simplify( - offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), + data.emplace_back(RadiusLayerPair(radius, layer_idx), polygons_simplify( + offset(union_ex(this->getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), - m_min_resolution); + m_min_resolution)); } } m_collision_cache_holefree.insert(std::move(data)); @@ -832,13 +833,25 @@ coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const return out; } +void TreeModelVolumes::RadiusLayerPolygonCache::allocate_layers(size_t num_layers) +{ + if (num_layers > m_data.size()) { + if (num_layers > m_data.capacity()) + reserve_power_of_2(m_data, num_layers); + m_data.resize(num_layers, {}); + } +} + // For debugging purposes, sorted by layer index, then by radius. std::vector>> TreeModelVolumes::RadiusLayerPolygonCache::sorted() const { std::vector>> out; - for (auto it = this->data.begin(); it != this->data.end(); ++ it) - out.emplace_back(it->first, it->second); - std::sort(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; }); + for (auto &layer : m_data) { + auto layer_idx = LayerIndex(&layer - m_data.data()); + for (auto &radius_polygons : layer) + out.emplace_back(std::make_pair(radius_polygons.first, layer_idx), radius_polygons.second); + } + assert(std::is_sorted(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; })); return out; } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 99f5797d2..407025e1f 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -305,36 +305,36 @@ private: * \brief Convenience typedef for the keys to the caches */ using RadiusLayerPair = std::pair; - using RadiusLayerPolygonCacheData = std::unordered_map>; class RadiusLayerPolygonCache { + // Map from radius to Polygons. Cache of one layer collision regions. + using LayerData = std::map; + // Vector of layers, at each layer map of radius to Polygons. + // Reference to Polygons returned shall be stable to insertion. + using Layers = std::vector; public: RadiusLayerPolygonCache() = default; - RadiusLayerPolygonCache(RadiusLayerPolygonCache &&rhs) : data(std::move(rhs.data)) {} - RadiusLayerPolygonCache& operator=(RadiusLayerPolygonCache &&rhs) { data = std::move(rhs.data); return *this; } + RadiusLayerPolygonCache(RadiusLayerPolygonCache &&rhs) : m_data(std::move(rhs.m_data)) {} + RadiusLayerPolygonCache& operator=(RadiusLayerPolygonCache &&rhs) { m_data = std::move(rhs.m_data); return *this; } RadiusLayerPolygonCache(const RadiusLayerPolygonCache&) = delete; RadiusLayerPolygonCache& operator=(const RadiusLayerPolygonCache&) = delete; - void insert(RadiusLayerPolygonCacheData &&in) { - std::lock_guard guard(this->mutex); - for (auto& d : in) - this->data.emplace(d.first, std::move(d.second)); - } void insert(std::vector> &&in) { - std::lock_guard guard(this->mutex); - for (auto& d : in) - this->data.emplace(d.first, std::move(d.second)); + std::lock_guard guard(m_mutex); + for (auto &d : in) + this->get_allocate_layer_data(d.first.second).emplace(d.first.first, std::move(d.second)); } // by layer void insert(std::vector> &&in, coord_t radius) { - std::lock_guard guard(this->mutex); + std::lock_guard guard(m_mutex); for (auto &d : in) - this->data.emplace(RadiusLayerPair{ radius, d.first }, std::move(d.second)); + this->get_allocate_layer_data(d.first).emplace(radius, std::move(d.second)); } void insert(std::vector &&in, coord_t first_layer_idx, coord_t radius) { - std::lock_guard guard(this->mutex); + std::lock_guard guard(m_mutex); + allocate_layers(first_layer_idx + in.size()); for (auto &d : in) - this->data.emplace(RadiusLayerPair{ radius, first_layer_idx ++ }, std::move(d)); + m_data[first_layer_idx ++].emplace(radius, std::move(d)); } /*! * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found @@ -342,11 +342,30 @@ private: * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) */ std::optional> getArea(const TreeModelVolumes::RadiusLayerPair &key) const { - std::lock_guard guard(this->mutex); - const auto it = this->data.find(key); - return it == this->data.end() ? + std::lock_guard guard(m_mutex); + if (key.second >= m_data.size()) + return std::optional>{}; + const auto &layer = m_data[key.second]; + auto it = layer.find(key.first); + return it == layer.end() ? std::optional>{} : std::optional>{ it->second }; } + // Get a collision area at a given layer for a radius that is a lower or equial to the key radius. + std::optional>> get_lower_bound_area(const TreeModelVolumes::RadiusLayerPair &key) const { + std::lock_guard guard(m_mutex); + if (key.second >= m_data.size()) + return {}; + const auto &layer = m_data[key.second]; + if (layer.empty()) + return {}; + auto it = layer.lower_bound(key.first); + if (it == layer.end() || it->first != key.first) { + if (it == layer.begin()) + return {}; + -- it; + } + return std::make_pair(it->first, std::reference_wrapper(it->second)); + } /*! * \brief Get the highest already calculated layer in the cache. * \param radius The radius for which the highest already calculated layer has to be found. @@ -355,22 +374,27 @@ private: * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) */ LayerIndex getMaxCalculatedLayer(coord_t radius) const { - std::lock_guard guard(this->mutex); - int max_layer = -1; - // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. - if (this->data.find({ radius, 1 }) != this->data.end()) - max_layer = 1; - while (this->data.count(TreeModelVolumes::RadiusLayerPair(radius, max_layer + 1)) > 0) - ++ max_layer; - return max_layer; + std::lock_guard guard(m_mutex); + auto layer_idx = LayerIndex(m_data.size()) - 1; + for (; layer_idx > 0; -- layer_idx) + if (const auto &layer = m_data[layer_idx]; layer.find(radius) != layer.end()) + break; + // The placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. + return layer_idx == 0 ? -1 : layer_idx; } // For debugging purposes, sorted by layer index, then by radius. [[nodiscard]] std::vector>> sorted() const; private: - RadiusLayerPolygonCacheData data; - mutable std::mutex mutex; + LayerData& get_allocate_layer_data(LayerIndex layer_idx) { + allocate_layers(layer_idx + 1); + return m_data[layer_idx]; + } + void allocate_layers(size_t num_layers); + + Layers m_data; + mutable std::mutex m_mutex; }; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 00de10fc4..815d6ff55 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -176,6 +176,21 @@ template size_t next_highest_power_of_2(T v, return next_highest_power_of_2(uint32_t(v)); } +template void reserve_power_of_2(VectorType &vector, size_t n) +{ + vector.reserve(next_highest_power_of_2(n)); +} + +template void reserve_more(VectorType &vector, size_t n) +{ + vector.reserve(vector.size() + n); +} + +template void reserve_more_power_of_2(VectorType &vector, size_t n) +{ + vector.reserve(next_highest_power_of_2(vector.size() + n)); +} + template inline INDEX_TYPE prev_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count) { From a3324d3e50114aa16c48be7ec6b939c9315f4dde Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 23 Jan 2023 08:17:18 +0100 Subject: [PATCH 166/206] Fixed crash when changing printer while Hollow gizmo is open --- src/slic3r/GUI/GLCanvas3D.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 41bd3a5c5..53df78bf2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2004,7 +2004,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re else m_selection.volumes_changed(map_glvolume_old_to_new); - m_gizmos.update_data(); + // The current gizmo may be not supported by the current printer technology + // after the user changes printer. + // Check if it is still activable before to call update_data() method. + const GLGizmoBase* gizmo = m_gizmos.get_current(); + if (gizmo != nullptr && gizmo->is_activable()) + m_gizmos.update_data(); m_gizmos.refresh_on_off_state(); // Update the toolbar From f327b805dbd21825b09aea72dde619a00f16d42e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 23 Jan 2023 09:21:07 +0100 Subject: [PATCH 167/206] Fixed rendering of shells in preview --- src/slic3r/GUI/GCodeViewer.cpp | 4 ---- src/slic3r/GUI/GLCanvas3D.cpp | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index fdfb6bfe8..84a628572 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3182,14 +3182,10 @@ void GCodeViewer::render_shells() if (shader == nullptr) return; -// glsafe(::glDepthMask(GL_FALSE)); - shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, camera.get_view_matrix(), camera.get_projection_matrix()); shader->stop_using(); - -// glsafe(::glDepthMask(GL_TRUE)); } void GCodeViewer::render_legend(float& legend_height) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 53df78bf2..0adfdb92e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1515,12 +1515,12 @@ void GLCanvas3D::render() _render_background(); _render_objects(GLVolumeCollection::ERenderType::Opaque); - if (!m_main_toolbar.is_enabled()) - _render_gcode(); _render_sla_slices(); _render_selection(); if (is_looking_downward) _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true); + if (!m_main_toolbar.is_enabled()) + _render_gcode(); _render_objects(GLVolumeCollection::ERenderType::Transparent); _render_sequential_clearance(); From c16fa93bdbd4bee6bd922ae6a0c24c7cdb2b0153 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Jan 2023 10:24:17 +0100 Subject: [PATCH 168/206] Allow to switch to the Simple mode, when all objects are single-parts or have support-modificators only. + Object's menu for Advanced and Expert mode are the same now. + "wxYES|wxCANCEL" style is changed to "wxOK|wxCANCEL" for MessageDialog. The reason: wxWidgets implementation for Linux doesn't respects to "wxYES|wxCANCEL" style for MessageDialog. In this case massage dialog has "wxYES_NO|wxCANCEL" style. --- src/slic3r/GUI/GUI_App.cpp | 22 +++++++++++++++++++++- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/GUI_Factories.cpp | 9 +++++---- src/slic3r/GUI/Plater.cpp | 12 ++++++------ src/slic3r/GUI/wxExtensions.cpp | 8 +++++--- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 56b2d2eb1..e9c912da2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2324,13 +2324,33 @@ ConfigOptionMode GUI_App::get_mode() mode == "simple" ? comSimple : comAdvanced; } -void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) +bool GUI_App::save_mode(const /*ConfigOptionMode*/int mode) { const std::string mode_str = mode == comExpert ? "expert" : mode == comSimple ? "simple" : "advanced"; + + auto can_switch_to_simple = [](Model& model) { + for (const ModelObject* model_object : model.objects) + if (model_object->volumes.size() > 1) { + for (size_t i = 1; i < model_object->volumes.size(); ++i) + if (!model_object->volumes[i]->is_support_modifier()) + return false; + } + return true; + }; + + if (mode == comSimple && !can_switch_to_simple(model())) { + show_info(nullptr, + _L("Simple mode supports manipulation with single-part object(s)\n" + "or object(s) with support modifiers only.") + "\n\n" + + _L("Please check your object list before mode changing."), + _L("Change application mode")); + return false; + } app_config->set("view_mode", mode_str); app_config->save(); update_mode(); + return true; } // Update view mode according to selected menu diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 81382d092..70d72d5e3 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -263,7 +263,7 @@ public: Tab* get_tab(Preset::Type type); ConfigOptionMode get_mode(); - void save_mode(const /*ConfigOptionMode*/int mode) ; + bool save_mode(const /*ConfigOptionMode*/int mode) ; void update_mode(); void add_config_menu(wxMenuBar *menu); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 3e01de834..4c41fd72a 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -465,19 +465,19 @@ void MenuFactory::append_menu_item_delete(wxMenu* menu) } -wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) { +wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) +{ auto sub_menu = new wxMenu; const ConfigOptionMode mode = wxGetApp().get_mode(); - if (mode == comExpert && type != ModelVolumeType::INVALID || - mode == comAdvanced && type == ModelVolumeType::MODEL_PART) { + if (mode > comSimple) { append_menu_item(sub_menu, wxID_ANY, _L("Load") + " " + dots, "", [type](wxCommandEvent&) { obj_list()->load_subobject(type); }, "", menu); sub_menu->AppendSeparator(); } - if (!(type == ModelVolumeType::MODEL_PART && mode == comAdvanced)) + //if (!(type == ModelVolumeType::MODEL_PART && mode == comAdvanced)) for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) { if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0) @@ -527,6 +527,7 @@ void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, ) { wxString item_name = is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": "; item_name += _L("Text"); + menu->AppendSeparator(); const std::string icon_name = is_submenu_item ? "" : ADD_VOLUME_MENU_ITEMS[int(type)].second; append_menu_item(menu, wxID_ANY, item_name, "", add_text, icon_name, menu); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1b2198111..af04d5865 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2659,10 +2659,10 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { MessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n", - _L("Detected advanced data"), wxICON_WARNING | wxYES | wxCANCEL); - if (msg_dlg.ShowModal() == wxID_YES) { - Slic3r::GUI::wxGetApp().save_mode(comAdvanced); - view3D->set_as_dirty(); + _L("Detected advanced data"), wxICON_WARNING | wxOK | wxCANCEL); + if (msg_dlg.ShowModal() == wxID_OK) { + if (wxGetApp().save_mode(comAdvanced)) + view3D->set_as_dirty(); } else return obj_idxs; @@ -5299,8 +5299,8 @@ void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bo if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) { // If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking. // There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken. - Slic3r::GUI::wxGetApp().save_mode(comAdvanced); - view3D->set_as_dirty(); + if (wxGetApp().save_mode(comAdvanced)) + view3D->set_as_dirty(); } // this->update() above was called with POSTPONE_VALIDATION_ERROR_MESSAGE, so that if an error message was generated when updating the back end, it would not open immediately, diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index bd2949cbf..e528191aa 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -747,9 +747,11 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 0*/) : { SetFlexibleDirection(wxHORIZONTAL); - auto modebtnfn = [](wxCommandEvent &event, int mode_id) { - Slic3r::GUI::wxGetApp().save_mode(mode_id); - event.Skip(); + auto modebtnfn = [this](wxCommandEvent &event, int mode_id) { + if (Slic3r::GUI::wxGetApp().save_mode(mode_id)) + event.Skip(); + else + SetMode(Slic3r::GUI::wxGetApp().get_mode()); }; m_mode_btns.reserve(3); From 7f9f47da25e9820d72347c44966780429809eb77 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Jan 2023 11:26:19 +0100 Subject: [PATCH 169/206] Follow up https://github.com/Prusa-Development/PrusaSlicerPrivate/commit/c16fa93bdbd4bee6bd922ae6a0c24c7cdb2b0153 -hot fix --- src/slic3r/GUI/GUI_Factories.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 4c41fd72a..c06947565 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -471,7 +471,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty const ConfigOptionMode mode = wxGetApp().get_mode(); - if (mode > comSimple) { + if (type != ModelVolumeType::INVALID && mode > comSimple) { append_menu_item(sub_menu, wxID_ANY, _L("Load") + " " + dots, "", [type](wxCommandEvent&) { obj_list()->load_subobject(type); }, "", menu); sub_menu->AppendSeparator(); From 22a1109e30a927e2314c140dde41d2841894e8a2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 23 Jan 2023 11:28:16 +0100 Subject: [PATCH 170/206] Fixed visibility of Object Manipulator reset buttons for single instance selection --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 41f4cf060..2e2a0d891 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -944,6 +944,9 @@ void ObjectManipulation::update_reset_buttons_visibility() if (selection.is_single_full_instance()) { #if ENABLE_WORLD_COORDINATE + const Geometry::Transformation& trafo = volume->get_instance_transformation(); + rotation = trafo.get_rotation_matrix(); + scale = trafo.get_scaling_factor_matrix(); const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int id : idxs) { const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix()); From b610053085af07b9ae56e36a4d8542213fdb89bd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 23 Jan 2023 12:22:43 +0100 Subject: [PATCH 171/206] Add "experimental" word to sla branching tree type in the combo box --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 333ad6cd0..4da75489c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3890,7 +3890,7 @@ void PrintConfigDef::init_sla_params() def->enum_values = ConfigOptionEnum::get_enum_names(); def->enum_labels = ConfigOptionEnum::get_enum_names(); def->enum_labels[0] = L("Default"); - def->enum_labels[1] = L("Branching"); + def->enum_labels[1] = L("Branching (experimental)"); // TODO: def->enum_labels[2] = L("Organic"); def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); From 4a42795220298ec8a3bd61258bedd3112a471a46 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 23 Jan 2023 12:33:28 +0100 Subject: [PATCH 172/206] Fixed invalid inheritance. --- resources/profiles/Templates.ini | 4286 +++++++++++++++--------------- 1 file changed, 2143 insertions(+), 2143 deletions(-) diff --git a/resources/profiles/Templates.ini b/resources/profiles/Templates.ini index 6c6ff646c..26b0c0f12 100644 --- a/resources/profiles/Templates.ini +++ b/resources/profiles/Templates.ini @@ -1,2144 +1,2144 @@ -# Generic filament profile templates - -[vendor] -name = Filaments Templates -config_version = 1.0.0 -config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Templates/ -templates_profile = 1 - -## Generic filament profiles - -# Print profiles for the Prusa Research printers. - -[filament:*common*] -cooling = 1 -compatible_printers = -compatible_printers_condition = -end_filament_gcode = "; Filament-specific end gcode" -extrusion_multiplier = 1 -filament_loading_speed = 14 -filament_loading_speed_start = 19 -filament_unloading_speed = 90 -filament_unloading_speed_start = 100 -filament_toolchange_delay = 0 -filament_cooling_moves = 1 -filament_cooling_initial_speed = 3 -filament_cooling_final_speed = 2 -filament_load_time = 0 -filament_unload_time = 0 -filament_ramming_parameters = "130 120 2.70968 2.93548 3.32258 3.83871 4.58065 5.54839 6.51613 7.35484 7.93548 8.16129| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" -filament_minimal_purge_on_wipe_tower = 0 -filament_cost = 0 -filament_density = 0 -filament_diameter = 1.75 -filament_notes = "" -filament_settings_id = "" -filament_soluble = 0 -min_print_speed = 10 -slowdown_below_layer_time = 10 -start_filament_gcode = - -[filament:*PLA*] -inherits = *common* -bed_temperature = 60 -bridge_fan_speed = 100 -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -fan_always_on = 1 -fan_below_layer_time = 100 -filament_colour = #FF8000 -filament_max_volumetric_speed = 0 -filament_type = PLA -first_layer_bed_temperature = 60 -first_layer_temperature = 215 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 210 - -[filament:*PET*] -inherits = *common* -bed_temperature = 90 -bridge_fan_speed = 50 -disable_fan_first_layers = 3 -full_fan_speed_layer = 5 -fan_always_on = 1 -fan_below_layer_time = 20 -filament_colour = #FF8000 -filament_max_volumetric_speed = 0 -filament_type = PETG -first_layer_bed_temperature = 85 -first_layer_temperature = 230 -max_fan_speed = 50 -min_fan_speed = 30 -temperature = 240 - -[filament:*ABS*] -inherits = *common* -bed_temperature = 100 -bridge_fan_speed = 25 -cooling = 0 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -filament_colour = #FFF2EC -filament_max_volumetric_speed = 0 -filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" -filament_type = ABS -first_layer_bed_temperature = 100 -first_layer_temperature = 255 -max_fan_speed = 30 -min_fan_speed = 20 -temperature = 255 - -[filament:*ABSC*] -inherits = *common* -bed_temperature = 100 -bridge_fan_speed = 25 -cooling = 1 -disable_fan_first_layers = 4 -fan_always_on = 0 -fan_below_layer_time = 30 -slowdown_below_layer_time = 20 -filament_colour = #FFF2EC -filament_max_volumetric_speed = 0 -filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" -filament_type = ABS -first_layer_bed_temperature = 100 -first_layer_temperature = 255 -max_fan_speed = 15 -min_fan_speed = 15 -min_print_speed = 15 -temperature = 255 - -[filament:*FLEX*] -inherits = *common* -bed_temperature = 50 -bridge_fan_speed = 80 -cooling = 0 -disable_fan_first_layers = 3 -extrusion_multiplier = 1.15 -fan_always_on = 0 -fan_below_layer_time = 100 -filament_colour = #008000 -filament_max_volumetric_speed = 1.8 -filament_type = FLEX -first_layer_bed_temperature = 50 -first_layer_temperature = 240 -max_fan_speed = 90 -min_fan_speed = 70 -temperature = 240 - -[filament:ColorFabb bronzeFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.12 -filament_cost = 80.65 -filament_density = 3.9 -filament_spool_weight = 236 -filament_colour = #804040 - -[filament:ColorFabb steelFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_cost = 80.65 -filament_density = 3.13 -filament_spool_weight = 236 -filament_colour = #808080 - -[filament:ColorFabb copperFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_cost = 80.65 -filament_density = 3.9 -filament_spool_weight = 236 -filament_colour = #82603E - -[filament:ColorFabb HT] -inherits = *PET* -filament_vendor = ColorFabb -bed_temperature = 100 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_cost = 65.66 -filament_density = 1.18 -filament_spool_weight = 236 -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 -temperature = 270 - -[filament:ColorFabb PLA-PHA] -inherits = *PLA* -filament_vendor = ColorFabb -filament_cost = 54.84 -filament_density = 1.24 -filament_spool_weight = 236 - -[filament:ColorFabb woodFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_cost = 78.63 -filament_density = 1.15 -filament_spool_weight = 236 -filament_colour = #dfc287 -first_layer_temperature = 200 -temperature = 200 - -[filament:ColorFabb corkFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_cost = 78.63 -filament_density = 1.18 -filament_spool_weight = 236 -filament_colour = #634d33 -filament_max_volumetric_speed = 6 -first_layer_temperature = 220 -temperature = 220 - -[filament:ColorFabb XT] -inherits = *PET* -filament_vendor = ColorFabb -filament_cost = 62.90 -filament_density = 1.27 -filament_spool_weight = 236 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 - -[filament:ColorFabb XT-CF20] -inherits = *PET* -filament_vendor = ColorFabb -extrusion_multiplier = 1.05 -filament_cost = 80.65 -filament_density = 1.35 -filament_spool_weight = 236 -filament_colour = #804040 -filament_max_volumetric_speed = 2 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 260 - -[filament:ColorFabb nGen] -inherits = *PET* -filament_vendor = ColorFabb -filament_cost = 52.46 -filament_density = 1.2 -filament_spool_weight = 236 -bridge_fan_speed = 40 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_type = NGEN -first_layer_temperature = 240 -max_fan_speed = 35 -min_fan_speed = 20 - -[filament:ColorFabb nGen flex] -inherits = *FLEX* -filament_vendor = ColorFabb -filament_cost = 58.30 -filament_density = 1 -filament_spool_weight = 236 -bed_temperature = 85 -bridge_fan_speed = 40 -cooling = 1 -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -fan_below_layer_time = 10 -filament_max_volumetric_speed = 5 -first_layer_bed_temperature = 85 -first_layer_temperature = 260 -max_fan_speed = 35 -min_fan_speed = 20 -temperature = 260 - -[filament:Kimya PETG Carbon] -inherits = *PET* -filament_vendor = Kimya -extrusion_multiplier = 1.05 -filament_cost = 150.02 -filament_density = 1.317 -filament_colour = #804040 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 240 - -[filament:Kimya ABS Carbon] -inherits = *ABSC* -filament_vendor = Kimya -filament_cost = 140.34 -filament_density = 1.032 -filament_colour = #804040 -first_layer_temperature = 260 -temperature = 260 - -[filament:Kimya ABS Kevlar] -inherits = Kimya ABS Carbon -filament_vendor = Kimya -filament_density = 1.037 - -[filament:E3D Edge] -inherits = *PET* -filament_vendor = E3D -filament_cost = 56.9 -filament_density = 1.26 -filament_type = EDGE - -[filament:E3D PC-ABS] -inherits = *ABS* -filament_vendor = E3D -filament_cost = 0 -filament_type = PC -filament_density = 1.05 -first_layer_temperature = 270 -temperature = 270 - -[filament:Fillamentum PLA] -inherits = *PLA* -filament_vendor = Fillamentum -filament_cost = 35.48 -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:Fillamentum ABS] -inherits = *ABSC* -filament_vendor = Fillamentum -filament_cost = 32.4 -filament_density = 1.04 -filament_spool_weight = 230 -first_layer_temperature = 240 -temperature = 240 - -[filament:Fillamentum ASA] -inherits = *ABS* -filament_vendor = Fillamentum -filament_cost = 38.7 -filament_density = 1.07 -filament_spool_weight = 230 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -min_print_speed = 15 -slowdown_below_layer_time = 15 -first_layer_temperature = 260 -temperature = 260 -filament_type = ASA - -[filament:Prusament ASA] -inherits = *ABS* -filament_vendor = Prusa Polymers -filament_cost = 42.69 -filament_density = 1.07 -filament_spool_weight = 201 -fan_always_on = 1 -first_layer_temperature = 260 -first_layer_bed_temperature = 100 -temperature = 260 -bed_temperature = 100 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 4 -filament_type = ASA -filament_colour = #FFF2EC - -[filament:Prusament PC Blend] -inherits = *ABS* -filament_vendor = Prusa Polymers -filament_cost = 62.36 -filament_density = 1.22 -filament_spool_weight = 201 -fan_always_on = 0 -first_layer_temperature = 275 -first_layer_bed_temperature = 105 -temperature = 275 -bed_temperature = 105 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 20 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -filament_type = PC -filament_colour = #DEE0E6 - -[filament:Prusament PC Blend Carbon Fiber] -inherits = Prusament PC Blend -filament_cost = 90.73 -filament_density = 1.16 -extrusion_multiplier = 1.04 -first_layer_temperature = 285 -temperature = 285 -disable_fan_first_layers = 4 -fan_below_layer_time = 10 -filament_colour = #BBBBBB - -[filament:Prusament PA11 Carbon Fiber] -inherits = Prusament PC Blend Carbon Fiber -filament_cost = 151.24 -filament_density = 1.11 -filament_type = PA -extrusion_multiplier = 1.05 -first_layer_temperature = 275 -temperature = 285 -first_layer_bed_temperature = 90 -bed_temperature = 105 -fan_below_layer_time = 10 - -[filament:Fillamentum CPE] -inherits = *PET* -filament_vendor = Fillamentum -filament_cost = 56.45 -filament_density = 1.25 -filament_spool_weight = 230 -filament_type = CPE -first_layer_bed_temperature = 90 -first_layer_temperature = 275 -min_fan_speed = 30 -max_fan_speed = 50 -disable_fan_first_layers = 3 -full_fan_speed_layer = 5 -temperature = 275 - -[filament:Fillamentum Timberfill] -inherits = *PLA* -filament_vendor = Fillamentum -extrusion_multiplier = 1.05 -filament_cost = 68 -filament_density = 1.15 -filament_spool_weight = 230 -filament_colour = #804040 -first_layer_temperature = 190 -temperature = 190 -filament_retract_lift = 0.2 - -[filament:Smartfil Wood] -inherits = *PLA* -filament_vendor = Smart Materials 3D -extrusion_multiplier = 1.05 -filament_cost = 68 -filament_density = 1.58 -filament_colour = #804040 -first_layer_temperature = 220 -temperature = 220 - -[filament:Generic ABS] -inherits = *ABSC* -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.04 - -[filament:Esun ABS] -inherits = *ABSC* -filament_vendor = Esun -filament_cost = 27.82 -filament_density = 1.01 -filament_spool_weight = 265 - -[filament:Hatchbox ABS] -inherits = *ABSC* -filament_vendor = Hatchbox -filament_cost = 27.82 -filament_density = 1.04 -filament_spool_weight = 245 - -[filament:Filament PM ABS] -inherits = *ABSC* -filament_vendor = Filament PM -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 230 - -[filament:Verbatim ABS] -inherits = *ABSC* -filament_vendor = Verbatim -filament_cost = 25.87 -filament_density = 1.05 -filament_spool_weight = 235 - -[filament:Generic PETG] -inherits = *PET* -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.27 - -[filament:Extrudr DuraPro ASA] -inherits = Fillamentum ASA -filament_vendor = Extrudr -bed_temperature = 90 -filament_cost = 34.64 -filament_density = 1.05 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" -first_layer_bed_temperature = 90 -first_layer_temperature = 220 -temperature = 220 -filament_spool_weight = 230 - -[filament:Extrudr PETG] -inherits = *PET* -filament_vendor = Extrudr -bed_temperature = 70 -filament_cost = 35.45 -filament_density = 1.29 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" -first_layer_bed_temperature = 70 -first_layer_temperature = 220 -temperature = 220 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 -full_fan_speed_layer = 0 - -[filament:Extrudr XPETG CF] -inherits = Extrudr PETG -filament_cost = 62.49 -filament_density = 1.29 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" -first_layer_temperature = 235 -temperature = 235 -filament_spool_weight = 230 - -[filament:Extrudr XPETG Matt] -inherits = Extrudr PETG -filament_cost = 29.99 -filament_density = 1.41 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" -first_layer_temperature = 230 -temperature = 230 - -[filament:Extrudr BioFusion] -inherits = *PLA* -filament_vendor = Extrudr -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_cost = 31.23 -filament_density = 1.25 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" -first_layer_temperature = 220 -temperature = 220 -max_fan_speed = 45 -min_fan_speed = 25 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr Flax] -inherits = *PLA* -filament_vendor = Extrudr -filament_cost = 50.91 -filament_density = 1.45 -filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" -first_layer_temperature = 190 -temperature = 190 -max_fan_speed = 80 -min_fan_speed = 30 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 - -[filament:Extrudr GreenTEC] -inherits = *PLA* -filament_vendor = Extrudr -filament_cost = 50.91 -filament_density = 1.3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?ignorechildren=1&material=106" -first_layer_temperature = 208 -temperature = 208 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 - -[filament:Extrudr GreenTEC Pro] -inherits = *PLA* -filament_vendor = Extrudr -filament_cost = 56.23 -filament_density = 1.35 -filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" -temperature = 215 -max_fan_speed = 80 -min_fan_speed = 30 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr GreenTEC Pro Carbon] -inherits = *PLA* -filament_vendor = Extrudr -filament_cost = 62.49 -filament_density = 1.2 -filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" -first_layer_temperature = 225 -max_fan_speed = 80 -min_fan_speed = 30 -temperature = 225 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr PLA NX1] -inherits = *PLA* -filament_vendor = Extrudr -filament_cost = 22.76 -filament_density = 1.24 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" -temperature = 205 -bed_temperature = 60 -first_layer_temperature = 205 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 0 -max_fan_speed = 90 -min_fan_speed = 30 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 - -[filament:Extrudr PLA NX2] -inherits = Extrudr PLA NX1 -filament_cost = 23.63 -filament_density = 1.3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" - -[filament:Extrudr Flex Hard] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.15 -filament_cost = 39.98 -filament_density = 1.2 -filament_max_volumetric_speed = 3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:Extrudr Flex Medium] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.15 -filament_cost = 39.98 -filament_density = 1.19 -filament_max_volumetric_speed = 3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=117" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:Extrudr Flex SemiSoft] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.15 -filament_cost = 39.98 -filament_density = 1.18 -filament_max_volumetric_speed = 1.8 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=116" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:addnorth Adamant S1] -inherits = *FLEX* -filament_vendor = addnorth -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -filament_cost = -filament_density = 1.22 -temperature = 250 -bed_temperature = 50 -first_layer_temperature = 245 -first_layer_bed_temperature = 50 -slowdown_below_layer_time = 20 -min_print_speed = 20 -fan_below_layer_time = 15 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 40 -max_fan_speed = 70 -bridge_fan_speed = 60 -filament_max_volumetric_speed = 1.7 - -[filament:addnorth Adura X] -inherits = *PET* -filament_vendor = addnorth -filament_cost = 29.99 -filament_density = 1.27 -filament_type = PA -extrusion_multiplier = 0.98 -bed_temperature = 105 -first_layer_bed_temperature = 105 -first_layer_temperature = 265 -temperature = 270 -fan_always_on = 0 -min_fan_speed = 20 -max_fan_speed = 40 -bridge_fan_speed = 70 -slowdown_below_layer_time = 10 -min_print_speed = 20 -fan_below_layer_time = 10 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 - -[filament:addnorth E-PLA] -inherits = *PLA* -filament_vendor = addnorth -filament_cost = 24.99 -filament_density = 1.24 -extrusion_multiplier = 0.98 -temperature = 215 -bed_temperature = 60 -first_layer_temperature = 215 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 15 -filament_spool_weight = 0 - -[filament:addnorth ESD-PETG] -inherits = *PET* -filament_vendor = addnorth -filament_cost = 29.99 -filament_density = 1.27 -extrusion_multiplier = 1 -bed_temperature = 80 -first_layer_bed_temperature = 85 -first_layer_temperature = 245 -temperature = 265 -fan_always_on = 1 -min_fan_speed = 15 -max_fan_speed = 30 -bridge_fan_speed = 35 -slowdown_below_layer_time = 10 -min_print_speed = 15 -fan_below_layer_time = 8 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 - -[filament:addnorth OBC Polyethylene] -inherits = *FLEX* -filament_vendor = addnorth -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -filament_cost = 82 -filament_density = 1.22 -temperature = 200 -bed_temperature = 100 -first_layer_temperature = 195 -first_layer_bed_temperature = 100 -slowdown_below_layer_time = 5 -fan_below_layer_time = 15 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 30 -bridge_fan_speed = 40 -min_print_speed = 20 -filament_spool_weight = 0 -filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." - -[filament:addnorth PETG] -inherits = *PET* -filament_vendor = addnorth -filament_cost = 29.99 -filament_density = 1.27 -bed_temperature = 80 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 250 -fan_always_on = 1 -min_fan_speed = 15 -max_fan_speed = 40 -bridge_fan_speed = 50 -slowdown_below_layer_time = 10 -min_print_speed = 15 -fan_below_layer_time = 15 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:addnorth Rigid X] -inherits = *PET* -filament_vendor = addnorth -filament_cost = 29.99 -filament_density = 1.27 -extrusion_multiplier = 1 -bed_temperature = 85 -first_layer_bed_temperature = 90 -first_layer_temperature = 250 -temperature = 260 -fan_always_on = 1 -min_fan_speed = 20 -max_fan_speed = 60 -bridge_fan_speed = 70 -slowdown_below_layer_time = 10 -fan_below_layer_time = 20 -min_print_speed = 20 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 -filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." - -[filament:addnorth Textura] -inherits = *PLA* -filament_vendor = addnorth -filament_cost = 24.99 -filament_density = 1.24 -extrusion_multiplier = 0.95 -temperature = 215 -bed_temperature = 65 -first_layer_temperature = 215 -first_layer_bed_temperature = 65 -min_fan_speed = 20 -max_fan_speed = 40 -bridge_fan_speed = 60 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 15 -min_print_speed = 20 -filament_spool_weight = 0 - -[filament:Filamentworld ABS] -inherits = *ABSC* -filament_vendor = Filamentworld -filament_cost = 24.9 -filament_density = 1.04 -temperature = 230 -bed_temperature = 95 -first_layer_temperature = 240 -first_layer_bed_temperature = 100 -max_fan_speed = 20 -min_fan_speed = 10 -min_print_speed = 20 -disable_fan_first_layers = 3 -fan_below_layer_time = 60 -slowdown_below_layer_time = 15 -bridge_fan_speed = 20 - -[filament:Filamentworld PETG] -inherits = *PET* -filament_vendor = Filamentworld -filament_cost = 34.9 -filament_density = 1.27 -bed_temperature = 70 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 235 -fan_always_on = 1 -min_fan_speed = 25 -max_fan_speed = 55 -bridge_fan_speed = 55 -slowdown_below_layer_time = 20 -min_print_speed = 20 -fan_below_layer_time = 35 -disable_fan_first_layers = 2 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:Filamentworld PLA] -inherits = *PLA* -filament_vendor = Filamentworld -filament_cost = 24.9 -filament_density = 1.24 -temperature = 205 -bed_temperature = 55 -first_layer_temperature = 215 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 10 -filament_spool_weight = 0 -min_print_speed = 20 - -[filament:Filament PM PETG] -inherits = *PET* -filament_vendor = Filament PM -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 - -[filament:Generic PLA] -inherits = *PLA* -filament_vendor = Generic -filament_cost = 25.4 -filament_density = 1.24 - -[filament:3D-Fuel Standard PLA] -inherits = *PLA* -filament_vendor = 3D-Fuel -filament_cost = 22.14 -filament_density = 1.24 -first_layer_temperature = 210 -temperature = 200 - -[filament:3D-Fuel EasiPrint PLA] -inherits = 3D-Fuel Standard PLA -filament_cost = 30.44 - -[filament:3D-Fuel Pro PLA] -inherits = *PLA* -filament_vendor = 3D-Fuel -filament_cost = 26.57 -filament_density = 1.22 -first_layer_temperature = 220 -temperature = 215 - -[filament:3D-Fuel Buzzed] -inherits = 3D-Fuel Standard PLA -filament_cost = 44.27 -filament_retract_lift = 0 -first_layer_temperature = 210 -temperature = 195 - -[filament:3D-Fuel Wound up] -inherits = 3D-Fuel Buzzed -filament_cost = 44.27 -filament_retract_lift = nil -first_layer_temperature = 215 -temperature = 210 - -[filament:3D-Fuel Workday ABS] -inherits = *ABSC* -filament_vendor = 3D-Fuel -filament_cost = 23.25 -filament_density = 1.04 - -[filament:Jessie PLA] -inherits = *PLA* -filament_vendor = Printed Solid -filament_cost = 21 -filament_density = 1.24 - -[filament:Jessie PETG] -inherits = *PET* -filament_vendor = Printed Solid -filament_cost = 22 -filament_density = 1.27 -first_layer_temperature = 240 -first_layer_bed_temperature = 85 -temperature = 245 -bed_temperature = 90 - -[filament:Devil Design PLA] -inherits = *PLA* -filament_vendor = Devil Design -filament_cost = 20.99 -filament_density = 1.24 -filament_spool_weight = 250 - -[filament:Devil Design PETG] -inherits = *PET* -filament_vendor = Devil Design -filament_cost = 20.99 -filament_density = 1.23 -filament_spool_weight = 250 -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 - -[filament:Spectrum PLA] -inherits = *PLA* -filament_vendor = Spectrum -filament_cost = 21.50 -filament_density = 1.24 - -[filament:Generic FLEX] -inherits = *FLEX* -filament_vendor = Generic -filament_cost = 82 -filament_density = 1.22 -filament_max_volumetric_speed = 1.2 -filament_retract_length = 0 -filament_retract_speed = nil -filament_retract_lift = nil - -[filament:Fillamentum Flexfill 92A] -inherits = *FLEX* -filament_vendor = Fillamentum -filament_cost = 33.99 -filament_density = 1.20 -filament_spool_weight = 230 -filament_max_volumetric_speed = 1.2 -fan_always_on = 1 -cooling = 0 -max_fan_speed = 60 -min_fan_speed = 60 -disable_fan_first_layers = 4 -full_fan_speed_layer = 6 - -[filament:AmazonBasics TPU] -inherits = *FLEX* -filament_vendor = AmazonBasics -fan_always_on = 1 -filament_max_volumetric_speed = 1.8 -extrusion_multiplier = 1.14 -first_layer_temperature = 235 -first_layer_bed_temperature = 50 -temperature = 235 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 80 -min_fan_speed = 80 -filament_retract_before_travel = 3 -filament_cost = 19.99 -filament_density = 1.21 -disable_fan_first_layers = 4 -full_fan_speed_layer = 6 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:SainSmart TPU] -inherits = *FLEX* -filament_vendor = SainSmart -fan_always_on = 1 -filament_max_volumetric_speed = 2.5 -extrusion_multiplier = 1.05 -first_layer_temperature = 230 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 80 -min_fan_speed = 80 -filament_retract_before_travel = 3 -filament_cost = 32.99 -filament_density = 1.21 -disable_fan_first_layers = 4 -full_fan_speed_layer = 6 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:NinjaTek NinjaFlex TPU] -inherits = *FLEX* -filament_vendor = NinjaTek -fan_always_on = 1 -filament_max_volumetric_speed = 1.2 -extrusion_multiplier = 1.15 -first_layer_temperature = 238 -first_layer_bed_temperature = 50 -temperature = 238 -bed_temperature = 50 -bridge_fan_speed = 75 -max_fan_speed = 60 -min_fan_speed = 60 -filament_cost = 85 -filament_density = 1.19 -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -min_print_speed = 10 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:NinjaTek Cheetah TPU] -inherits = NinjaTek NinjaFlex TPU -filament_retract_length = 1.5 -filament_density = 1.22 -filament_max_volumetric_speed = 4 -extrusion_multiplier = 1.05 -filament_retract_speed = 45 -filament_deretract_speed = 25 -first_layer_temperature = 240 -temperature = 240 - -[filament:Filatech FilaFlex40] -inherits = *FLEX* -filament_vendor = Filatech -fan_always_on = 1 -filament_max_volumetric_speed = 2.5 -extrusion_multiplier = 1.1 -first_layer_temperature = 230 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 50 -min_fan_speed = 50 -filament_cost = 84.68 -filament_density = 1.22 -disable_fan_first_layers = 4 -full_fan_speed_layer = 6 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:Filatech FilaFlex30] -inherits = Filatech FilaFlex40 -temperature = 225 -filament_density = 1.15 -extrusion_multiplier = 1.1 -filament_cost = - -[filament:Filatech FilaFlex55] -inherits = Filatech FilaFlex40 -temperature = 230 -filament_density = 1.18 -bed_temperature = 60 -fan_always_on = 0 -fan_below_layer_time = 60 -filament_cost = -first_layer_temperature = 235 -extrusion_multiplier = 1 - -[filament:Filatech TPU] -inherits = Filatech FilaFlex40 -first_layer_temperature = 230 -filament_density = 1.2 -fan_below_layer_time = 60 -max_fan_speed = 80 -min_fan_speed = 80 -fan_always_on = 1 -temperature = 235 - -[filament:Filatech ABS] -inherits = *ABSC* -filament_vendor = Filatech -filament_cost = -extrusion_multiplier = 1 -filament_density = 1.05 - -[filament:Filatech FilaCarbon] -inherits = *ABSC* -filament_vendor = Filatech -filament_cost = -extrusion_multiplier = 0.95 -filament_density = 1.1 -first_layer_bed_temperature = 100 -bed_temperature = 100 - -[filament:Filatech FilaPLA] -inherits = *PLA* -filament_vendor = Filatech -filament_cost = -filament_density = 1.3 -first_layer_temperature = 235 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 55 - -[filament:Filatech PLA] -inherits = *PLA* -filament_vendor = Filatech -filament_cost = -filament_density = 1.25 -first_layer_temperature = 215 -temperature = 210 - -[filament:Filatech PLA+] -inherits = Filatech PLA -filament_density = 1.24 - -[filament:Filatech FilaTough] -inherits = Filatech ABS -filament_cost = -extrusion_multiplier = 0.95 -filament_density = 1.29 -first_layer_temperature = 245 -first_layer_bed_temperature = 80 -temperature = 240 -bed_temperature = 90 -cooling = 0 - -[filament:Filatech HIPS] -inherits = Prusa HIPS -filament_vendor = Filatech -filament_density = 1.07 -filament_spool_weight = -first_layer_temperature = 230 -first_layer_bed_temperature = 100 -temperature = 225 -bed_temperature = 100 - -[filament:Filatech PA] -inherits = *ABSC* -filament_vendor = Filatech -filament_density = 1.1 -first_layer_temperature = 275 -first_layer_bed_temperature = 105 -temperature = 275 -bed_temperature = 105 -fan_always_on = 0 -cooling = 0 -bridge_fan_speed = 25 -filament_type = PA - -[filament:Filatech PC] -inherits = Filatech PA -filament_density = 1.2 -filament_type = PC - -[filament:Filatech PC-ABS] -inherits = Filatech PC -first_layer_temperature = 270 -temperature = 270 -first_layer_bed_temperature = 105 -bed_temperature = 105 -filament_density = 1.08 -filament_type = PC -fan_always_on = 0 -cooling = 1 -extrusion_multiplier = 0.95 -disable_fan_first_layers = 6 - -[filament:Filatech PETG] -inherits = *PET* -filament_vendor = Filatech -filament_cost = -filament_density = 1.27 -first_layer_temperature = 240 -first_layer_bed_temperature = 75 -temperature = 245 -bed_temperature = 80 -extrusion_multiplier = 0.95 -fan_always_on = 0 - -[filament:Filatech Wood-PLA] -inherits = Filatech PLA -filament_density = 1.05 -first_layer_temperature = 210 - -[filament:Ultrafuse PET] -inherits = *PET* -filament_vendor = BASF -filament_density = 1.33 -filament_colour = #F7F7F7 -first_layer_temperature = 220 -first_layer_bed_temperature = 70 -temperature = 215 -bed_temperature = 70 -fan_below_layer_time = 10 -min_fan_speed = 75 -max_fan_speed = 100 -bridge_fan_speed = 100 -filament_type = PET -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -filament_notes = "Material Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." - -[filament:Ultrafuse PRO1] -inherits = Prusament PLA -filament_vendor = BASF -filament_cost = -filament_density = 1.25 -filament_spool_weight = 0 -filament_colour = #FFFFFF -filament_notes = "Material Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate." - -[filament:Ultrafuse ABS] -inherits = *ABSC* -filament_vendor = BASF -filament_density = 1.04 -min_fan_speed = 10 -max_fan_speed = 20 -bed_temperature = 100 -disable_fan_first_layers = 3 -filament_colour = #FFFFFF -filament_notes = "Material Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion." - -[filament:Ultrafuse ABS Fusion+] -inherits = Ultrafuse ABS -filament_density = 1.08 -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -temperature = 270 -filament_colour = #FFF8D9 -filament_notes = "Material Description\nABS Fusion+ made with Polyscope XILOY™ 3D is an engineering filament which has been optimized for 3D-printing. This special grade has been developed in collaboration with Polyscope Polymers - renowned for its material solutions in the automotive industry. ABS is a thermoplastic which is used in many applications. Although ABS has been classified as a standard material in 3D-printing it is known to be quite challenging to process. ABS Fusion+ combines the properties of ABS with an improved processability. The filament is based on an ABS grade which can be directly printed on glass without any adhesives or tape and has a higher success rate of prints due to extreme low warping." - -[filament:Ultrafuse ASA] -inherits = Ultrafuse ABS Fusion+ -filament_density = 1.07 -filament_colour = #FFF4CA -first_layer_temperature = 275 -temperature = 275 -first_layer_bed_temperature = 100 -bed_temperature = 100 -filament_type = ASA -min_fan_speed = 25 -max_fan_speed = 50 -bridge_fan_speed = 100 -disable_fan_first_layers = 4 -filament_notes = "Material Description\nUltrafuse ASA is a high-performance thermoplastic with similar mechanical properties as ABS. ASA offers additional benefits such as high outdoor weather resistance. The UV resistance, toughness, and rigidity make it an ideal material to 3D-print outdoor fixtures and appliances without losing its properties or color. When also taking into account the high heat resistance and high chemical resistance, this filament is a good choice for many types of applications.\n\nPrinting Recommendations:\nApply Magigoo PC, 3D lac or Dimafix to a clean build plate to improve adhesion." - -[filament:Ultrafuse HIPS] -inherits = Ultrafuse ABS -temperature = 250 -filament_density = 1.02 -filament_type = HIPS -min_fan_speed = 20 -max_fan_speed = 20 -filament_soluble = 1 -filament_notes = "Material Description\nUltrafuse HIPS is a high-quality engineering thermoplastic, which is well known in the 3D-printing industry as a support material for ABS. But this material has additional properties to offer like good impact resistance, good dimensional stability, and easy post-processing. HiPS is a great material to use as a support for ABS because there is a good compatibility between the two materials, and HIPS is an easy breakaway support. Now you have the opportunity to create ABS models with complex geometry. HIPS is easy to post process with glue or with sanding paper." - -[filament:Ultrafuse PA] -inherits = Fillamentum Nylon FX256 -filament_vendor = BASF -filament_density = 1.12 -filament_colour = #ECFAFF -first_layer_temperature = 240 -temperature = 240 -first_layer_bed_temperature = 80 -bed_temperature = 70 -min_fan_speed = 0 -max_fan_speed = 0 -bridge_fan_speed = 0 -fan_below_layer_time = 30 -slowdown_below_layer_time = 20 -min_print_speed = 15 -filament_notes = "Material Description\nThe key features of Ultrafuse PA are the high strength and high modulus. Furthermore, Ultrafuse PA shows a good thermal distortion stability.\n\nPrinting Recommendations:\nApply PVA glue, Kapton tape or PA adhesive to a clean buildplate to improve adhesion." - -[filament:Ultrafuse PA6 GF30] -inherits = Ultrafuse PA -filament_density = 1.17 -first_layer_temperature = 270 -temperature = 270 -first_layer_bed_temperature = 100 -bed_temperature = 100 -filament_colour = #404040 -fan_always_on = 1 -min_fan_speed = 0 -max_fan_speed = 50 -bridge_fan_speed = 100 -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 15 -filament_notes = "Material Description\nUltrafuse® PA6 GF30 is a unique compound specifically developed for FFF printing. Due to the glass fiber content of 30%, parts tend to warp less. In addition the excellent layer adhesion and its compatibility with the water soluble support Ultrafuse® BVOH make this material the perfect solution to develop industrial applications on an FFF printer.\n\nWith its high wear and chemical resistance, high stiffness and strength, Ultrafuse® PA6 GF30 is perfect for a wide variety of applications in automotive, electronics or transportation.\n\nUltrafuse PA6 GF30 is designed for functional prototyping and demanding applications such as industrial tooling, transportation, electronics, small appliances, sports & leisure\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PA6 GF30 can be printed directly onto a clean build plate. For challenging prints, use Magigoo PA gluestick to improve adhesion." - -[filament:Ultrafuse PAHT-CF15] -inherits = Ultrafuse PA6 GF30 -filament_density = 1.23 -filament_notes = "Material Description\nPAHT CF15 is a high-performance 3D printing filament that opens new application fields in FFF printing. In parallel to its advanced mechanical properties, dimensional stability, and chemical resistance, it has very good processability. It works in any FFF printer with a hardened nozzle. In addition to that, it is compatible with water-soluble support material and HiPS, which allow printing complex geometries that work in challenging environments. PAHT CF15 has high heat resistance up to 130 °C and low moisture absorption.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PAHT-CF can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." - -[filament:Ultrafuse PC-ABS-FR] -inherits = Ultrafuse ABS -filament_colour = #505050 -filament_density = 1.17 -first_layer_temperature = 275 -temperature = 275 -first_layer_bed_temperature = 105 -bed_temperature = 105 -filament_type = PC -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -disable_fan_first_layers = 4 -filament_notes = "Material Description\nUltrafuse® PC/ABS FR Black is a V-0 flame retardant blend of Polycarbonate and ABS – two of the most used thermoplastics for engineering & electrical applications. The combination of these two materials results in a premium material with a mix of the excellent mechanical properties of PC and the comparably low printing temperature of ABS. Combined with a halogen free flame retardant, parts printed with Ultrafuse® PC/ABS FR Black feature great tensile and impact strength, higher thermal resistance than ABS and can fulfill the requirements of the UL94 V-0 standard.\n\nPrinting Recommendations:\nApply Magigoo PC to a clean build plate to improve adhesion." - -[filament:Ultrafuse PET-CF15] -inherits = Ultrafuse PET -filament_density = 1.36 -filament_colour = #404040 -first_layer_temperature = 270 -temperature = 270 -first_layer_bed_temperature = 75 -bed_temperature = 75 -min_fan_speed = 60 -max_fan_speed = 100 -bridge_fan_speed = 100 -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 15 -fan_below_layer_time = 30 -filament_notes = "Material Description\nPET CF15 is a Carbon Fiber reinforced PET which has precisely tuned material properties, for a wide range of technical applications. The filament is very strong and stiff and has high heat resistance. With its high dimensional stability and low abrasiveness, the filament offers an easy to print experience which allows direct printing on glass or a PEI sheet. It is compatible with HiPS for breakaway support and water soluble support and has an excellent surface finish.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PET-CF15 can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." - -[filament:Ultrafuse PLA] -inherits = *PLA* -filament_vendor = BASF -filament_density = 1.25 -full_fan_speed_layer = 3 -filament_notes = "Material Description\nPLA is one of the most used materials for 3D printing. Ultrafuse PLA is available in a wide range of colors. The glossy feel often attracts those who print display models or items for household use. Many appreciate the plant-based origin of this material. When properly cooled, PLA has a high maximum printing speed and sharp printed corners. Combining this with low warping of the print makes it a popular plastic for home printers, hobbyists, prototyping and schools.\n\nPrinting Recommendations:\nUltrafuse PLA can be printed directly onto a clean build plate." - -[filament:Ultrafuse PP] -inherits = Ultrafuse ABS -filament_density = 0.91 -filament_colour = #F0F0F0 -first_layer_temperature = 240 -temperature = 240 -first_layer_bed_temperature = 80 -bed_temperature = 70 -min_fan_speed = 100 -max_fan_speed = 100 -bridge_fan_speed = 100 -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -fan_below_layer_time = 60 -slowdown_below_layer_time = 20 -min_print_speed = 10 -filament_type = PP -filament_max_volumetric_speed = 2.5 -filament_notes = "Material Description\nUltrafuse PP is high-performance thermoplastic with low density, high elasticity and high resistance to fatigue. The mechanical properties make it an ideal material for 3D-printing applications which have to endure high stress or strain. The filament has high chemical resistance and a high isolation value. PP is one of the most used materials in the world, due to its versatility and ability to engineer lightweight tough parts.\n\nPrinting Recommendations:\nApply PP tape or Magigoo PP adhesive to the buildplate for optimal adhesion." - -[filament:Ultrafuse PP-GF30] -inherits = Ultrafuse PP -filament_density = 1.07 -filament_colour = #404040 -first_layer_temperature = 260 -temperature = 250 -first_layer_bed_temperature = 90 -bed_temperature = 40 -min_fan_speed = 40 -max_fan_speed = 75 -fan_always_on = 1 -fan_below_layer_time = 30 -slowdown_below_layer_time = 15 -min_print_speed = 15 -filament_notes = "Ultrafuse PP GF30 is polypropylene, reinforced with 30% glass fiber content. The fibers in this compound are specially designed for 3D-printing filaments and are compatible with a wide range of standard FFF 3D-printers. The extreme stiffness makes this material highly suitable for demanding applications. Other key properties of PPGF30 are high heat resistance and improved UV-resistance. All these excellent properties make this filament highly suitable in an industrial environment.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nApply PP strapping tape or PPGF adhesive to a clean build plate for optimal adhesion." - -[filament:Ultrafuse TPC-45D] -inherits = *FLEX* -filament_vendor = BASF -extrusion_multiplier = 1 -filament_density = 1.15 -filament_colour = #0035EC -first_layer_temperature = 235 -temperature = 235 -first_layer_bed_temperature = 60 -bed_temperature = 60 -min_fan_speed = 10 -max_fan_speed = 50 -bridge_fan_speed = 80 -fan_below_layer_time = 30 -slowdown_below_layer_time = 15 -min_print_speed = 15 -fan_always_on = 1 -cooling = 1 -filament_max_volumetric_speed = 1.2 -filament_notes = "Material Description\nTPC 45D is a flexible, shore 45D, rubber-like Thermoplastic Copolyester Elastomer (TPE-C), which is derived from rapeseed oil and combines the best properties of elastomers (rubbers) and polyesters. The material delivers excellent adhesion in the Z-direction, meaning that the printed layers do not detach - even with extreme deformation.\n\nPrinting Recommendations:\nApply Magigoo Flex to a clean build plate to improve adhesion." - -[filament:Ultrafuse TPU-64D] -inherits = Ultrafuse TPC-45D -filament_density = 1.16 -first_layer_temperature = 230 -temperature = 225 -first_layer_bed_temperature = 40 -bed_temperature = 40 -min_fan_speed = 20 -max_fan_speed = 100 -filament_notes = "Material Description\nUltrafuse® TPU 64D is the hardest elastomer in BASF Forward AM’s flexible productline. The material shows a relatively high rigidity while maintaining a certain flexibility. This filament is the perfect match for industrial applications requiring rigid parts being resistant to impact, wear and tear. Due to its property profile, the material can be used as an alternative for parts made from ABS and rubbers. Ultrafuse® TPU 64D is easy to print on direct drive and bowden style printers and is compatible with soluble BVOH support to realize the most complex geometries.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." - -[filament:Ultrafuse TPU-85A] -inherits = Ultrafuse TPU-64D -filament_density = 1.11 -first_layer_temperature = 225 -temperature = 220 -filament_notes = "Material Description\nUltrafuse® TPU 85A comes in its natural white color. Chemical properties (e.g. resistance against particular substances) and tolerance for solvents can be made available, if these factors are relevant for a specific application. Generally, these properties correspond to publicly available data on polyether based TPUs. This material is not FDA conform. Good flexibility at low temperature, good wear performance and good damping behavior are the key features of Ultrafuse® TPU 85A.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." - -[filament:Ultrafuse TPU-95A] -inherits = Ultrafuse TPU-85A -filament_density = 1.14 -first_layer_temperature = 230 -temperature = 225 -filament_notes = "Material Description\nUltrafuse® TPU 95A comes with a well-balanced profile of flexibility and durability. On top of that, it allows for easier and faster printing then softer TPU grades. Parts printed with Ultrafuse® TPU 95A show a high elongation, good impact resistance, excellent layer adhesion and a good resistance to oils and common industrially used chemicals. Due to its good printing behavior, Ultrafuse® TPU 95A is a good choice for starting printing flexible materials on both direct drive and bowden style printers.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." - -[filament:Ultrafuse rPET] -inherits = Ultrafuse PET -filament_density = 1.27 -filament_colour = #9DC5FF -first_layer_temperature = 235 -temperature = 235 -first_layer_bed_temperature = 80 -bed_temperature = 75 -min_fan_speed = 50 -max_fan_speed = 100 -fan_below_layer_time = 15 -filament_notes = "Material Description\nPET is mainly known by the well-known PET bottle material. This recycled has a natural transparent blueish look. It has excellent 3D printing properties and good mechanical characteristics." - -[filament:Ultrafuse Metal] -inherits = *ABSC* -filament_vendor = BASF -filament_density = 4.5 -extrusion_multiplier = 1.08 -first_layer_temperature = 250 -first_layer_bed_temperature = 100 -temperature = 250 -bed_temperature = 100 -min_fan_speed = 0 -max_fan_speed = 0 -bridge_fan_speed = 0 -cooling = 0 -fan_always_on = 0 -filament_max_volumetric_speed = 4 -filament_type = METAL -compatible_printers_condition = nozzle_diameter[0]>=0.4 -filament_colour = #FFFFFF - -[filament:Polymaker PC-Max] -inherits = *ABS* -filament_vendor = Polymaker -filament_cost = 77.3 -filament_density = 1.20 -filament_type = PC -bed_temperature = 115 -filament_colour = #FFF2EC -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 0 - -[filament:PrimaSelect PVA+] -inherits = *PLA* -filament_vendor = PrimaSelect -filament_cost = 122.1 -filament_density = 1.23 -cooling = 0 -fan_always_on = 0 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = PVA -first_layer_temperature = 195 -temperature = 195 - -[filament:Prusa ABS] -inherits = *ABSC* -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 230 - -[filament:Prusa HIPS] -inherits = *Generic HIPS* -filament_vendor = Made for Prusa -first_layer_temperature = 220 -temperature = 220 - -[filament:Generic HIPS] -inherits = *ABS* -filament_vendor = Generic -filament_cost = 27.3 -filament_density = 1.04 -bridge_fan_speed = 50 -cooling = 1 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 10 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = HIPS -first_layer_temperature = 230 -max_fan_speed = 20 -min_fan_speed = 20 -temperature = 230 -compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) - -[filament:Prusa PETG] -inherits = *PET* -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 - -[filament:Verbatim PETG] -inherits = *PET* -filament_vendor = Verbatim -filament_cost = 27.90 -filament_density = 1.27 -filament_spool_weight = 235 - -[filament:Prusament PETG] -inherits = *PET* -filament_vendor = Prusa Polymers -first_layer_temperature = 240 -temperature = 250 -filament_cost = 36.29 -filament_density = 1.27 -filament_spool_weight = 201 -filament_type = PETG - -[filament:Prusa PLA] -inherits = *PLA* -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:Eolas Prints PLA] -inherits = *PLA* -filament_vendor = Eolas Prints -filament_cost = 23.50 -filament_density = 1.24 -filament_spool_weight = 0 -filament_colour = #4D9398 -temperature = 208 - -[filament:Eolas Prints PLA Matte] -inherits = Eolas Prints PLA -filament_cost = 25.50 -temperature = 212 - -[filament:Eolas Prints INGEO 850] -inherits = Eolas Prints PLA -filament_cost = 25.90 -temperature = 210 - -[filament:Eolas Prints INGEO 870] -inherits = Eolas Prints PLA -filament_cost = 25.90 -temperature = 215 -first_layer_bed_temperature = 68 -first_layer_temperature = 220 -bed_temperature = 65 - -[filament:Eolas Prints PETG] -inherits = *PET* -filament_vendor = Eolas Prints -filament_cost = 29.90 -filament_density = 1.27 -filament_spool_weight = 0 -filament_colour = #4D9398 -temperature = 240 -first_layer_bed_temperature = 85 -first_layer_temperature = 235 -bed_temperature = 90 - -[filament:Eolas Prints PETG - UV Resistant] -inherits = Eolas Prints PETG -filament_cost = 35.90 -temperature = 237 -first_layer_temperature = 232 - -[filament:Eolas Prints TPU 93A] -inherits = *FLEX* -filament_vendor = Eolas Prints -filament_cost = 34.99 -filament_density = 1.21 -filament_colour = #4D9398 -filament_max_volumetric_speed = 1.2 -temperature = 235 -first_layer_bed_temperature = 30 -bed_temperature = 30 -filament_retract_length = 0 -extrusion_multiplier = 1.16 - -[filament:Fiberlogy Easy PLA] -inherits = *PLA* -filament_vendor = Fiberlogy -filament_cost = 20 -filament_density = 1.24 -first_layer_temperature = 220 -temperature = 220 -filament_spool_weight = 330 - -[filament:Fiberlogy Easy PET-G] -inherits = *PET* -filament_vendor = Fiberlogy -filament_spool_weight = 330 -filament_cost = 20 -filament_density = 1.27 -first_layer_bed_temperature = 80 -bed_temperature = 80 -first_layer_temperature = 235 -temperature = 235 -min_fan_speed = 15 -max_fan_speed = 30 -bridge_fan_speed = 60 -disable_fan_first_layers = 5 -full_fan_speed_layer = 5 -slowdown_below_layer_time = 15 - -[filament:Fiberlogy ASA] -inherits = *ABS* -filament_vendor = Fiberlogy -filament_cost = 33 -filament_density = 1.07 -filament_spool_weight = 330 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 10 -max_fan_speed = 15 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 15 -first_layer_temperature = 260 -temperature = 260 -first_layer_bed_temperature = 100 -bed_temperature = 100 -filament_type = ASA -fan_below_layer_time = 30 -disable_fan_first_layers = 5 - -[filament:Fiberlogy Easy ABS] -inherits = Fiberlogy ASA -filament_cost = 22.67 -filament_density = 1.09 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 10 -max_fan_speed = 15 -min_print_speed = 15 -slowdown_below_layer_time = 15 -first_layer_temperature = 250 -temperature = 250 -first_layer_bed_temperature = 100 -bed_temperature = 100 -filament_type = ABS -fan_below_layer_time = 25 -disable_fan_first_layers = 5 - -[filament:Fiberlogy CPE HT] -inherits = *PET* -filament_vendor = Fiberlogy -filament_cost = 42.67 -filament_density = 1.18 -extrusion_multiplier = 0.98 -filament_spool_weight = 330 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 0 -max_fan_speed = 0 -bridge_fan_speed = 50 -min_print_speed = 15 -first_layer_temperature = 275 -temperature = 275 -first_layer_bed_temperature = 105 -bed_temperature = 105 -filament_type = CPE -fan_below_layer_time = 20 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 5 - -[filament:Fiberlogy PCTG] -inherits = Fiberlogy CPE HT -filament_cost = 29.41 -filament_density = 1.23 -extrusion_multiplier = 1 -min_fan_speed = 10 -max_fan_speed = 15 -first_layer_temperature = 265 -temperature = 265 -first_layer_bed_temperature = 90 -bed_temperature = 90 -filament_type = PCTG - -[filament:Fiberlogy FiberFlex 40D] -inherits = *FLEX* -filament_vendor = Fiberlogy -fan_always_on = 1 -filament_max_volumetric_speed = 1.5 -extrusion_multiplier = 1.12 -first_layer_temperature = 230 -first_layer_bed_temperature = 60 -temperature = 230 -bed_temperature = 60 -bridge_fan_speed = 75 -min_fan_speed = 25 -max_fan_speed = 75 -filament_cost = 39.41 -filament_density = 1.16 -disable_fan_first_layers = 5 -full_fan_speed_layer = 5 -min_print_speed = 15 -cooling = 1 -filament_spool_weight = 330 - -[filament:Fiberlogy MattFlex 40D] -inherits = Fiberlogy FiberFlex 40D -filament_vendor = Fiberlogy -fan_always_on = 1 -filament_max_volumetric_speed = 1.35 -extrusion_multiplier = 1.1 -filament_cost = 49.11 - -[filament:Fiberlogy FiberFlex 30D] -inherits = Fiberlogy FiberFlex 40D -filament_max_volumetric_speed = 1.2 -extrusion_multiplier = 1.15 -first_layer_temperature = 240 -temperature = 240 -min_fan_speed = 25 -max_fan_speed = 60 -filament_density = 1.07 - -[filament:Fiberlogy FiberSatin] -inherits = Fiberlogy Easy PLA -first_layer_temperature = 215 -temperature = 215 -extrusion_multiplier = 1 -filament_density = 1.2 -filament_cost = 32.35 - -[filament:Fiberlogy FiberSilk] -inherits = Fiberlogy FiberSatin -first_layer_temperature = 230 -temperature = 230 -extrusion_multiplier = 0.97 -filament_density = 1.22 -filament_cost = 32.35 - -[filament:Fiberlogy FiberWood] -inherits = Fiberlogy Easy PLA -first_layer_temperature = 185 -temperature = 185 -extrusion_multiplier = 1 -filament_density = 1.23 -filament_cost = 38.66 - -[filament:Fiberlogy HD PLA] -inherits = Fiberlogy Easy PLA -first_layer_temperature = 230 -temperature = 230 -extrusion_multiplier = 1 -filament_density = 1.24 -filament_cost = 30.59 - -[filament:Fiberlogy PLA Mineral] -inherits = Fiberlogy Easy PLA -first_layer_temperature = 195 -temperature = 190 -extrusion_multiplier = 0.98 -filament_density = 1.38 -filament_cost = 37.64 - -[filament:Fiberlogy Impact PLA] -inherits = Fiberlogy HD PLA -filament_density = 1.22 -filament_cost = 27.65 - -[filament:Fiberlogy Nylon PA12] -inherits = Fiberlogy ASA -filament_type = PA -filament_density = 1.01 -filament_cost = 48 -first_layer_bed_temperature = 105 -bed_temperature = 105 -first_layer_temperature = 265 -temperature = 265 -min_fan_speed = 10 -max_fan_speed = 15 -fan_below_layer_time = 20 -bridge_fan_speed = 30 -fan_always_on = 0 - -[filament:Fiberlogy Nylon PA12+CF15] -inherits = Fiberlogy Nylon PA12 -extrusion_multiplier = 0.97 -filament_density = 1.07 -filament_cost = 87.5 -first_layer_bed_temperature = 105 -bed_temperature = 105 -first_layer_temperature = 265 -temperature = 265 -min_fan_speed = 10 -max_fan_speed = 15 -fan_below_layer_time = 20 -bridge_fan_speed = 30 -fan_always_on = 0 - -[filament:Fiberlogy Nylon PA12+GF15] -inherits = Fiberlogy Nylon PA12+CF15 -filament_density = 1.13 - -[filament:Fiberlogy PP] -inherits = *ABS* -filament_vendor = Fiberlogy -filament_cost = 36.67 -filament_density = 1.05 -extrusion_multiplier = 1.05 -filament_spool_weight = 330 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 0 -max_fan_speed = 25 -bridge_fan_speed = 70 -min_print_speed = 15 -slowdown_below_layer_time = 15 -first_layer_temperature = 245 -temperature = 245 -first_layer_bed_temperature = 0 -bed_temperature = 0 -filament_type = PP -fan_below_layer_time = 100 -disable_fan_first_layers = 5 -filament_max_volumetric_speed = 5 - -[filament:Filament PM PLA] -inherits = *PLA* -filament_vendor = Filament PM -filament_cost = 27.82 -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:AmazonBasics PLA] -inherits = *PLA* -filament_vendor = AmazonBasics -filament_cost = 25.4 -filament_density = 1.24 - -[filament:Overture PLA] -inherits = *PLA* -filament_vendor = Overture -filament_cost = 22 -filament_density = 1.24 -filament_spool_weight = 235 - -[filament:Hatchbox PLA] -inherits = *PLA* -filament_vendor = Hatchbox -filament_cost = 25.4 -filament_density = 1.27 -filament_spool_weight = 245 - -[filament:Esun PLA] -inherits = *PLA* -filament_vendor = Esun -filament_cost = 25.4 -filament_density = 1.24 -filament_spool_weight = 265 - -[filament:Das Filament PLA] -inherits = *PLA* -filament_vendor = Das Filament -filament_cost = 25.4 -filament_density = 1.24 - -[filament:EUMAKERS PLA] -inherits = *PLA* -filament_vendor = EUMAKERS -filament_cost = 25.4 -filament_density = 1.24 - -[filament:Floreon3D PLA] -inherits = *PLA* -filament_vendor = Floreon3D -filament_cost = 25.4 -filament_density = 1.24 - -[filament:Prusament PLA] -inherits = *PLA* -filament_vendor = Prusa Polymers -temperature = 215 -filament_cost = 36.29 -filament_density = 1.24 -filament_spool_weight = 201 -filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" - -[filament:Prusament PVB] -inherits = *PLA* -filament_vendor = Prusa Polymers -temperature = 215 -bed_temperature = 75 -first_layer_bed_temperature = 75 -filament_cost = 60.48 -filament_density = 1.09 -filament_spool_weight = 201 -filament_max_volumetric_speed = 8 -filament_type = PVB -filament_soluble = 1 -filament_colour = #FFFF6F -slowdown_below_layer_time = 20 - -[filament:Fillamentum Flexfill 98A] -inherits = *FLEX* -filament_vendor = Fillamentum -filament_cost = 82.26 -filament_density = 1.23 -filament_spool_weight = 230 -extrusion_multiplier = 1.1 -filament_max_volumetric_speed = 1.5 -fan_always_on = 1 -cooling = 0 -max_fan_speed = 60 -min_fan_speed = 60 -disable_fan_first_layers = 4 -full_fan_speed_layer = 6 - -[filament:ColorFabb VarioShore TPU] -inherits = Fillamentum Flexfill 98A -filament_vendor = ColorFabb -filament_colour = #BBBBBB -filament_cost = 71.35 -filament_density = 1.22 -filament_spool_weight = 0 -extrusion_multiplier = 0.85 -first_layer_temperature = 220 -temperature = 220 - -[filament:Taulman Bridge] -inherits = *common* -filament_vendor = Taulman -filament_cost = 40 -filament_density = 1.13 -bed_temperature = 110 -bridge_fan_speed = 40 -cooling = 0 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -filament_colour = #DEE0E6 -filament_soluble = 0 -filament_type = PA -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 260 -max_fan_speed = 0 -min_fan_speed = 0 -filament_max_volumetric_speed = 6 - -[filament:Fillamentum Nylon FX256] -inherits = *common* -filament_vendor = Fillamentum -filament_cost = 56.99 -filament_density = 1.01 -filament_spool_weight = 230 -bed_temperature = 90 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 6 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 20 -filament_colour = #DEE0E6 -filament_max_volumetric_speed = 6 -filament_soluble = 0 -filament_type = PA -first_layer_bed_temperature = 90 -first_layer_temperature = 250 -max_fan_speed = 0 -min_fan_speed = 0 -temperature = 250 - -[filament:Fiberthree F3 PA Pure Pro] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 200.84 -filament_density = 1.2 -bed_temperature = 90 -first_layer_bed_temperature = 90 -first_layer_temperature = 285 -temperature = 285 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 -filament_max_volumetric_speed = 5 -filament_soluble = 0 -filament_type = PA -max_fan_speed = 20 -min_fan_speed = 20 - -[filament:Fiberthree F3 PA-CF Pro] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 208.1 -filament_density = 1.25 -bed_temperature = 90 -first_layer_bed_temperature = 90 -first_layer_temperature = 285 -temperature = 285 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 -filament_max_volumetric_speed = 5 -filament_soluble = 0 -filament_type = PA -max_fan_speed = 0 -min_fan_speed = 0 - -[filament:Fiberthree F3 PA-GF Pro] -inherits = Fiberthree F3 PA-CF Pro -filament_vendor = Fiberthree -filament_cost = 205.68 -filament_density = 1.27 -fan_always_on = 1 -max_fan_speed = 15 -min_fan_speed = 15 - -[filament:Taulman T-Glase] -inherits = *PET* -filament_vendor = Taulman -filament_cost = 40 -filament_density = 1.27 -bridge_fan_speed = 40 -cooling = 0 -fan_always_on = 0 -first_layer_bed_temperature = 90 -first_layer_temperature = 240 -max_fan_speed = 5 -min_fan_speed = 0 - -[filament:Verbatim PLA] -inherits = *PLA* -filament_vendor = Verbatim -filament_cost = 42.99 -filament_density = 1.24 -filament_spool_weight = 235 - -[filament:Verbatim BVOH] -inherits = *common* -filament_vendor = Verbatim -filament_cost = 193.58 -filament_density = 1.14 -filament_spool_weight = 235 -bed_temperature = 60 -bridge_fan_speed = 100 -cooling = 0 -disable_fan_first_layers = 1 -extrusion_multiplier = 1 -fan_always_on = 0 -fan_below_layer_time = 100 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = PVA -first_layer_bed_temperature = 60 -first_layer_temperature = 215 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 210 - -[filament:Verbatim PP] -inherits = *common* -filament_vendor = Verbatim -filament_cost = 72 -filament_density = 0.89 -filament_spool_weight = 235 -bed_temperature = 100 -bridge_fan_speed = 100 -cooling = 1 -disable_fan_first_layers = 2 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 100 -filament_colour = #DEE0E6 -filament_max_volumetric_speed = 5 -filament_type = PP -first_layer_bed_temperature = 100 -first_layer_temperature = 220 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 220 - -[filament:FormFutura Centaur PP] -inherits = *common* -filament_vendor = FormFutura -filament_cost = 70 -filament_density = 0.89 -filament_spool_weight = 212 -bridge_fan_speed = 100 -cooling = 1 -disable_fan_first_layers = 2 -extrusion_multiplier = 1.05 -fan_always_on = 1 -fan_below_layer_time = 100 -filament_colour = #DEE0E6 -filament_max_volumetric_speed = 4 -filament_type = PP -first_layer_bed_temperature = 85 -bed_temperature = 85 -first_layer_temperature = 235 -max_fan_speed = 70 -min_fan_speed = 70 -temperature = 235 -filament_wipe = 0 +# Generic filament profile templates + +[vendor] +name = Templates +config_version = 1.0.0 +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Templates/ +templates_profile = 1 + +## Generic filament profiles + +# Print profiles for the Prusa Research printers. + +[filament:*common*] +cooling = 1 +compatible_printers = +compatible_printers_condition = +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_loading_speed = 14 +filament_loading_speed_start = 19 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_toolchange_delay = 0 +filament_cooling_moves = 1 +filament_cooling_initial_speed = 3 +filament_cooling_final_speed = 2 +filament_load_time = 0 +filament_unload_time = 0 +filament_ramming_parameters = "130 120 2.70968 2.93548 3.32258 3.83871 4.58065 5.54839 6.51613 7.35484 7.93548 8.16129| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" +filament_minimal_purge_on_wipe_tower = 0 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 10 +slowdown_below_layer_time = 10 +start_filament_gcode = + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF8000 +filament_max_volumetric_speed = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:*PET*] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_max_volumetric_speed = 0 +filament_type = PETG +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #FFF2EC +filament_max_volumetric_speed = 0 +filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*ABSC*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 1 +disable_fan_first_layers = 4 +fan_always_on = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +filament_colour = #FFF2EC +filament_max_volumetric_speed = 0 +filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 15 +min_fan_speed = 15 +min_print_speed = 15 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bed_temperature = 50 +bridge_fan_speed = 80 +cooling = 0 +disable_fan_first_layers = 3 +extrusion_multiplier = 1.15 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #008000 +filament_max_volumetric_speed = 1.8 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +temperature = 240 + +[filament:ColorFabb bronzeFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.12 +filament_cost = 80.65 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #804040 + +[filament:ColorFabb steelFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 80.65 +filament_density = 3.13 +filament_spool_weight = 236 +filament_colour = #808080 + +[filament:ColorFabb copperFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 80.65 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #82603E + +[filament:ColorFabb HT] +inherits = *PET* +filament_vendor = ColorFabb +bed_temperature = 100 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_cost = 65.66 +filament_density = 1.18 +filament_spool_weight = 236 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* +filament_vendor = ColorFabb +filament_cost = 54.84 +filament_density = 1.24 +filament_spool_weight = 236 + +[filament:ColorFabb woodFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 78.63 +filament_density = 1.15 +filament_spool_weight = 236 +filament_colour = #dfc287 +first_layer_temperature = 200 +temperature = 200 + +[filament:ColorFabb corkFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 78.63 +filament_density = 1.18 +filament_spool_weight = 236 +filament_colour = #634d33 +filament_max_volumetric_speed = 6 +first_layer_temperature = 220 +temperature = 220 + +[filament:ColorFabb XT] +inherits = *PET* +filament_vendor = ColorFabb +filament_cost = 62.90 +filament_density = 1.27 +filament_spool_weight = 236 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +filament_vendor = ColorFabb +extrusion_multiplier = 1.05 +filament_cost = 80.65 +filament_density = 1.35 +filament_spool_weight = 236 +filament_colour = #804040 +filament_max_volumetric_speed = 2 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +filament_vendor = ColorFabb +filament_cost = 52.46 +filament_density = 1.2 +filament_spool_weight = 236 +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:ColorFabb nGen flex] +inherits = *FLEX* +filament_vendor = ColorFabb +filament_cost = 58.30 +filament_density = 1 +filament_spool_weight = 236 +bed_temperature = 85 +bridge_fan_speed = 40 +cooling = 1 +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +fan_below_layer_time = 10 +filament_max_volumetric_speed = 5 +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +temperature = 260 + +[filament:Kimya PETG Carbon] +inherits = *PET* +filament_vendor = Kimya +extrusion_multiplier = 1.05 +filament_cost = 150.02 +filament_density = 1.317 +filament_colour = #804040 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 240 + +[filament:Kimya ABS Carbon] +inherits = *ABSC* +filament_vendor = Kimya +filament_cost = 140.34 +filament_density = 1.032 +filament_colour = #804040 +first_layer_temperature = 260 +temperature = 260 + +[filament:Kimya ABS Kevlar] +inherits = Kimya ABS Carbon +filament_vendor = Kimya +filament_density = 1.037 + +[filament:E3D Edge] +inherits = *PET* +filament_vendor = E3D +filament_cost = 56.9 +filament_density = 1.26 +filament_type = EDGE + +[filament:E3D PC-ABS] +inherits = *ABS* +filament_vendor = E3D +filament_cost = 0 +filament_type = PC +filament_density = 1.05 +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum PLA] +inherits = *PLA* +filament_vendor = Fillamentum +filament_cost = 35.48 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Fillamentum ABS] +inherits = *ABSC* +filament_vendor = Fillamentum +filament_cost = 32.4 +filament_density = 1.04 +filament_spool_weight = 230 +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +filament_vendor = Fillamentum +filament_cost = 38.7 +filament_density = 1.07 +filament_spool_weight = 230 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +filament_type = ASA + +[filament:Prusament ASA] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_cost = 42.69 +filament_density = 1.07 +filament_spool_weight = 201 +fan_always_on = 1 +first_layer_temperature = 260 +first_layer_bed_temperature = 100 +temperature = 260 +bed_temperature = 100 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 4 +filament_type = ASA +filament_colour = #FFF2EC + +[filament:Prusament PC Blend] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_cost = 62.36 +filament_density = 1.22 +filament_spool_weight = 201 +fan_always_on = 0 +first_layer_temperature = 275 +first_layer_bed_temperature = 105 +temperature = 275 +bed_temperature = 105 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 20 +disable_fan_first_layers = 4 +fan_below_layer_time = 30 +filament_type = PC +filament_colour = #DEE0E6 + +[filament:Prusament PC Blend Carbon Fiber] +inherits = Prusament PC Blend +filament_cost = 90.73 +filament_density = 1.16 +extrusion_multiplier = 1.04 +first_layer_temperature = 285 +temperature = 285 +disable_fan_first_layers = 4 +fan_below_layer_time = 10 +filament_colour = #BBBBBB + +[filament:Prusament PA11 Carbon Fiber] +inherits = Prusament PC Blend Carbon Fiber +filament_cost = 151.24 +filament_density = 1.11 +filament_type = PA +extrusion_multiplier = 1.05 +first_layer_temperature = 275 +temperature = 285 +first_layer_bed_temperature = 90 +bed_temperature = 105 +fan_below_layer_time = 10 + +[filament:Fillamentum CPE] +inherits = *PET* +filament_vendor = Fillamentum +filament_cost = 56.45 +filament_density = 1.25 +filament_spool_weight = 230 +filament_type = CPE +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +min_fan_speed = 30 +max_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +temperature = 275 + +[filament:Fillamentum Timberfill] +inherits = *PLA* +filament_vendor = Fillamentum +extrusion_multiplier = 1.05 +filament_cost = 68 +filament_density = 1.15 +filament_spool_weight = 230 +filament_colour = #804040 +first_layer_temperature = 190 +temperature = 190 +filament_retract_lift = 0.2 + +[filament:Smartfil Wood] +inherits = *PLA* +filament_vendor = Smart Materials 3D +extrusion_multiplier = 1.05 +filament_cost = 68 +filament_density = 1.58 +filament_colour = #804040 +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic ABS] +inherits = *ABSC* +filament_vendor = Generic +filament_cost = 27.82 +filament_density = 1.04 + +[filament:Esun ABS] +inherits = *ABSC* +filament_vendor = Esun +filament_cost = 27.82 +filament_density = 1.01 +filament_spool_weight = 265 + +[filament:Hatchbox ABS] +inherits = *ABSC* +filament_vendor = Hatchbox +filament_cost = 27.82 +filament_density = 1.04 +filament_spool_weight = 245 + +[filament:Filament PM ABS] +inherits = *ABSC* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Verbatim ABS] +inherits = *ABSC* +filament_vendor = Verbatim +filament_cost = 25.87 +filament_density = 1.05 +filament_spool_weight = 235 + +[filament:Generic PETG] +inherits = *PET* +filament_vendor = Generic +filament_cost = 27.82 +filament_density = 1.27 + +[filament:Extrudr DuraPro ASA] +inherits = Fillamentum ASA +filament_vendor = Extrudr +bed_temperature = 90 +filament_cost = 34.64 +filament_density = 1.05 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" +first_layer_bed_temperature = 90 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 230 + +[filament:Extrudr PETG] +inherits = *PET* +filament_vendor = Extrudr +bed_temperature = 70 +filament_cost = 35.45 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" +first_layer_bed_temperature = 70 +first_layer_temperature = 220 +temperature = 220 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 +full_fan_speed_layer = 0 + +[filament:Extrudr XPETG CF] +inherits = Extrudr PETG +filament_cost = 62.49 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" +first_layer_temperature = 235 +temperature = 235 +filament_spool_weight = 230 + +[filament:Extrudr XPETG Matt] +inherits = Extrudr PETG +filament_cost = 29.99 +filament_density = 1.41 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" +first_layer_temperature = 230 +temperature = 230 + +[filament:Extrudr BioFusion] +inherits = *PLA* +filament_vendor = Extrudr +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_cost = 31.23 +filament_density = 1.25 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" +first_layer_temperature = 220 +temperature = 220 +max_fan_speed = 45 +min_fan_speed = 25 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr Flax] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 50.91 +filament_density = 1.45 +filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" +first_layer_temperature = 190 +temperature = 190 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 50.91 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?ignorechildren=1&material=106" +first_layer_temperature = 208 +temperature = 208 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC Pro] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 56.23 +filament_density = 1.35 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" +temperature = 215 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr GreenTEC Pro Carbon] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 62.49 +filament_density = 1.2 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" +first_layer_temperature = 225 +max_fan_speed = 80 +min_fan_speed = 30 +temperature = 225 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr PLA NX1] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 22.76 +filament_density = 1.24 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 0 +max_fan_speed = 90 +min_fan_speed = 30 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr PLA NX2] +inherits = Extrudr PLA NX1 +filament_cost = 23.63 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" + +[filament:Extrudr Flex Hard] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.2 +filament_max_volumetric_speed = 3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex Medium] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.19 +filament_max_volumetric_speed = 3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=117" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex SemiSoft] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.18 +filament_max_volumetric_speed = 1.8 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=116" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:addnorth Adamant S1] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_cost = +filament_density = 1.22 +temperature = 250 +bed_temperature = 50 +first_layer_temperature = 245 +first_layer_bed_temperature = 50 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 40 +max_fan_speed = 70 +bridge_fan_speed = 60 +filament_max_volumetric_speed = 1.7 + +[filament:addnorth Adura X] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +filament_type = PA +extrusion_multiplier = 0.98 +bed_temperature = 105 +first_layer_bed_temperature = 105 +first_layer_temperature = 265 +temperature = 270 +fan_always_on = 0 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +min_print_speed = 20 +fan_below_layer_time = 10 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 + +[filament:addnorth E-PLA] +inherits = *PLA* +filament_vendor = addnorth +filament_cost = 24.99 +filament_density = 1.24 +extrusion_multiplier = 0.98 +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_spool_weight = 0 + +[filament:addnorth ESD-PETG] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 245 +temperature = 265 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 35 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 8 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 + +[filament:addnorth OBC Polyethylene] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_cost = 82 +filament_density = 1.22 +temperature = 200 +bed_temperature = 100 +first_layer_temperature = 195 +first_layer_bed_temperature = 100 +slowdown_below_layer_time = 5 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 30 +bridge_fan_speed = 40 +min_print_speed = 20 +filament_spool_weight = 0 +filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." + +[filament:addnorth PETG] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 250 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 40 +bridge_fan_speed = 50 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 15 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:addnorth Rigid X] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 85 +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +temperature = 260 +fan_always_on = 1 +min_fan_speed = 20 +max_fan_speed = 60 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +fan_below_layer_time = 20 +min_print_speed = 20 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 +filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." + +[filament:addnorth Textura] +inherits = *PLA* +filament_vendor = addnorth +filament_cost = 24.99 +filament_density = 1.24 +extrusion_multiplier = 0.95 +temperature = 215 +bed_temperature = 65 +first_layer_temperature = 215 +first_layer_bed_temperature = 65 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 60 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 15 +min_print_speed = 20 +filament_spool_weight = 0 + +[filament:Filamentworld ABS] +inherits = *ABSC* +filament_vendor = Filamentworld +filament_cost = 24.9 +filament_density = 1.04 +temperature = 230 +bed_temperature = 95 +first_layer_temperature = 240 +first_layer_bed_temperature = 100 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 20 +disable_fan_first_layers = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 15 +bridge_fan_speed = 20 + +[filament:Filamentworld PETG] +inherits = *PET* +filament_vendor = Filamentworld +filament_cost = 34.9 +filament_density = 1.27 +bed_temperature = 70 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 235 +fan_always_on = 1 +min_fan_speed = 25 +max_fan_speed = 55 +bridge_fan_speed = 55 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 35 +disable_fan_first_layers = 2 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:Filamentworld PLA] +inherits = *PLA* +filament_vendor = Filamentworld +filament_cost = 24.9 +filament_density = 1.24 +temperature = 205 +bed_temperature = 55 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 10 +filament_spool_weight = 0 +min_print_speed = 20 + +[filament:Filament PM PETG] +inherits = *PET* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Generic PLA] +inherits = *PLA* +filament_vendor = Generic +filament_cost = 25.4 +filament_density = 1.24 + +[filament:3D-Fuel Standard PLA] +inherits = *PLA* +filament_vendor = 3D-Fuel +filament_cost = 22.14 +filament_density = 1.24 +first_layer_temperature = 210 +temperature = 200 + +[filament:3D-Fuel EasiPrint PLA] +inherits = 3D-Fuel Standard PLA +filament_cost = 30.44 + +[filament:3D-Fuel Pro PLA] +inherits = *PLA* +filament_vendor = 3D-Fuel +filament_cost = 26.57 +filament_density = 1.22 +first_layer_temperature = 220 +temperature = 215 + +[filament:3D-Fuel Buzzed] +inherits = 3D-Fuel Standard PLA +filament_cost = 44.27 +filament_retract_lift = 0 +first_layer_temperature = 210 +temperature = 195 + +[filament:3D-Fuel Wound up] +inherits = 3D-Fuel Buzzed +filament_cost = 44.27 +filament_retract_lift = nil +first_layer_temperature = 215 +temperature = 210 + +[filament:3D-Fuel Workday ABS] +inherits = *ABSC* +filament_vendor = 3D-Fuel +filament_cost = 23.25 +filament_density = 1.04 + +[filament:Jessie PLA] +inherits = *PLA* +filament_vendor = Printed Solid +filament_cost = 21 +filament_density = 1.24 + +[filament:Jessie PETG] +inherits = *PET* +filament_vendor = Printed Solid +filament_cost = 22 +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 85 +temperature = 245 +bed_temperature = 90 + +[filament:Devil Design PLA] +inherits = *PLA* +filament_vendor = Devil Design +filament_cost = 20.99 +filament_density = 1.24 +filament_spool_weight = 250 + +[filament:Devil Design PETG] +inherits = *PET* +filament_vendor = Devil Design +filament_cost = 20.99 +filament_density = 1.23 +filament_spool_weight = 250 +first_layer_temperature = 230 +first_layer_bed_temperature = 85 +temperature = 230 +bed_temperature = 90 + +[filament:Spectrum PLA] +inherits = *PLA* +filament_vendor = Spectrum +filament_cost = 21.50 +filament_density = 1.24 + +[filament:Generic FLEX] +inherits = *FLEX* +filament_vendor = Generic +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil + +[filament:Fillamentum Flexfill 92A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_cost = 33.99 +filament_density = 1.20 +filament_spool_weight = 230 +filament_max_volumetric_speed = 1.2 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:AmazonBasics TPU] +inherits = *FLEX* +filament_vendor = AmazonBasics +fan_always_on = 1 +filament_max_volumetric_speed = 1.8 +extrusion_multiplier = 1.14 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 235 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_retract_before_travel = 3 +filament_cost = 19.99 +filament_density = 1.21 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:SainSmart TPU] +inherits = *FLEX* +filament_vendor = SainSmart +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.05 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_retract_before_travel = 3 +filament_cost = 32.99 +filament_density = 1.21 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:NinjaTek NinjaFlex TPU] +inherits = *FLEX* +filament_vendor = NinjaTek +fan_always_on = 1 +filament_max_volumetric_speed = 1.2 +extrusion_multiplier = 1.15 +first_layer_temperature = 238 +first_layer_bed_temperature = 50 +temperature = 238 +bed_temperature = 50 +bridge_fan_speed = 75 +max_fan_speed = 60 +min_fan_speed = 60 +filament_cost = 85 +filament_density = 1.19 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +min_print_speed = 10 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:NinjaTek Cheetah TPU] +inherits = NinjaTek NinjaFlex TPU +filament_retract_length = 1.5 +filament_density = 1.22 +filament_max_volumetric_speed = 4 +extrusion_multiplier = 1.05 +filament_retract_speed = 45 +filament_deretract_speed = 25 +first_layer_temperature = 240 +temperature = 240 + +[filament:Filatech FilaFlex40] +inherits = *FLEX* +filament_vendor = Filatech +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.1 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 50 +min_fan_speed = 50 +filament_cost = 84.68 +filament_density = 1.22 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:Filatech FilaFlex30] +inherits = Filatech FilaFlex40 +temperature = 225 +filament_density = 1.15 +extrusion_multiplier = 1.1 +filament_cost = + +[filament:Filatech FilaFlex55] +inherits = Filatech FilaFlex40 +temperature = 230 +filament_density = 1.18 +bed_temperature = 60 +fan_always_on = 0 +fan_below_layer_time = 60 +filament_cost = +first_layer_temperature = 235 +extrusion_multiplier = 1 + +[filament:Filatech TPU] +inherits = Filatech FilaFlex40 +first_layer_temperature = 230 +filament_density = 1.2 +fan_below_layer_time = 60 +max_fan_speed = 80 +min_fan_speed = 80 +fan_always_on = 1 +temperature = 235 + +[filament:Filatech ABS] +inherits = *ABSC* +filament_vendor = Filatech +filament_cost = +extrusion_multiplier = 1 +filament_density = 1.05 + +[filament:Filatech FilaCarbon] +inherits = *ABSC* +filament_vendor = Filatech +filament_cost = +extrusion_multiplier = 0.95 +filament_density = 1.1 +first_layer_bed_temperature = 100 +bed_temperature = 100 + +[filament:Filatech FilaPLA] +inherits = *PLA* +filament_vendor = Filatech +filament_cost = +filament_density = 1.3 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 55 + +[filament:Filatech PLA] +inherits = *PLA* +filament_vendor = Filatech +filament_cost = +filament_density = 1.25 +first_layer_temperature = 215 +temperature = 210 + +[filament:Filatech PLA+] +inherits = Filatech PLA +filament_density = 1.24 + +[filament:Filatech FilaTough] +inherits = Filatech ABS +filament_cost = +extrusion_multiplier = 0.95 +filament_density = 1.29 +first_layer_temperature = 245 +first_layer_bed_temperature = 80 +temperature = 240 +bed_temperature = 90 +cooling = 0 + +[filament:Filatech HIPS] +inherits = Prusa HIPS +filament_vendor = Filatech +filament_density = 1.07 +filament_spool_weight = +first_layer_temperature = 230 +first_layer_bed_temperature = 100 +temperature = 225 +bed_temperature = 100 + +[filament:Filatech PA] +inherits = *ABSC* +filament_vendor = Filatech +filament_density = 1.1 +first_layer_temperature = 275 +first_layer_bed_temperature = 105 +temperature = 275 +bed_temperature = 105 +fan_always_on = 0 +cooling = 0 +bridge_fan_speed = 25 +filament_type = PA + +[filament:Filatech PC] +inherits = Filatech PA +filament_density = 1.2 +filament_type = PC + +[filament:Filatech PC-ABS] +inherits = Filatech PC +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_density = 1.08 +filament_type = PC +fan_always_on = 0 +cooling = 1 +extrusion_multiplier = 0.95 +disable_fan_first_layers = 6 + +[filament:Filatech PETG] +inherits = *PET* +filament_vendor = Filatech +filament_cost = +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 75 +temperature = 245 +bed_temperature = 80 +extrusion_multiplier = 0.95 +fan_always_on = 0 + +[filament:Filatech Wood-PLA] +inherits = Filatech PLA +filament_density = 1.05 +first_layer_temperature = 210 + +[filament:Ultrafuse PET] +inherits = *PET* +filament_vendor = BASF +filament_density = 1.33 +filament_colour = #F7F7F7 +first_layer_temperature = 220 +first_layer_bed_temperature = 70 +temperature = 215 +bed_temperature = 70 +fan_below_layer_time = 10 +min_fan_speed = 75 +max_fan_speed = 100 +bridge_fan_speed = 100 +filament_type = PET +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +filament_notes = "Material Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PRO1] +inherits = Prusament PLA +filament_vendor = BASF +filament_cost = +filament_density = 1.25 +filament_spool_weight = 0 +filament_colour = #FFFFFF +filament_notes = "Material Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate." + +[filament:Ultrafuse ABS] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 1.04 +min_fan_speed = 10 +max_fan_speed = 20 +bed_temperature = 100 +disable_fan_first_layers = 3 +filament_colour = #FFFFFF +filament_notes = "Material Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion." + +[filament:Ultrafuse ABS Fusion+] +inherits = Ultrafuse ABS +filament_density = 1.08 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +filament_colour = #FFF8D9 +filament_notes = "Material Description\nABS Fusion+ made with Polyscope XILOY™ 3D is an engineering filament which has been optimized for 3D-printing. This special grade has been developed in collaboration with Polyscope Polymers - renowned for its material solutions in the automotive industry. ABS is a thermoplastic which is used in many applications. Although ABS has been classified as a standard material in 3D-printing it is known to be quite challenging to process. ABS Fusion+ combines the properties of ABS with an improved processability. The filament is based on an ABS grade which can be directly printed on glass without any adhesives or tape and has a higher success rate of prints due to extreme low warping." + +[filament:Ultrafuse ASA] +inherits = Ultrafuse ABS Fusion+ +filament_density = 1.07 +filament_colour = #FFF4CA +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ASA +min_fan_speed = 25 +max_fan_speed = 50 +bridge_fan_speed = 100 +disable_fan_first_layers = 4 +filament_notes = "Material Description\nUltrafuse ASA is a high-performance thermoplastic with similar mechanical properties as ABS. ASA offers additional benefits such as high outdoor weather resistance. The UV resistance, toughness, and rigidity make it an ideal material to 3D-print outdoor fixtures and appliances without losing its properties or color. When also taking into account the high heat resistance and high chemical resistance, this filament is a good choice for many types of applications.\n\nPrinting Recommendations:\nApply Magigoo PC, 3D lac or Dimafix to a clean build plate to improve adhesion." + +[filament:Ultrafuse HIPS] +inherits = Ultrafuse ABS +temperature = 250 +filament_density = 1.02 +filament_type = HIPS +min_fan_speed = 20 +max_fan_speed = 20 +filament_soluble = 1 +filament_notes = "Material Description\nUltrafuse HIPS is a high-quality engineering thermoplastic, which is well known in the 3D-printing industry as a support material for ABS. But this material has additional properties to offer like good impact resistance, good dimensional stability, and easy post-processing. HiPS is a great material to use as a support for ABS because there is a good compatibility between the two materials, and HIPS is an easy breakaway support. Now you have the opportunity to create ABS models with complex geometry. HIPS is easy to post process with glue or with sanding paper." + +[filament:Ultrafuse PA] +inherits = Fillamentum Nylon FX256 +filament_vendor = BASF +filament_density = 1.12 +filament_colour = #ECFAFF +first_layer_temperature = 240 +temperature = 240 +first_layer_bed_temperature = 80 +bed_temperature = 70 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +min_print_speed = 15 +filament_notes = "Material Description\nThe key features of Ultrafuse PA are the high strength and high modulus. Furthermore, Ultrafuse PA shows a good thermal distortion stability.\n\nPrinting Recommendations:\nApply PVA glue, Kapton tape or PA adhesive to a clean buildplate to improve adhesion." + +[filament:Ultrafuse PA6 GF30] +inherits = Ultrafuse PA +filament_density = 1.17 +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_colour = #404040 +fan_always_on = 1 +min_fan_speed = 0 +max_fan_speed = 50 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_notes = "Material Description\nUltrafuse® PA6 GF30 is a unique compound specifically developed for FFF printing. Due to the glass fiber content of 30%, parts tend to warp less. In addition the excellent layer adhesion and its compatibility with the water soluble support Ultrafuse® BVOH make this material the perfect solution to develop industrial applications on an FFF printer.\n\nWith its high wear and chemical resistance, high stiffness and strength, Ultrafuse® PA6 GF30 is perfect for a wide variety of applications in automotive, electronics or transportation.\n\nUltrafuse PA6 GF30 is designed for functional prototyping and demanding applications such as industrial tooling, transportation, electronics, small appliances, sports & leisure\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PA6 GF30 can be printed directly onto a clean build plate. For challenging prints, use Magigoo PA gluestick to improve adhesion." + +[filament:Ultrafuse PAHT-CF15] +inherits = Ultrafuse PA6 GF30 +filament_density = 1.23 +filament_notes = "Material Description\nPAHT CF15 is a high-performance 3D printing filament that opens new application fields in FFF printing. In parallel to its advanced mechanical properties, dimensional stability, and chemical resistance, it has very good processability. It works in any FFF printer with a hardened nozzle. In addition to that, it is compatible with water-soluble support material and HiPS, which allow printing complex geometries that work in challenging environments. PAHT CF15 has high heat resistance up to 130 °C and low moisture absorption.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PAHT-CF can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PC-ABS-FR] +inherits = Ultrafuse ABS +filament_colour = #505050 +filament_density = 1.17 +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_type = PC +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +disable_fan_first_layers = 4 +filament_notes = "Material Description\nUltrafuse® PC/ABS FR Black is a V-0 flame retardant blend of Polycarbonate and ABS – two of the most used thermoplastics for engineering & electrical applications. The combination of these two materials results in a premium material with a mix of the excellent mechanical properties of PC and the comparably low printing temperature of ABS. Combined with a halogen free flame retardant, parts printed with Ultrafuse® PC/ABS FR Black feature great tensile and impact strength, higher thermal resistance than ABS and can fulfill the requirements of the UL94 V-0 standard.\n\nPrinting Recommendations:\nApply Magigoo PC to a clean build plate to improve adhesion." + +[filament:Ultrafuse PET-CF15] +inherits = Ultrafuse PET +filament_density = 1.36 +filament_colour = #404040 +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 75 +bed_temperature = 75 +min_fan_speed = 60 +max_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +fan_below_layer_time = 30 +filament_notes = "Material Description\nPET CF15 is a Carbon Fiber reinforced PET which has precisely tuned material properties, for a wide range of technical applications. The filament is very strong and stiff and has high heat resistance. With its high dimensional stability and low abrasiveness, the filament offers an easy to print experience which allows direct printing on glass or a PEI sheet. It is compatible with HiPS for breakaway support and water soluble support and has an excellent surface finish.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PET-CF15 can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PLA] +inherits = *PLA* +filament_vendor = BASF +filament_density = 1.25 +full_fan_speed_layer = 3 +filament_notes = "Material Description\nPLA is one of the most used materials for 3D printing. Ultrafuse PLA is available in a wide range of colors. The glossy feel often attracts those who print display models or items for household use. Many appreciate the plant-based origin of this material. When properly cooled, PLA has a high maximum printing speed and sharp printed corners. Combining this with low warping of the print makes it a popular plastic for home printers, hobbyists, prototyping and schools.\n\nPrinting Recommendations:\nUltrafuse PLA can be printed directly onto a clean build plate." + +[filament:Ultrafuse PP] +inherits = Ultrafuse ABS +filament_density = 0.91 +filament_colour = #F0F0F0 +first_layer_temperature = 240 +temperature = 240 +first_layer_bed_temperature = 80 +bed_temperature = 70 +min_fan_speed = 100 +max_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 20 +min_print_speed = 10 +filament_type = PP +filament_max_volumetric_speed = 2.5 +filament_notes = "Material Description\nUltrafuse PP is high-performance thermoplastic with low density, high elasticity and high resistance to fatigue. The mechanical properties make it an ideal material for 3D-printing applications which have to endure high stress or strain. The filament has high chemical resistance and a high isolation value. PP is one of the most used materials in the world, due to its versatility and ability to engineer lightweight tough parts.\n\nPrinting Recommendations:\nApply PP tape or Magigoo PP adhesive to the buildplate for optimal adhesion." + +[filament:Ultrafuse PP-GF30] +inherits = Ultrafuse PP +filament_density = 1.07 +filament_colour = #404040 +first_layer_temperature = 260 +temperature = 250 +first_layer_bed_temperature = 90 +bed_temperature = 40 +min_fan_speed = 40 +max_fan_speed = 75 +fan_always_on = 1 +fan_below_layer_time = 30 +slowdown_below_layer_time = 15 +min_print_speed = 15 +filament_notes = "Ultrafuse PP GF30 is polypropylene, reinforced with 30% glass fiber content. The fibers in this compound are specially designed for 3D-printing filaments and are compatible with a wide range of standard FFF 3D-printers. The extreme stiffness makes this material highly suitable for demanding applications. Other key properties of PPGF30 are high heat resistance and improved UV-resistance. All these excellent properties make this filament highly suitable in an industrial environment.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nApply PP strapping tape or PPGF adhesive to a clean build plate for optimal adhesion." + +[filament:Ultrafuse TPC-45D] +inherits = *FLEX* +filament_vendor = BASF +extrusion_multiplier = 1 +filament_density = 1.15 +filament_colour = #0035EC +first_layer_temperature = 235 +temperature = 235 +first_layer_bed_temperature = 60 +bed_temperature = 60 +min_fan_speed = 10 +max_fan_speed = 50 +bridge_fan_speed = 80 +fan_below_layer_time = 30 +slowdown_below_layer_time = 15 +min_print_speed = 15 +fan_always_on = 1 +cooling = 1 +filament_max_volumetric_speed = 1.2 +filament_notes = "Material Description\nTPC 45D is a flexible, shore 45D, rubber-like Thermoplastic Copolyester Elastomer (TPE-C), which is derived from rapeseed oil and combines the best properties of elastomers (rubbers) and polyesters. The material delivers excellent adhesion in the Z-direction, meaning that the printed layers do not detach - even with extreme deformation.\n\nPrinting Recommendations:\nApply Magigoo Flex to a clean build plate to improve adhesion." + +[filament:Ultrafuse TPU-64D] +inherits = Ultrafuse TPC-45D +filament_density = 1.16 +first_layer_temperature = 230 +temperature = 225 +first_layer_bed_temperature = 40 +bed_temperature = 40 +min_fan_speed = 20 +max_fan_speed = 100 +filament_notes = "Material Description\nUltrafuse® TPU 64D is the hardest elastomer in BASF Forward AM’s flexible productline. The material shows a relatively high rigidity while maintaining a certain flexibility. This filament is the perfect match for industrial applications requiring rigid parts being resistant to impact, wear and tear. Due to its property profile, the material can be used as an alternative for parts made from ABS and rubbers. Ultrafuse® TPU 64D is easy to print on direct drive and bowden style printers and is compatible with soluble BVOH support to realize the most complex geometries.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse TPU-85A] +inherits = Ultrafuse TPU-64D +filament_density = 1.11 +first_layer_temperature = 225 +temperature = 220 +filament_notes = "Material Description\nUltrafuse® TPU 85A comes in its natural white color. Chemical properties (e.g. resistance against particular substances) and tolerance for solvents can be made available, if these factors are relevant for a specific application. Generally, these properties correspond to publicly available data on polyether based TPUs. This material is not FDA conform. Good flexibility at low temperature, good wear performance and good damping behavior are the key features of Ultrafuse® TPU 85A.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse TPU-95A] +inherits = Ultrafuse TPU-85A +filament_density = 1.14 +first_layer_temperature = 230 +temperature = 225 +filament_notes = "Material Description\nUltrafuse® TPU 95A comes with a well-balanced profile of flexibility and durability. On top of that, it allows for easier and faster printing then softer TPU grades. Parts printed with Ultrafuse® TPU 95A show a high elongation, good impact resistance, excellent layer adhesion and a good resistance to oils and common industrially used chemicals. Due to its good printing behavior, Ultrafuse® TPU 95A is a good choice for starting printing flexible materials on both direct drive and bowden style printers.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse rPET] +inherits = Ultrafuse PET +filament_density = 1.27 +filament_colour = #9DC5FF +first_layer_temperature = 235 +temperature = 235 +first_layer_bed_temperature = 80 +bed_temperature = 75 +min_fan_speed = 50 +max_fan_speed = 100 +fan_below_layer_time = 15 +filament_notes = "Material Description\nPET is mainly known by the well-known PET bottle material. This recycled has a natural transparent blueish look. It has excellent 3D printing properties and good mechanical characteristics." + +[filament:Ultrafuse Metal] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 4.5 +extrusion_multiplier = 1.08 +first_layer_temperature = 250 +first_layer_bed_temperature = 100 +temperature = 250 +bed_temperature = 100 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +cooling = 0 +fan_always_on = 0 +filament_max_volumetric_speed = 4 +filament_type = METAL +compatible_printers_condition = nozzle_diameter[0]>=0.4 +filament_colour = #FFFFFF + +[filament:Polymaker PC-Max] +inherits = *ABS* +filament_vendor = Polymaker +filament_cost = 77.3 +filament_density = 1.20 +filament_type = PC +bed_temperature = 115 +filament_colour = #FFF2EC +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +bridge_fan_speed = 0 + +[filament:PrimaSelect PVA+] +inherits = *PLA* +filament_vendor = PrimaSelect +filament_cost = 122.1 +filament_density = 1.23 +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +temperature = 195 + +[filament:Prusa ABS] +inherits = *ABSC* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Prusa HIPS] +inherits = Generic HIPS +filament_vendor = Made for Prusa +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic HIPS] +inherits = *ABS* +filament_vendor = Generic +filament_cost = 27.3 +filament_density = 1.04 +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 230 +max_fan_speed = 20 +min_fan_speed = 20 +temperature = 230 +compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) + +[filament:Prusa PETG] +inherits = *PET* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Verbatim PETG] +inherits = *PET* +filament_vendor = Verbatim +filament_cost = 27.90 +filament_density = 1.27 +filament_spool_weight = 235 + +[filament:Prusament PETG] +inherits = *PET* +filament_vendor = Prusa Polymers +first_layer_temperature = 240 +temperature = 250 +filament_cost = 36.29 +filament_density = 1.27 +filament_spool_weight = 201 +filament_type = PETG + +[filament:Prusa PLA] +inherits = *PLA* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Eolas Prints PLA] +inherits = *PLA* +filament_vendor = Eolas Prints +filament_cost = 23.50 +filament_density = 1.24 +filament_spool_weight = 0 +filament_colour = #4D9398 +temperature = 208 + +[filament:Eolas Prints PLA Matte] +inherits = Eolas Prints PLA +filament_cost = 25.50 +temperature = 212 + +[filament:Eolas Prints INGEO 850] +inherits = Eolas Prints PLA +filament_cost = 25.90 +temperature = 210 + +[filament:Eolas Prints INGEO 870] +inherits = Eolas Prints PLA +filament_cost = 25.90 +temperature = 215 +first_layer_bed_temperature = 68 +first_layer_temperature = 220 +bed_temperature = 65 + +[filament:Eolas Prints PETG] +inherits = *PET* +filament_vendor = Eolas Prints +filament_cost = 29.90 +filament_density = 1.27 +filament_spool_weight = 0 +filament_colour = #4D9398 +temperature = 240 +first_layer_bed_temperature = 85 +first_layer_temperature = 235 +bed_temperature = 90 + +[filament:Eolas Prints PETG - UV Resistant] +inherits = Eolas Prints PETG +filament_cost = 35.90 +temperature = 237 +first_layer_temperature = 232 + +[filament:Eolas Prints TPU 93A] +inherits = *FLEX* +filament_vendor = Eolas Prints +filament_cost = 34.99 +filament_density = 1.21 +filament_colour = #4D9398 +filament_max_volumetric_speed = 1.2 +temperature = 235 +first_layer_bed_temperature = 30 +bed_temperature = 30 +filament_retract_length = 0 +extrusion_multiplier = 1.16 + +[filament:Fiberlogy Easy PLA] +inherits = *PLA* +filament_vendor = Fiberlogy +filament_cost = 20 +filament_density = 1.24 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 330 + +[filament:Fiberlogy Easy PET-G] +inherits = *PET* +filament_vendor = Fiberlogy +filament_spool_weight = 330 +filament_cost = 20 +filament_density = 1.27 +first_layer_bed_temperature = 80 +bed_temperature = 80 +first_layer_temperature = 235 +temperature = 235 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 60 +disable_fan_first_layers = 5 +full_fan_speed_layer = 5 +slowdown_below_layer_time = 15 + +[filament:Fiberlogy ASA] +inherits = *ABS* +filament_vendor = Fiberlogy +filament_cost = 33 +filament_density = 1.07 +filament_spool_weight = 330 +fan_always_on = 0 +cooling = 1 +min_fan_speed = 10 +max_fan_speed = 15 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ASA +fan_below_layer_time = 30 +disable_fan_first_layers = 5 + +[filament:Fiberlogy Easy ABS] +inherits = Fiberlogy ASA +filament_cost = 22.67 +filament_density = 1.09 +fan_always_on = 0 +cooling = 1 +min_fan_speed = 10 +max_fan_speed = 15 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 250 +temperature = 250 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ABS +fan_below_layer_time = 25 +disable_fan_first_layers = 5 + +[filament:Fiberlogy CPE HT] +inherits = *PET* +filament_vendor = Fiberlogy +filament_cost = 42.67 +filament_density = 1.18 +extrusion_multiplier = 0.98 +filament_spool_weight = 330 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 50 +min_print_speed = 15 +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_type = CPE +fan_below_layer_time = 20 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 5 + +[filament:Fiberlogy PCTG] +inherits = Fiberlogy CPE HT +filament_cost = 29.41 +filament_density = 1.23 +extrusion_multiplier = 1 +min_fan_speed = 10 +max_fan_speed = 15 +first_layer_temperature = 265 +temperature = 265 +first_layer_bed_temperature = 90 +bed_temperature = 90 +filament_type = PCTG + +[filament:Fiberlogy FiberFlex 40D] +inherits = *FLEX* +filament_vendor = Fiberlogy +fan_always_on = 1 +filament_max_volumetric_speed = 1.5 +extrusion_multiplier = 1.12 +first_layer_temperature = 230 +first_layer_bed_temperature = 60 +temperature = 230 +bed_temperature = 60 +bridge_fan_speed = 75 +min_fan_speed = 25 +max_fan_speed = 75 +filament_cost = 39.41 +filament_density = 1.16 +disable_fan_first_layers = 5 +full_fan_speed_layer = 5 +min_print_speed = 15 +cooling = 1 +filament_spool_weight = 330 + +[filament:Fiberlogy MattFlex 40D] +inherits = Fiberlogy FiberFlex 40D +filament_vendor = Fiberlogy +fan_always_on = 1 +filament_max_volumetric_speed = 1.35 +extrusion_multiplier = 1.1 +filament_cost = 49.11 + +[filament:Fiberlogy FiberFlex 30D] +inherits = Fiberlogy FiberFlex 40D +filament_max_volumetric_speed = 1.2 +extrusion_multiplier = 1.15 +first_layer_temperature = 240 +temperature = 240 +min_fan_speed = 25 +max_fan_speed = 60 +filament_density = 1.07 + +[filament:Fiberlogy FiberSatin] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 215 +temperature = 215 +extrusion_multiplier = 1 +filament_density = 1.2 +filament_cost = 32.35 + +[filament:Fiberlogy FiberSilk] +inherits = Fiberlogy FiberSatin +first_layer_temperature = 230 +temperature = 230 +extrusion_multiplier = 0.97 +filament_density = 1.22 +filament_cost = 32.35 + +[filament:Fiberlogy FiberWood] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 185 +temperature = 185 +extrusion_multiplier = 1 +filament_density = 1.23 +filament_cost = 38.66 + +[filament:Fiberlogy HD PLA] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 230 +temperature = 230 +extrusion_multiplier = 1 +filament_density = 1.24 +filament_cost = 30.59 + +[filament:Fiberlogy PLA Mineral] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 195 +temperature = 190 +extrusion_multiplier = 0.98 +filament_density = 1.38 +filament_cost = 37.64 + +[filament:Fiberlogy Impact PLA] +inherits = Fiberlogy HD PLA +filament_density = 1.22 +filament_cost = 27.65 + +[filament:Fiberlogy Nylon PA12] +inherits = Fiberlogy ASA +filament_type = PA +filament_density = 1.01 +filament_cost = 48 +first_layer_bed_temperature = 105 +bed_temperature = 105 +first_layer_temperature = 265 +temperature = 265 +min_fan_speed = 10 +max_fan_speed = 15 +fan_below_layer_time = 20 +bridge_fan_speed = 30 +fan_always_on = 0 + +[filament:Fiberlogy Nylon PA12+CF15] +inherits = Fiberlogy Nylon PA12 +extrusion_multiplier = 0.97 +filament_density = 1.07 +filament_cost = 87.5 +first_layer_bed_temperature = 105 +bed_temperature = 105 +first_layer_temperature = 265 +temperature = 265 +min_fan_speed = 10 +max_fan_speed = 15 +fan_below_layer_time = 20 +bridge_fan_speed = 30 +fan_always_on = 0 + +[filament:Fiberlogy Nylon PA12+GF15] +inherits = Fiberlogy Nylon PA12+CF15 +filament_density = 1.13 + +[filament:Fiberlogy PP] +inherits = *ABS* +filament_vendor = Fiberlogy +filament_cost = 36.67 +filament_density = 1.05 +extrusion_multiplier = 1.05 +filament_spool_weight = 330 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 0 +max_fan_speed = 25 +bridge_fan_speed = 70 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 245 +temperature = 245 +first_layer_bed_temperature = 0 +bed_temperature = 0 +filament_type = PP +fan_below_layer_time = 100 +disable_fan_first_layers = 5 +filament_max_volumetric_speed = 5 + +[filament:Filament PM PLA] +inherits = *PLA* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:AmazonBasics PLA] +inherits = *PLA* +filament_vendor = AmazonBasics +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Overture PLA] +inherits = *PLA* +filament_vendor = Overture +filament_cost = 22 +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Hatchbox PLA] +inherits = *PLA* +filament_vendor = Hatchbox +filament_cost = 25.4 +filament_density = 1.27 +filament_spool_weight = 245 + +[filament:Esun PLA] +inherits = *PLA* +filament_vendor = Esun +filament_cost = 25.4 +filament_density = 1.24 +filament_spool_weight = 265 + +[filament:Das Filament PLA] +inherits = *PLA* +filament_vendor = Das Filament +filament_cost = 25.4 +filament_density = 1.24 + +[filament:EUMAKERS PLA] +inherits = *PLA* +filament_vendor = EUMAKERS +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Floreon3D PLA] +inherits = *PLA* +filament_vendor = Floreon3D +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Prusament PLA] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +filament_cost = 36.29 +filament_density = 1.24 +filament_spool_weight = 201 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" + +[filament:Prusament PVB] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +bed_temperature = 75 +first_layer_bed_temperature = 75 +filament_cost = 60.48 +filament_density = 1.09 +filament_spool_weight = 201 +filament_max_volumetric_speed = 8 +filament_type = PVB +filament_soluble = 1 +filament_colour = #FFFF6F +slowdown_below_layer_time = 20 + +[filament:Fillamentum Flexfill 98A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_cost = 82.26 +filament_density = 1.23 +filament_spool_weight = 230 +extrusion_multiplier = 1.1 +filament_max_volumetric_speed = 1.5 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:ColorFabb VarioShore TPU] +inherits = Fillamentum Flexfill 98A +filament_vendor = ColorFabb +filament_colour = #BBBBBB +filament_cost = 71.35 +filament_density = 1.22 +filament_spool_weight = 0 +extrusion_multiplier = 0.85 +first_layer_temperature = 220 +temperature = 220 + +[filament:Taulman Bridge] +inherits = *common* +filament_vendor = Taulman +filament_cost = 40 +filament_density = 1.13 +bed_temperature = 110 +bridge_fan_speed = 40 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = PA +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 +max_fan_speed = 0 +min_fan_speed = 0 +filament_max_volumetric_speed = 6 + +[filament:Fillamentum Nylon FX256] +inherits = *common* +filament_vendor = Fillamentum +filament_cost = 56.99 +filament_density = 1.01 +filament_spool_weight = 230 +bed_temperature = 90 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 6 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 6 +filament_soluble = 0 +filament_type = PA +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +max_fan_speed = 0 +min_fan_speed = 0 +temperature = 250 + +[filament:Fiberthree F3 PA Pure Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_cost = 200.84 +filament_density = 1.2 +bed_temperature = 90 +first_layer_bed_temperature = 90 +first_layer_temperature = 285 +temperature = 285 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_soluble = 0 +filament_type = PA +max_fan_speed = 20 +min_fan_speed = 20 + +[filament:Fiberthree F3 PA-CF Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_cost = 208.1 +filament_density = 1.25 +bed_temperature = 90 +first_layer_bed_temperature = 90 +first_layer_temperature = 285 +temperature = 285 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_soluble = 0 +filament_type = PA +max_fan_speed = 0 +min_fan_speed = 0 + +[filament:Fiberthree F3 PA-GF Pro] +inherits = Fiberthree F3 PA-CF Pro +filament_vendor = Fiberthree +filament_cost = 205.68 +filament_density = 1.27 +fan_always_on = 1 +max_fan_speed = 15 +min_fan_speed = 15 + +[filament:Taulman T-Glase] +inherits = *PET* +filament_vendor = Taulman +filament_cost = 40 +filament_density = 1.27 +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 + +[filament:Verbatim PLA] +inherits = *PLA* +filament_vendor = Verbatim +filament_cost = 42.99 +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Verbatim BVOH] +inherits = *common* +filament_vendor = Verbatim +filament_cost = 193.58 +filament_density = 1.14 +filament_spool_weight = 235 +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +filament_vendor = Verbatim +filament_cost = 72 +filament_density = 0.89 +filament_spool_weight = 235 +bed_temperature = 100 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_type = PP +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 220 + +[filament:FormFutura Centaur PP] +inherits = *common* +filament_vendor = FormFutura +filament_cost = 70 +filament_density = 0.89 +filament_spool_weight = 212 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1.05 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 4 +filament_type = PP +first_layer_bed_temperature = 85 +bed_temperature = 85 +first_layer_temperature = 235 +max_fan_speed = 70 +min_fan_speed = 70 +temperature = 235 +filament_wipe = 0 filament_retract_lift = 0 \ No newline at end of file From 2d55f5761beb0447b89c71faa4a885251c5f8cc0 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 23 Jan 2023 12:35:30 +0100 Subject: [PATCH 173/206] 1.6.0-alpha0 Default top fill set to monotonic lines. Updated infill/perimeter overlap values. Updated output filename format. Enabled dynamic overhang speeds. --- resources/profiles/PrusaResearch.idx | 440 ++++++++++++++------------- resources/profiles/PrusaResearch.ini | 19 +- 2 files changed, 233 insertions(+), 226 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 68dff19c1..bd671ae74 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,219 +1,221 @@ -min_slic3r_version = 2.5.0-alpha0 -1.5.5 Added new Prusament Resin material profiles. Enabled g-code thumbnails for MK2.5 family printers. -1.5.4 Added material profiles for Prusament Resin BioBased60. -1.5.3 Added filament profiles for ColorFabb VarioShore TPU, FormFutura PP, NinjaTek NinjaFlex/Cheetah TPU and for multiple Eolas Prints filaments. Updated bridging settings in 50um and 70um profiles. -1.5.2 Added SLA material profiles. -1.5.1 Renamed filament type "NYLON" to "PA". Updated Adura X profile. Updated PETG fan settings for Prusa MINI (removed fan ramp up). -1.5.0 Updated arachne parameters. Added profiles for Jessie filaments. -1.5.0-alpha1 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments. -1.5.0-alpha0 Added parameters for Arachne perimeter generator. Changed default seam position. Updated output filename format. -min_slic3r_version = 2.4.0-rc -1.4.8 Added filament and SLA material profiles. Updated settings. -1.4.7 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments. -1.4.6 Added SLA materials. Updated filament profiles. -1.4.5 Added MMU2/S profiles for 0.25mm nozzle. Updated FW version. Enabled g-code thumbnails for MK3 family printers. Updated end g-code. -1.4.4 Added multiple Fiberlogy filament profiles. Updated Extrudr filament profiles. -1.4.3 Added new filament profiles and SLA materials. -1.4.2 Added SLA material profiles. -1.4.1 Updated firmware version. -1.4.0 Updated for the PrusaSlicer 2.4.0-rc release. Updated SLA material colors. -min_slic3r_version = 2.4.0-beta2 -1.4.0-beta3 Added material profiles for Prusament Resins. -1.4.0-beta2 Added SLA material colors. Updated BASF filament profiles. -min_slic3r_version = 2.4.0-beta0 -1.4.0-beta1 Updated pad wall slope angle for SLA printers. Updated Filatech Filacarbon profile for Prusa MINI. -1.4.0-beta0 Added multiple Filatech and BASF filament profiles. Added material profiles for SL1S. -min_slic3r_version = 2.4.0-alpha0 -1.4.0-alpha8 Added material profiles for Prusament Resin. Detect bridging perimeters enabled by default. -1.4.0-alpha7 Updated brim_separation value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles. -1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height). -1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S). -1.4.0-alpha4 Decreased Area Fill (SL1S). -1.4.0-alpha3 Updated SL1S tilt times. -1.4.0-alpha2 Updated Prusa MINI machine limits. -1.4.0-alpha1 Added new SL1S resin profiles. -1.4.0-alpha0 Bumped up config version. -1.3.0-alpha2 Added SL1S SPEED profiles. -1.3.0-alpha1 Added Prusament PCCF. Increased travel acceleration for Prusa MINI. Updated start g-code for Prusa MINI. Added multiple add:north and Extrudr filament profiles. Updated Z travel speed values. -1.3.0-alpha0 Disabled thick bridges, updated support settings. -min_slic3r_version = 2.3.2-alpha0 -1.3.7 Updated firmware version. -1.3.6 Updated firmware version. -1.3.5 Added material profiles for Prusament Resins. -1.3.4 Added material profiles for new Prusament Resins. Added profiles for multiple BASF filaments. -1.3.3 Added multiple profiles for Filatech filaments. Added material profiles for SL1S SPEED. Updated SLA print settings. -1.3.2 Added material profiles for Prusament Resin. -1.3.1 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S). -1.3.0 Added SL1S SPEED profiles. -min_slic3r_version = 2.3.0-rc1 -1.2.12 Updated firmware version. -1.2.11 Updated firmware version. -1.2.10 Added multiple profiles for Filatech filaments. Updated SLA print settings (pad wall slope angle). -1.2.9 Added material profiles for Prusament Resin. -1.2.8 Added multiple add:north and Extrudr filament profiles. -1.2.7 Updated "Prusament PC Blend Carbon Fiber" profile for Prusa MINI. -1.2.6 Added filament profile for "Prusament PC Blend Carbon Fiber". -1.2.5 Updated firmware version. Added filament profiles. Various improvements. -1.2.4 Updated cost/density values in filament settings. Various changes in print settings. -1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles. -1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. -1.2.1 Updated FW version for MK2.5 family printers. -1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version. -min_slic3r_version = 2.3.0-beta2 -1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values. -min_slic3r_version = 2.3.0-beta0 -1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights. -min_slic3r_version = 2.3.0-alpha4 -1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. -1.2.0-alpha0 Added filament spool weights -min_slic3r_version = 2.2.0-alpha3 -1.1.16 Updated firmware version. -1.1.15 Updated firmware version. -1.1.14 Updated firmware version. -1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles. -1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. -1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. -1.1.10 Updated firmware version. -1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials. -1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles. -1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles. -1.1.6 Updated firmware version for MK2.5/S and MK3/S. -1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend -1.1.4 Added Prusament PC Blend filament profile. -1.1.3 Added SLA material and filament profile -1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET. -1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS. -1.1.1-beta Updated for PrusaSlicer 2.2.0-beta -1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc -1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. -# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, -# so they will see the print bed. -max_slic3r_version = 2.2.0-alpha2 -min_slic3r_version = 2.2.0-alpha0 -1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles. -1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0 -min_slic3r_version = 2.1.1-beta0 -1.0.12 Updated firmware version. -1.0.11 Updated firmware version. -1.0.10 Updated firmware version for MK2.5/S and MK3/S. -1.0.9 Updated firmware version for MK2.5/S and MK3/S. -1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog. -1.0.7 Updated layer height limits for MINI -1.0.6 Added Prusa MINI profiles -min_slic3r_version = 2.1.0-alpha0 -1.0.5 Added SLA materials -1.0.4 Updated firmware version and 0.25mm nozzle profiles -1.0.3 Added filament profiles -1.0.2 Added SLA materials -1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers. -1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports. -1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament. -1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times -1.0.0-alpha2 Printer model and nozzle diameter check -1.0.0-alpha1 Added Prusament ASA profile -1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX -min_slic3r_version = 1.42.0-alpha6 -0.8.11 Updated firmware version. -0.8.10 Updated firmware version. -0.8.9 Updated firmware version for MK2.5/S and MK3/S. -0.8.8 Updated firmware version for MK2.5/S and MK3/S. -0.8.7 Updated firmware version -0.8.6 Updated firmware version for MK2.5/S and MK3/S -0.8.5 Updated SL1 printer and material settings -0.8.4 Added Prusament ASA profile -0.8.3 FW version and SL1 materials update -0.8.2 FFF and SL1 settings update -0.8.1 Output settings and SLA materials update -0.8.0 Updated for the PrusaSlicer 2.0.0 final release -0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S -0.8.0-rc1 Updated SLA profiles -0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release -0.8.0-beta4 Updated SLA profiles -0.8.0-beta3 Updated SLA profiles -0.8.0-beta2 Updated SLA profiles -0.8.0-beta1 Updated SLA profiles -0.8.0-beta Updated SLA profiles -0.8.0-alpha9 Updated SLA and FFF profiles -0.8.0-alpha8 Updated SLA profiles -0.8.0-alpha7 Updated SLA profiles -0.8.0-alpha6 Updated SLA profiles -min_slic3r_version = 1.42.0-alpha -0.8.0-alpha Updated SLA profiles -0.4.0-alpha4 Updated SLA profiles -0.4.0-alpha3 Update of SLA profiles -0.4.0-alpha2 First SLA profiles -min_slic3r_version = 1.41.3-alpha -0.4.12 Updated firmware version for MK2.5/S and MK3/S. -0.4.11 Updated firmware version for MK2.5/S and MK3/S. -0.4.10 Updated firmware version -0.4.9 Updated firmware version for MK2.5/S and MK3/S -0.4.8 MK2.5/3/S FW update -0.4.7 MK2/S/MMU FW update -0.4.6 Updated firmware versions for MK2.5/S and MK3/S -0.4.5 Enabled remaining time support for MK2/S/MMU1 -0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.4.1 New MK2.5S and MK3S FW versions -0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -min_slic3r_version = 1.41.1 -0.3.11 Updated firmware version for MK2.5/S and MK3/S. -0.3.10 Updated firmware version -0.3.9 Updated firmware version for MK2.5/S and MK3/S -0.3.8 MK2.5/3/S FW update -0.3.7 MK2/S/MMU FW update -0.3.6 Updated firmware versions for MK2.5 and MK3 -0.3.5 New MK2.5 and MK3 FW versions -0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.3.3 Prusament PETG released -0.3.2 New MK2.5 and MK3 FW versions -0.3.1 New MK2.5 and MK3 FW versions -0.3.0 New MK2.5 and MK3 FW version -min_slic3r_version = 1.41.0-alpha -0.2.9 New MK2.5 and MK3 FW versions -0.2.8 New MK2.5 and MK3 FW version -min_slic3r_version = 1.41.1 -0.2.7 New MK2.5 and MK3 FW version -0.2.6 Added MMU2 MK2.5 settings -min_slic3r_version = 1.41.0-alpha -0.2.5 Prusament is out - added prusament settings -0.2.4 Added soluble support profiles for MMU2 -0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit -0.2.2 Edited MMU2 Single mode purge line -0.2.1 Added PET and BVOH settings for MMU2 -0.2.0-beta5 Fixed MMU1 ramming parameters -0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower -0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2 -0.2.0-beta2 Edited first layer speed and wipe tower position -0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles -0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. -0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references -0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version -0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 -0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. -0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost -0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material -0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 -0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters -min_slic3r_version = 1.40.0 -0.1.18 Updated firmware version -0.1.17 Updated firmware version for MK2.5/S and MK3/S -0.1.16 MK2.5/3/S FW update -0.1.15 MK2/S/MMU FW update -0.1.14 Updated firmware versions for MK2.5 and MK3 -0.1.13 New MK2.5 and MK3 FW versions -0.1.12 New MK2.5 and MK3 FW versions -0.1.11 fw version changed to 3.3.1 -0.1.10 MK3 jerk and acceleration update -0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles -0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes -0.1.7 Fixed errors in 0.25mm and 0.6mm profiles -0.1.6 Split the MK2.5 profile from the MK2S -min_slic3r_version = 1.40.0-beta -0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles -0.1.4 edited fw version, added z-raise after print -min_slic3r_version = 1.40.0-alpha -0.1.3 Fixed an incorrect position of the max_print_height parameter -0.1.2 Wipe tower changes -0.1.1 Minor print speed adjustments -0.1.0 Initial +min_slic3r_version = 2.6.0-alpha1 +1.6.0-alpha0 Default top fill set to monotonic lines. Updated infill/perimeter overlap values. Updated output filename format. Enabled dynamic overhang speeds. +min_slic3r_version = 2.5.0-alpha0 +1.5.5 Added new Prusament Resin material profiles. Enabled g-code thumbnails for MK2.5 family printers. +1.5.4 Added material profiles for Prusament Resin BioBased60. +1.5.3 Added filament profiles for ColorFabb VarioShore TPU, FormFutura PP, NinjaTek NinjaFlex/Cheetah TPU and for multiple Eolas Prints filaments. Updated bridging settings in 50um and 70um profiles. +1.5.2 Added SLA material profiles. +1.5.1 Renamed filament type "NYLON" to "PA". Updated Adura X profile. Updated PETG fan settings for Prusa MINI (removed fan ramp up). +1.5.0 Updated arachne parameters. Added profiles for Jessie filaments. +1.5.0-alpha1 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments. +1.5.0-alpha0 Added parameters for Arachne perimeter generator. Changed default seam position. Updated output filename format. +min_slic3r_version = 2.4.0-rc +1.4.8 Added filament and SLA material profiles. Updated settings. +1.4.7 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments. +1.4.6 Added SLA materials. Updated filament profiles. +1.4.5 Added MMU2/S profiles for 0.25mm nozzle. Updated FW version. Enabled g-code thumbnails for MK3 family printers. Updated end g-code. +1.4.4 Added multiple Fiberlogy filament profiles. Updated Extrudr filament profiles. +1.4.3 Added new filament profiles and SLA materials. +1.4.2 Added SLA material profiles. +1.4.1 Updated firmware version. +1.4.0 Updated for the PrusaSlicer 2.4.0-rc release. Updated SLA material colors. +min_slic3r_version = 2.4.0-beta2 +1.4.0-beta3 Added material profiles for Prusament Resins. +1.4.0-beta2 Added SLA material colors. Updated BASF filament profiles. +min_slic3r_version = 2.4.0-beta0 +1.4.0-beta1 Updated pad wall slope angle for SLA printers. Updated Filatech Filacarbon profile for Prusa MINI. +1.4.0-beta0 Added multiple Filatech and BASF filament profiles. Added material profiles for SL1S. +min_slic3r_version = 2.4.0-alpha0 +1.4.0-alpha8 Added material profiles for Prusament Resin. Detect bridging perimeters enabled by default. +1.4.0-alpha7 Updated brim_separation value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles. +1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height). +1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S). +1.4.0-alpha4 Decreased Area Fill (SL1S). +1.4.0-alpha3 Updated SL1S tilt times. +1.4.0-alpha2 Updated Prusa MINI machine limits. +1.4.0-alpha1 Added new SL1S resin profiles. +1.4.0-alpha0 Bumped up config version. +1.3.0-alpha2 Added SL1S SPEED profiles. +1.3.0-alpha1 Added Prusament PCCF. Increased travel acceleration for Prusa MINI. Updated start g-code for Prusa MINI. Added multiple add:north and Extrudr filament profiles. Updated Z travel speed values. +1.3.0-alpha0 Disabled thick bridges, updated support settings. +min_slic3r_version = 2.3.2-alpha0 +1.3.7 Updated firmware version. +1.3.6 Updated firmware version. +1.3.5 Added material profiles for Prusament Resins. +1.3.4 Added material profiles for new Prusament Resins. Added profiles for multiple BASF filaments. +1.3.3 Added multiple profiles for Filatech filaments. Added material profiles for SL1S SPEED. Updated SLA print settings. +1.3.2 Added material profiles for Prusament Resin. +1.3.1 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S). +1.3.0 Added SL1S SPEED profiles. +min_slic3r_version = 2.3.0-rc1 +1.2.12 Updated firmware version. +1.2.11 Updated firmware version. +1.2.10 Added multiple profiles for Filatech filaments. Updated SLA print settings (pad wall slope angle). +1.2.9 Added material profiles for Prusament Resin. +1.2.8 Added multiple add:north and Extrudr filament profiles. +1.2.7 Updated "Prusament PC Blend Carbon Fiber" profile for Prusa MINI. +1.2.6 Added filament profile for "Prusament PC Blend Carbon Fiber". +1.2.5 Updated firmware version. Added filament profiles. Various improvements. +1.2.4 Updated cost/density values in filament settings. Various changes in print settings. +1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles. +1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. +1.2.1 Updated FW version for MK2.5 family printers. +1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version. +min_slic3r_version = 2.3.0-beta2 +1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values. +min_slic3r_version = 2.3.0-beta0 +1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights. +min_slic3r_version = 2.3.0-alpha4 +1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. +1.2.0-alpha0 Added filament spool weights +min_slic3r_version = 2.2.0-alpha3 +1.1.16 Updated firmware version. +1.1.15 Updated firmware version. +1.1.14 Updated firmware version. +1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles. +1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. +1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. +1.1.10 Updated firmware version. +1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials. +1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles. +1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles. +1.1.6 Updated firmware version for MK2.5/S and MK3/S. +1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend +1.1.4 Added Prusament PC Blend filament profile. +1.1.3 Added SLA material and filament profile +1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET. +1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS. +1.1.1-beta Updated for PrusaSlicer 2.2.0-beta +1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc +1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. +# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, +# so they will see the print bed. +max_slic3r_version = 2.2.0-alpha2 +min_slic3r_version = 2.2.0-alpha0 +1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles. +1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0 +min_slic3r_version = 2.1.1-beta0 +1.0.12 Updated firmware version. +1.0.11 Updated firmware version. +1.0.10 Updated firmware version for MK2.5/S and MK3/S. +1.0.9 Updated firmware version for MK2.5/S and MK3/S. +1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog. +1.0.7 Updated layer height limits for MINI +1.0.6 Added Prusa MINI profiles +min_slic3r_version = 2.1.0-alpha0 +1.0.5 Added SLA materials +1.0.4 Updated firmware version and 0.25mm nozzle profiles +1.0.3 Added filament profiles +1.0.2 Added SLA materials +1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers. +1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports. +1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament. +1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times +1.0.0-alpha2 Printer model and nozzle diameter check +1.0.0-alpha1 Added Prusament ASA profile +1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX +min_slic3r_version = 1.42.0-alpha6 +0.8.11 Updated firmware version. +0.8.10 Updated firmware version. +0.8.9 Updated firmware version for MK2.5/S and MK3/S. +0.8.8 Updated firmware version for MK2.5/S and MK3/S. +0.8.7 Updated firmware version +0.8.6 Updated firmware version for MK2.5/S and MK3/S +0.8.5 Updated SL1 printer and material settings +0.8.4 Added Prusament ASA profile +0.8.3 FW version and SL1 materials update +0.8.2 FFF and SL1 settings update +0.8.1 Output settings and SLA materials update +0.8.0 Updated for the PrusaSlicer 2.0.0 final release +0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S +0.8.0-rc1 Updated SLA profiles +0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release +0.8.0-beta4 Updated SLA profiles +0.8.0-beta3 Updated SLA profiles +0.8.0-beta2 Updated SLA profiles +0.8.0-beta1 Updated SLA profiles +0.8.0-beta Updated SLA profiles +0.8.0-alpha9 Updated SLA and FFF profiles +0.8.0-alpha8 Updated SLA profiles +0.8.0-alpha7 Updated SLA profiles +0.8.0-alpha6 Updated SLA profiles +min_slic3r_version = 1.42.0-alpha +0.8.0-alpha Updated SLA profiles +0.4.0-alpha4 Updated SLA profiles +0.4.0-alpha3 Update of SLA profiles +0.4.0-alpha2 First SLA profiles +min_slic3r_version = 1.41.3-alpha +0.4.12 Updated firmware version for MK2.5/S and MK3/S. +0.4.11 Updated firmware version for MK2.5/S and MK3/S. +0.4.10 Updated firmware version +0.4.9 Updated firmware version for MK2.5/S and MK3/S +0.4.8 MK2.5/3/S FW update +0.4.7 MK2/S/MMU FW update +0.4.6 Updated firmware versions for MK2.5/S and MK3/S +0.4.5 Enabled remaining time support for MK2/S/MMU1 +0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.1 New MK2.5S and MK3S FW versions +0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +min_slic3r_version = 1.41.1 +0.3.11 Updated firmware version for MK2.5/S and MK3/S. +0.3.10 Updated firmware version +0.3.9 Updated firmware version for MK2.5/S and MK3/S +0.3.8 MK2.5/3/S FW update +0.3.7 MK2/S/MMU FW update +0.3.6 Updated firmware versions for MK2.5 and MK3 +0.3.5 New MK2.5 and MK3 FW versions +0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.3.3 Prusament PETG released +0.3.2 New MK2.5 and MK3 FW versions +0.3.1 New MK2.5 and MK3 FW versions +0.3.0 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.0-alpha +0.2.9 New MK2.5 and MK3 FW versions +0.2.8 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.1 +0.2.7 New MK2.5 and MK3 FW version +0.2.6 Added MMU2 MK2.5 settings +min_slic3r_version = 1.41.0-alpha +0.2.5 Prusament is out - added prusament settings +0.2.4 Added soluble support profiles for MMU2 +0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit +0.2.2 Edited MMU2 Single mode purge line +0.2.1 Added PET and BVOH settings for MMU2 +0.2.0-beta5 Fixed MMU1 ramming parameters +0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower +0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2 +0.2.0-beta2 Edited first layer speed and wipe tower position +0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles +0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. +0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references +0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version +0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 +0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. +0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost +0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material +0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 +0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters +min_slic3r_version = 1.40.0 +0.1.18 Updated firmware version +0.1.17 Updated firmware version for MK2.5/S and MK3/S +0.1.16 MK2.5/3/S FW update +0.1.15 MK2/S/MMU FW update +0.1.14 Updated firmware versions for MK2.5 and MK3 +0.1.13 New MK2.5 and MK3 FW versions +0.1.12 New MK2.5 and MK3 FW versions +0.1.11 fw version changed to 3.3.1 +0.1.10 MK3 jerk and acceleration update +0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles +0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes +0.1.7 Fixed errors in 0.25mm and 0.6mm profiles +0.1.6 Split the MK2.5 profile from the MK2S +min_slic3r_version = 1.40.0-beta +0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles +0.1.4 edited fw version, added z-raise after print +min_slic3r_version = 1.40.0-alpha +0.1.3 Fixed an incorrect position of the max_print_height parameter +0.1.2 Wipe tower changes +0.1.1 Minor print speed adjustments +0.1.0 Initial diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 5922b216a..188e744b6 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.5.5 +config_version = 1.6.0-alpha0 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -173,7 +173,7 @@ infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 -infill_overlap = 25% +infill_overlap = 10% interface_shells = 0 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 @@ -184,7 +184,7 @@ notes = overhangs = 1 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -248,6 +248,8 @@ wall_transition_filter_deviation = 25% wall_transition_length = 0.4 wall_distribution_count = 1 min_bead_width = 85% +enable_dynamic_overhang_speeds = 1 +top_fill_pattern = monotoniclines [print:*MK3*] fill_pattern = grid @@ -289,7 +291,7 @@ support_material_interface_spacing = 0.15 support_material_spacing = 1 support_material_xy_spacing = 150% support_material_contact_distance = 0.1 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode thick_bridges = 0 bridge_flow_ratio = 1 bridge_speed = 20 @@ -299,6 +301,8 @@ wall_transition_filter_deviation = 25% wall_transition_length = 0.25 wall_distribution_count = 1 min_bead_width = 85% +infill_overlap = 10% +dynamic_overhang_speeds[0] = 20,20,15,15 [print:*0.25nozzleMK3*] inherits = *0.25nozzle* @@ -340,7 +344,7 @@ support_material_extrusion_width = 0.55 support_material_contact_distance = 0.15 support_material_xy_spacing = 80% support_material_interface_spacing = 0.3 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode infill_anchor_max = 15 top_solid_min_thickness = 0.9 bottom_solid_min_thickness = 0.6 @@ -352,6 +356,7 @@ wall_transition_filter_deviation = 25% wall_transition_length = 0.6 wall_distribution_count = 1 min_bead_width = 85% +infill_overlap = 15% [print:*0.6nozzleMK3*] inherits = *0.6nozzle* @@ -390,7 +395,7 @@ support_material_interface_speed = 100% support_material_spacing = 2 support_material_xy_spacing = 80% support_material_threshold = 50 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode fill_pattern = gyroid fill_density = 15% infill_anchor_max = 20 @@ -399,7 +404,7 @@ bottom_solid_layers = 3 skirt_distance = 3 skirt_height = 2 first_layer_height = 0.3 -infill_overlap = 30% +infill_overlap = 15% bridge_speed = 22 gap_fill_speed = 30 bridge_flow_ratio = 0.9 From c7783a58911e68fab46986b842d7d4ca5cb20040 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Jan 2023 16:06:00 +0100 Subject: [PATCH 174/206] Fix for #6377 - Prevent 3mf files from overwriting filament/printer settings + Ask about action on load of project even if print bed isn't empty --- src/slic3r/GUI/GUI_App.cpp | 7 +++++++ src/slic3r/GUI/Plater.cpp | 8 ++++++-- src/slic3r/GUI/Preferences.cpp | 6 ++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e9c912da2..e75f8836d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -772,8 +772,15 @@ void GUI_App::post_init() this->mainframe->load_config_file(this->init_params->load_configs.back()); // If loading a 3MF file, the config is loaded from the last one. if (!this->init_params->input_files.empty()) { +#if 1 // #ysFIXME_delete_after_test_of + wxArrayString fns; + for (const std::string& name : this->init_params->input_files) + fns.Add(from_u8(name)); + if (plater()->load_files(fns) && this->init_params->input_files.size() == 1) { +#else const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); if (!res.empty() && this->init_params->input_files.size() == 1) { +#endif // Update application titlebar when opening a project file const std::string& filename = this->init_params->input_files.front(); if (boost::algorithm::iends_with(filename, ".amf") || diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index af04d5865..0aad65ba1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6010,7 +6010,9 @@ protected: ProjectDropDialog::ProjectDropDialog(const std::string& filename) : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, - from_u8((boost::format(_utf8(L("%s - Drop project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition, +// #ysFIXME_delete_after_test_of_6377 +// from_u8((boost::format(_utf8(L("%s - Drop project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition, + from_u8((boost::format(_utf8(L("%s - Load project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { SetFont(wxGetApp().normal_font()); @@ -6118,7 +6120,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/* std::string filename = (*it).filename().string(); if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) { ProjectDropDialog::LoadType load_type = ProjectDropDialog::LoadType::Unknown; - if (!model().objects.empty()) { +// if (!model().objects.empty()) { // #ysFIXME_delete_after_test_of_6377 if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) || (boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) load_type = ProjectDropDialog::LoadType::LoadGeometry; @@ -6135,9 +6137,11 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/* load_type = static_cast(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), static_cast(ProjectDropDialog::LoadType::OpenProject), static_cast(ProjectDropDialog::LoadType::LoadConfig))); } +/* // #ysFIXME_delete_after_test_of_6377 } else load_type = ProjectDropDialog::LoadType::OpenProject; +*/ if (load_type == ProjectDropDialog::LoadType::Unknown) return false; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index fc9ea1867..3830ba75e 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -322,8 +322,14 @@ void PreferencesDialog::build() m_optgroup_general->append_separator(); append_bool_option(m_optgroup_general, "show_drop_project_dialog", +#if 1 // #ysFIXME_delete_after_test_of_6377 + L("Show load project dialog"), + L("When checked, whenever dragging and dropping a project file on the application or open it from a browser, " + "shows a dialog asking to select the action to take on the file to load."), +#else L("Show drop project dialog"), L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."), +#endif app_config->get("show_drop_project_dialog") == "1"); append_bool_option(m_optgroup_general, "single_instance", From f825b5c19397278aef71edda837e3e23f0a1e3f3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 24 Jan 2023 08:45:51 +0100 Subject: [PATCH 175/206] Fixed scaling and sizing objects using sidebar panel in Object and Part reeference systems --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 +- src/slic3r/GUI/Selection.cpp | 70 ++++++----------------- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 2e2a0d891..d737a7be4 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1218,8 +1218,8 @@ void ObjectManipulation::change_scale_value(int axis, double value) const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d ref_scale = m_cache.scale; if (selection.is_single_volume_or_modifier()) { - if (is_local_coordinates()) - ref_scale = 100.0 * Vec3d::Ones(); + scale = scale.cwiseQuotient(ref_scale); + ref_scale = Vec3d::Ones(); } else if (selection.is_single_full_instance()) { scale = scale.cwiseQuotient(ref_scale); @@ -1260,8 +1260,6 @@ void ObjectManipulation::change_size_value(int axis, double value) Vec3d ref_size = m_cache.size; #if ENABLE_WORLD_COORDINATE if (selection.is_single_volume_or_modifier()) { - if (is_local_coordinates()) - ref_size = selection.get_first_volume()->bounding_box().size(); size = size.cwiseQuotient(ref_size); ref_size = Vec3d::Ones(); #else diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index d74a17cc6..3c5b563b7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1464,46 +1464,13 @@ void Selection::mirror(Axis axis) void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type) { if (!m_valid) - return; - - Vec3d relative_scale = scale; + return; for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); - if (transformation_type.absolute()) { - // convert from absolute scaling to relative scaling - BoundingBoxf3 original_box; - BoundingBoxf3 reference_box = m_box.get_bounding_box(); - if (m_mode == Instance) { - if (is_single_full_instance()) { - if (transformation_type.world()) - original_box = get_full_unscaled_instance_bounding_box(); - else - original_box = get_full_unscaled_instance_local_bounding_box(); - } - else - original_box = get_bounding_box(); - } - else { - if (!is_single_volume_or_modifier()) - original_box = get_bounding_box(); - else if (transformation_type.world()) - original_box = get_bounding_box(); - else if (transformation_type.instance()) - original_box = v.transformed_convex_hull_bounding_box(volume_data.get_volume_transform().get_matrix()); - else { - original_box = v.bounding_box(); - reference_box = v.bounding_box().transformed(volume_data.get_volume_transform().get_scaling_factor_matrix()); - } - transformation_type.set_relative(); - } - - relative_scale = original_box.size().cwiseProduct(scale).cwiseQuotient(reference_box.size()); - } - if (m_mode == Instance) { if (transformation_type.instance()) { const Vec3d world_inst_pivot = m_cache.dragging_center - inst_trafo.get_offset(); @@ -1511,39 +1478,40 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation Matrix3d inst_rotation, inst_scale; inst_trafo.get_matrix().computeRotationScaling(&inst_rotation, &inst_scale); const Transform3d offset_trafo = Geometry::translation_transform(inst_trafo.get_offset() + inst_rotation * translation); - const Transform3d scale_trafo = Transform3d(inst_scale) * Geometry::scale_transform(relative_scale); + const Transform3d scale_trafo = Transform3d(inst_scale) * Geometry::scale_transform(scale); v.set_instance_transformation(Geometry::translation_transform(world_inst_pivot) * offset_trafo * Transform3d(inst_rotation) * scale_trafo * Geometry::translation_transform(-local_inst_pivot)); } else - transform_instance_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale), m_cache.dragging_center); + transform_instance_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale), m_cache.dragging_center); } else { if (!is_single_volume_or_modifier()) { assert(transformation_type.world()); - transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale), m_cache.dragging_center); + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale), m_cache.dragging_center); } else { - if (transformation_type.local()) { - const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); - Matrix3d vol_rotation, vol_scale; - vol_trafo.get_matrix().computeRotationScaling(&vol_rotation, &vol_scale); - const Transform3d offset_trafo = Geometry::translation_transform(vol_trafo.get_offset() + vol_rotation * translation); - const Transform3d scale_trafo = Transform3d(vol_scale) * Geometry::scale_transform(relative_scale); - v.set_volume_transformation(offset_trafo * Transform3d(vol_rotation) * scale_trafo); - } - else { - transformation_type.set_independent(); - transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale), m_cache.dragging_center); - } + if (transformation_type.local()) { + const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); + Matrix3d vol_rotation, vol_scale; + vol_trafo.get_matrix().computeRotationScaling(&vol_rotation, &vol_scale); + const Transform3d offset_trafo = Geometry::translation_transform(vol_trafo.get_offset() + vol_rotation * translation); + const Transform3d scale_trafo = Transform3d(vol_scale) * Geometry::scale_transform(scale); + v.set_volume_transformation(offset_trafo * Transform3d(vol_rotation) * scale_trafo); + } + else { + transformation_type.set_independent(); + transformation_type.set_relative(); + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale), m_cache.dragging_center); + } } } } #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - synchronize_unselected_instances(SyncRotationType::NONE); + synchronize_unselected_instances(SyncRotationType::NONE); else if (m_mode == Volume) - synchronize_unselected_volumes(); + synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH ensure_on_bed(); From fdcfae18dbde87e2826da17e5156491e17231e4f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 24 Jan 2023 09:47:53 +0100 Subject: [PATCH 176/206] Fixed transformation of volumes in Part reference systems --- src/slic3r/GUI/Selection.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 3c5b563b7..261604524 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -972,7 +972,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor transform_instance_relative(v, volume_data, transformation_type, Geometry::translation_transform(displacement), m_cache.dragging_center); } else { - if (transformation_type.local()) { + if (transformation_type.local() && transformation_type.absolute()) { const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); v.set_volume_offset(vol_trafo.get_offset() + inst_trafo.get_scaling_factor_matrix().inverse() * vol_trafo.get_rotation_matrix() * displacement); @@ -1074,7 +1074,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center); } else { - if (transformation_type.local()) { + if (transformation_type.local() && transformation_type.absolute()) { const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); Matrix3d vol_rotation, vol_scale; vol_trafo.get_matrix().computeRotationScaling(&vol_rotation, &vol_scale); @@ -1490,7 +1490,7 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale), m_cache.dragging_center); } else { - if (transformation_type.local()) { + if (transformation_type.local() && transformation_type.absolute()) { const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); Matrix3d vol_rotation, vol_scale; vol_trafo.get_matrix().computeRotationScaling(&vol_rotation, &vol_scale); @@ -3266,6 +3266,8 @@ void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& v const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot); volume.set_volume_transformation(trafo * vol_trafo.get_matrix()); } + else if (transformation_type.local()) + volume.set_volume_transformation(vol_trafo.get_matrix() * transform); else assert(false); } From d327a6b2ab2670674f6d10f26941a2b9097819ee Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Jan 2023 11:22:01 +0100 Subject: [PATCH 177/206] Sidebar: Use CallAfter for update of the PresetComboBox visibility, when printer technology is changed during a project loading AND/OR switching the application mode. Otherwise, some of PresetComboBoxes are invisible --- src/slic3r/GUI/Plater.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0aad65ba1..c966f942b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -702,10 +702,8 @@ void Sidebar::priv::show_preset_comboboxes() for (size_t i = 0; i < 4; ++i) sizer_presets->Show(i, !showSLA); - for (size_t i = 4; i < 8; ++i) { - if (sizer_presets->IsShown(i) != showSLA) - sizer_presets->Show(i, showSLA); - } + for (size_t i = 4; i < 8; ++i) + sizer_presets->Show(i, showSLA); frequently_changed_parameters->Show(!showSLA); @@ -819,8 +817,11 @@ Sidebar::Sidebar(Plater *parent) auto *sizer_presets = this->p->sizer_presets; auto *sizer_filaments = this->p->sizer_filaments; + // Hide controls, which will be shown/hidden in respect to the printer technology + text->Show(preset_type == Preset::TYPE_PRINTER); sizer_presets->Add(text, 0, wxALIGN_LEFT | wxEXPAND | wxRIGHT, 4); if (! filament) { + combo_and_btn_sizer->ShowItems(preset_type == Preset::TYPE_PRINTER); sizer_presets->Add(combo_and_btn_sizer, 0, wxEXPAND | #ifdef __WXGTK3__ wxRIGHT, margin_5); @@ -835,6 +836,7 @@ Sidebar::Sidebar(Plater *parent) wxBOTTOM, 1); #endif // __WXGTK3__ (*combo)->set_extruder_idx(0); + sizer_filaments->ShowItems(false); sizer_presets->Add(sizer_filaments, 1, wxEXPAND); } }; @@ -1087,7 +1089,10 @@ void Sidebar::update_presets(Preset::Type preset_type) case Preset::TYPE_PRINTER: { update_all_preset_comboboxes(); - p->show_preset_comboboxes(); + // CallAfter is really needed here to correct layout of the preset comboboxes, + // when printer technology is changed during a project loading AND/OR switching the application mode. + // Otherwise, some of comboboxes are invisible + CallAfter([this]() { p->show_preset_comboboxes(); }); break; } From 3d9f39e2584c5015b9e15f757c65a752f06b7a65 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 24 Jan 2023 14:31:20 +0100 Subject: [PATCH 178/206] Speed up of organic support smoothing & collision detection. --- src/libslic3r/TreeModelVolumes.cpp | 8 + src/libslic3r/TreeModelVolumes.hpp | 5 + src/libslic3r/TreeSupport.cpp | 411 ++++++++++++++++++++++------- 3 files changed, 326 insertions(+), 98 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 3c7cd8af3..63b0ab7a3 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -355,6 +355,14 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI return getCollision(orig_radius, layer_idx, min_xy_dist); } +// Get a collision area at a given layer for a radius that is a lower or equial to the key radius. +// It is expected that the collision area is precalculated for a given layer at least for the radius zero. +// Used for pushing tree supports away from object during the final Organic optimization step. +std::optional>> TreeModelVolumes::get_collision_lower_bound_area(LayerIndex layer_id, coord_t max_radius) const +{ + return m_collision_cache.get_lower_bound_area({ max_radius, layer_id }); +} + // Private. Only called internally by calculateAvoidance() and calculateAvoidanceToModel(), radius is already snapped to grid. const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const { diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 407025e1f..0c3c4cc8a 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -237,6 +237,11 @@ public: */ const Polygons& getCollision(const coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; + // Get a collision area at a given layer for a radius that is a lower or equial to the key radius. + // It is expected that the collision area is precalculated for a given layer at least for the radius zero. + // Used for pushing tree supports away from object during the final Organic optimization step. + std::optional>> get_collision_lower_bound_area(LayerIndex layer_id, coord_t max_radius) const; + /*! * \brief Provides the areas that have to be avoided by the tree's branches * in order to reach the build plate. diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 7f5f497e5..9b373312f 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -8,6 +8,7 @@ #include "TreeSupport.hpp" #include "AABBTreeIndirect.hpp" +#include "AABBTreeLines.hpp" #include "BuildVolume.hpp" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" @@ -801,6 +802,14 @@ static double layer_z(const SlicingParameters &slicing_params, const size_t laye { return slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height; } +static LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const double z) +{ + return LayerIndex(ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); +} +static LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const double z) +{ + return LayerIndex(floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); +} static inline SupportGeneratorLayer& layer_initialize( SupportGeneratorLayer &layer_new, @@ -2533,6 +2542,7 @@ static void create_nodes_from_area( double radius_increase = config.getRadius(elem.state) - config.getRadius(parent.state); assert(radius_increase >= 0); double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); + //FIXME this assert fails a lot. Is it correct? assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); } } @@ -2557,6 +2567,7 @@ static void create_nodes_from_area( double radius_increase = config.getRadius(elem.state) - config.getRadius(parent.state); assert(radius_increase >= 0); double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); + //FIXME this assert fails a lot. Is it correct? assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); } } @@ -3378,6 +3389,306 @@ static void extrude_branch( } #endif +// #define TREE_SUPPORT_ORGANIC_NUDGE_NEW 1 + +#ifdef TREE_SUPPORT_ORGANIC_NUDGE_NEW +// New version using per layer AABB trees of lines for nudging spheres away from an object. +static void organic_smooth_branches_avoid_collisions( + const PrintObject &print_object, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds, + const std::vector> &elements_with_link_down, + const std::vector &linear_data_layers) +{ + struct LayerCollisionCache { + coord_t min_element_radius{ std::numeric_limits::max() }; + bool min_element_radius_known() const { return this->min_element_radius != std::numeric_limits::max(); } + coord_t collision_radius{ 0 }; + std::vector lines; + AABBTreeIndirect::Tree<2, double> aabbtree_lines; + bool empty() const { return this->lines.empty(); } + }; + std::vector layer_collision_cache; + layer_collision_cache.reserve(1024); + const SlicingParameters &slicing_params = print_object.slicing_parameters(); + for (const std::pair& element : elements_with_link_down) { + LayerIndex layer_idx = element.first->state.layer_idx; + if (size_t num_layers = layer_idx + 1; num_layers > layer_collision_cache.size()) { + if (num_layers > layer_collision_cache.capacity()) + reserve_power_of_2(layer_collision_cache, num_layers); + layer_collision_cache.resize(num_layers, {}); + } + auto& l = layer_collision_cache[layer_idx]; + l.min_element_radius = std::min(l.min_element_radius, config.getRadius(element.first->state)); + } + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_collision_cache.size()); ++layer_idx) + if (LayerCollisionCache& l = layer_collision_cache[layer_idx]; !l.min_element_radius_known()) + l.min_element_radius = 0; + else { + //FIXME + l.min_element_radius = 0; + std::optional>> res = volumes.get_collision_lower_bound_area(layer_idx, l.min_element_radius); + assert(res.has_value()); + l.collision_radius = res->first; + Lines alines = to_lines(res->second.get()); + l.lines.reserve(alines.size()); + for (const Line &line : alines) + l.lines.push_back({ unscaled(line.a), unscaled(line.b) }); + l.aabbtree_lines = AABBTreeLines::build_aabb_tree_over_indexed_lines(l.lines); + } + + struct CollisionSphere { + const SupportElement& element; + int element_below_id; + const bool locked; + float radius; + // Current position, when nudged away from the collision. + Vec3f position; + // Previous position, for Laplacian smoothing. + Vec3f prev_position; + // + Vec3f last_collision; + double last_collision_depth; + // Minimum Z for which the sphere collision will be evaluated. + // Limited by the minimum sloping angle and by the bottom of the tree. + float min_z{ -std::numeric_limits::max() }; + // Maximum Z for which the sphere collision will be evaluated. + // Limited by the minimum sloping angle and by the tip of the current branch. + float max_z{ std::numeric_limits::max() }; + uint32_t layer_begin; + uint32_t layer_end; + }; + + std::vector collision_spheres; + collision_spheres.reserve(elements_with_link_down.size()); + for (const std::pair &element_with_link : elements_with_link_down) { + const SupportElement &element = *element_with_link.first; + const int link_down = element_with_link.second; + collision_spheres.push_back({ + element, + link_down, + // locked + element.parents.empty() || (link_down == -1 && element.state.layer_idx > 0), + unscaled(config.getRadius(element.state)), + // 3D position + to_3d(unscaled(element.state.result_on_layer), float(layer_z(slicing_params, element.state.layer_idx))) + }); + // Update min_z coordinate to min_z of the tree below. + CollisionSphere &collision_sphere = collision_spheres.back(); + if (link_down != -1) { + const size_t offset_below = linear_data_layers[element.state.layer_idx - 1]; + collision_sphere.min_z = collision_spheres[offset_below + link_down].min_z; + } else + collision_sphere.min_z = collision_sphere.position.z(); + } + // Update max_z by propagating max_z from the tips of the branches. + for (int collision_sphere_id = int(collision_spheres.size()) - 1; collision_sphere_id >= 0; -- collision_sphere_id) { + CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; + if (collision_sphere.element.parents.empty()) + // Tip + collision_sphere.max_z = collision_sphere.position.z(); + else { + // Below tip + const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1]; + for (auto iparent : collision_sphere.element.parents) { + float parent_z = collision_spheres[offset_above + iparent].max_z; +// collision_sphere.max_z = collision_sphere.max_z == std::numeric_limits::max() ? parent_z : std::max(collision_sphere.max_z, parent_z); + collision_sphere.max_z = std::min(collision_sphere.max_z, parent_z); + } + } + } + // Update min_z / max_z to limit the search Z span of a given sphere for collision detection. + for (CollisionSphere &collision_sphere : collision_spheres) { + //FIXME limit the collision span by the tree slope. + collision_sphere.min_z = std::max(collision_sphere.min_z, collision_sphere.position.z() - collision_sphere.radius); + collision_sphere.max_z = std::min(collision_sphere.max_z, collision_sphere.position.z() + collision_sphere.radius); + collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, collision_sphere.min_z)); + collision_sphere.layer_end = std::max(collision_sphere.element.state.layer_idx, layer_idx_floor(slicing_params, collision_sphere.max_z)) + 1; + } + + static constexpr const double collision_extra_gap = 0.1; + static constexpr const double max_nudge_collision_avoidance = 0.2; + static constexpr const double max_nudge_smoothing = 0.2; + static constexpr const size_t num_iter = 100; // 1000; + for (size_t iter = 0; iter < num_iter; ++ iter) { + // Back up prev position before Laplacian smoothing. + for (CollisionSphere &collision_sphere : collision_spheres) + collision_sphere.prev_position = collision_sphere.position; + std::atomic num_moved{ 0 }; + tbb::parallel_for(tbb::blocked_range(0, collision_spheres.size()), + [&collision_spheres, &layer_collision_cache, &slicing_params, &move_bounds, &linear_data_layers, &num_moved](const tbb::blocked_range range) { + for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id) + if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) { + // Calculate collision of multiple 2D layers against a collision sphere. + collision_sphere.last_collision_depth = - std::numeric_limits::max(); + for (uint32_t layer_id = collision_sphere.layer_begin; layer_id != collision_sphere.layer_end; ++ layer_id) { + double dz = (layer_id - collision_sphere.element.state.layer_idx) * slicing_params.layer_height; + if (double r2 = sqr(collision_sphere.radius) - sqr(dz); r2 > 0) { + if (const LayerCollisionCache &layer_collision_cache_item = layer_collision_cache[layer_id]; ! layer_collision_cache.empty()) { + size_t hit_idx_out; + Vec2d hit_point_out; + double dist = sqrt(AABBTreeLines::squared_distance_to_indexed_lines( + layer_collision_cache_item.lines, layer_collision_cache_item.aabbtree_lines, Vec2d(to_2d(collision_sphere.position).cast()), + hit_idx_out, hit_point_out, r2)); + double collision_depth = sqrt(r2) - dist; + if (collision_depth > collision_sphere.last_collision_depth) { + collision_sphere.last_collision_depth = collision_depth; + collision_sphere.last_collision = to_3d(hit_point_out.cast(), float(layer_z(slicing_params, layer_id))); + } + } + } + } + if (collision_sphere.last_collision_depth > 0) { + // Collision detected to be removed. + // Nudge the circle center away from the collision. + if (collision_sphere.last_collision_depth > EPSILON) + // a little bit of hysteresis to detect end of + ++ num_moved; + // Shift by maximum 2mm. + double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance); + Vec2d nudge_vector = (to_2d(collision_sphere.position) - to_2d(collision_sphere.last_collision)).cast().normalized() * nudge_dist; + collision_sphere.position.head<2>() += (nudge_vector * nudge_dist).cast(); + } + // Laplacian smoothing + Vec2d avg{ 0, 0 }; + const SupportElements &above = move_bounds[collision_sphere.element.state.layer_idx + 1]; + const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1]; + double weight = 0.; + for (auto iparent : collision_sphere.element.parents) { + double w = collision_sphere.radius; + avg += w * to_2d(collision_spheres[offset_above + iparent].prev_position.cast()); + weight += w; + } + if (collision_sphere.element_below_id != -1) { + const size_t offset_below = linear_data_layers[collision_sphere.element.state.layer_idx - 1]; + const double w = weight; // config.getRadius(move_bounds[element.state.layer_idx - 1][below].state); + avg += w * to_2d(collision_spheres[offset_below + collision_sphere.element_below_id].prev_position.cast()); + weight += w; + } + avg /= weight; + static constexpr const double smoothing_factor = 0.5; + Vec2d old_pos = to_2d(collision_sphere.position).cast(); + Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg; + Vec2d shift = new_pos - old_pos; + double nudge_dist_max = shift.norm(); + // Shift by maximum 1mm, less than the collision avoidance factor. + double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing); + collision_sphere.position.head<2>() += (shift.normalized() * nudge_dist).cast(); + } + }); + // printf("iteration: %d, moved: %d\n", int(iter), int(num_moved)); + if (num_moved == 0) + break; + } + + for (size_t i = 0; i < collision_spheres.size(); ++ i) + elements_with_link_down[i].first->state.result_on_layer = scaled(to_2d(collision_spheres[i].position)); +} +#else // TREE_SUPPORT_ORGANIC_NUDGE_NEW +// Old version using OpenVDB, works but it is extremely slow for complex meshes. +static void organic_smooth_branches_avoid_collisions( + const PrintObject &print_object, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds, + const std::vector> &elements_with_link_down, + const std::vector &linear_data_layers) +{ + TriangleMesh mesh = print_object.model_object()->raw_mesh(); + mesh.transform(print_object.trafo_centered()); + double scale = 10.; + openvdb::FloatGrid::Ptr grid = mesh_to_grid(mesh.its, openvdb::math::Transform{}, scale, 0., 0.); + std::unique_ptr> closest_surface_point = openvdb::tools::ClosestSurfacePoint::create(*grid); + std::vector pts, prev, projections; + std::vector distances; + for (const std::pair& element : elements_with_link_down) { + Vec3d pt = to_3d(unscaled(element.first->state.result_on_layer), layer_z(print_object.slicing_parameters(), element.first->state.layer_idx)) * scale; + pts.push_back({ pt.x(), pt.y(), pt.z() }); + } + + const double collision_extra_gap = 1. * scale; + const double max_nudge_collision_avoidance = 2. * scale; + const double max_nudge_smoothing = 1. * scale; + + static constexpr const size_t num_iter = 100; // 1000; + for (size_t iter = 0; iter < num_iter; ++ iter) { + prev = pts; + projections = pts; + distances.assign(pts.size(), std::numeric_limits::max()); + closest_surface_point->searchAndReplace(projections, distances); + size_t num_moved = 0; + for (size_t i = 0; i < projections.size(); ++ i) { + const SupportElement &element = *elements_with_link_down[i].first; + const int below = elements_with_link_down[i].second; + const bool locked = below == -1 && element.state.layer_idx > 0; + if (! locked && pts[i] != projections[i]) { + // Nudge the circle center away from the collision. + Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() }; + double depth = v.norm(); + assert(std::abs(distances[i] - depth) < EPSILON); + double radius = unscaled(config.getRadius(element.state)) * scale; + if (depth < radius) { + // Collision detected to be removed. + ++ num_moved; + double dxy = sqrt(sqr(radius) - sqr(v.z())); + double nudge_dist_max = dxy - std::hypot(v.x(), v.y()) + //FIXME 1mm gap + + collision_extra_gap; + // Shift by maximum 2mm. + double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_collision_avoidance); + Vec2d nudge_v = to_2d(v).normalized() * (- nudge_dist); + pts[i].x() += nudge_v.x(); + pts[i].y() += nudge_v.y(); + } + } + // Laplacian smoothing + if (! locked && ! element.parents.empty()) { + Vec2d avg{ 0, 0 }; + const SupportElements &above = move_bounds[element.state.layer_idx + 1]; + const size_t offset_above = linear_data_layers[element.state.layer_idx + 1]; + double weight = 0.; + for (auto iparent : element.parents) { + double w = config.getRadius(above[iparent].state); + avg.x() += w * prev[offset_above + iparent].x(); + avg.y() += w * prev[offset_above + iparent].y(); + weight += w; + } + size_t cnt = element.parents.size(); + if (below != -1) { + const size_t offset_below = linear_data_layers[element.state.layer_idx - 1]; + const double w = weight; // config.getRadius(move_bounds[element.state.layer_idx - 1][below].state); + avg.x() += w * prev[offset_below + below].x(); + avg.y() += w * prev[offset_below + below].y(); + ++ cnt; + weight += w; + } + //avg /= double(cnt); + avg /= weight; + static constexpr const double smoothing_factor = 0.5; + Vec2d old_pos{ pts[i].x(), pts[i].y() }; + Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg; + Vec2d shift = new_pos - old_pos; + double nudge_dist_max = shift.norm(); + // Shift by maximum 1mm, less than the collision avoidance factor. + double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing); + Vec2d nudge_v = shift.normalized() * nudge_dist; + pts[i].x() += nudge_v.x(); + pts[i].y() += nudge_v.y(); + } + } +// printf("iteration: %d, moved: %d\n", int(iter), int(num_moved)); + if (num_moved == 0) + break; + } + + for (size_t i = 0; i < projections.size(); ++ i) { + elements_with_link_down[i].first->state.result_on_layer.x() = scaled(pts[i].x()) / scale; + elements_with_link_down[i].first->state.result_on_layer.y() = scaled(pts[i].y()) / scale; + } +} +#endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW + static void draw_branches( PrintObject &print_object, const TreeModelVolumes &volumes, @@ -3392,8 +3703,6 @@ static void draw_branches( { static int irun = 0; - const SlicingParameters& slicing_params = print_object.slicing_parameters(); - // All SupportElements are put into a layer independent storage to improve parallelization. std::vector> elements_with_link_down; std::vector linear_data_layers; @@ -3432,102 +3741,7 @@ static void draw_branches( } } - std::unique_ptr> closest_surface_point; - { - TriangleMesh mesh = print_object.model_object()->raw_mesh(); - mesh.transform(print_object.trafo_centered()); - double scale = 10.; - openvdb::FloatGrid::Ptr grid = mesh_to_grid(mesh.its, openvdb::math::Transform{}, scale, 0., 0.); - closest_surface_point = openvdb::tools::ClosestSurfacePoint::create(*grid); - std::vector pts, prev, projections; - std::vector distances; - for (const std::pair &element : elements_with_link_down) { - Vec3d pt = to_3d(unscaled(element.first->state.result_on_layer), layer_z(slicing_params, element.first->state.layer_idx)) * scale; - pts.push_back({ pt.x(), pt.y(), pt.z() }); - } - - const double collision_extra_gap = 1. * scale; - const double max_nudge_collision_avoidance = 2. * scale; - const double max_nudge_smoothing = 1. * scale; - - static constexpr const size_t num_iter = 100; // 1000; - for (size_t iter = 0; iter < num_iter; ++ iter) { - prev = pts; - projections = pts; - distances.assign(pts.size(), std::numeric_limits::max()); - closest_surface_point->searchAndReplace(projections, distances); - size_t num_moved = 0; - for (size_t i = 0; i < projections.size(); ++ i) { - const SupportElement &element = *elements_with_link_down[i].first; - const int below = elements_with_link_down[i].second; - const bool locked = below == -1 && element.state.layer_idx > 0; - if (! locked && pts[i] != projections[i]) { - // Nudge the circle center away from the collision. - Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() }; - double depth = v.norm(); - assert(std::abs(distances[i] - depth) < EPSILON); - double radius = unscaled(config.getRadius(element.state)) * scale; - if (depth < radius) { - // Collision detected to be removed. - ++ num_moved; - double dxy = sqrt(sqr(radius) - sqr(v.z())); - double nudge_dist_max = dxy - std::hypot(v.x(), v.y()) - //FIXME 1mm gap - + collision_extra_gap; - // Shift by maximum 2mm. - double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_collision_avoidance); - Vec2d nudge_v = to_2d(v).normalized() * (- nudge_dist); - pts[i].x() += nudge_v.x(); - pts[i].y() += nudge_v.y(); - } - } - // Laplacian smoothing - if (! locked && ! element.parents.empty()) { - Vec2d avg{ 0, 0 }; - const SupportElements &above = move_bounds[element.state.layer_idx + 1]; - const size_t offset_above = linear_data_layers[element.state.layer_idx + 1]; - double weight = 0.; - for (auto iparent : element.parents) { - double w = config.getRadius(above[iparent].state); - avg.x() += w * prev[offset_above + iparent].x(); - avg.y() += w * prev[offset_above + iparent].y(); - weight += w; - } - size_t cnt = element.parents.size(); - if (below != -1) { - const size_t offset_below = linear_data_layers[element.state.layer_idx - 1]; - const double w = weight; // config.getRadius(move_bounds[element.state.layer_idx - 1][below].state); - avg.x() += w * prev[offset_below + below].x(); - avg.y() += w * prev[offset_below + below].y(); - ++ cnt; - weight += w; - } - //avg /= double(cnt); - avg /= weight; - static constexpr const double smoothing_factor = 0.5; - Vec2d old_pos{ pts[i].x(), pts[i].y() }; - Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg; - Vec2d shift = new_pos - old_pos; - double nudge_dist_max = shift.norm(); - // Shift by maximum 1mm, less than the collision avoidance factor. - double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing); - Vec2d nudge_v = shift.normalized() * nudge_dist; - pts[i].x() += nudge_v.x(); - pts[i].y() += nudge_v.y(); - } - } -// printf("iteration: %d, moved: %d\n", int(iter), int(num_moved)); - if (num_moved == 0) - break; - } - -#if 1 - for (size_t i = 0; i < projections.size(); ++ i) { - elements_with_link_down[i].first->state.result_on_layer.x() = scaled(pts[i].x()) / scale; - elements_with_link_down[i].first->state.result_on_layer.y() = scaled(pts[i].y()) / scale; - } -#endif - } + organic_smooth_branches_avoid_collisions(print_object, volumes, config, move_bounds, elements_with_link_down, linear_data_layers); std::vector support_layer_storage(move_bounds.size()); std::vector support_roof_storage(move_bounds.size()); @@ -3539,6 +3753,7 @@ static void draw_branches( // Traverse all nodes, generate tubes. // Traversal stack with nodes and thier current parent + const SlicingParameters &slicing_params = print_object.slicing_parameters(); std::vector path; indexed_triangle_set cummulative_mesh; indexed_triangle_set partial_mesh; From 4078b7eafc7506748410d757564b9bd1f2ffdcfb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Jan 2023 15:48:13 +0100 Subject: [PATCH 179/206] Partially revert for https://github.com/Prusa-Development/PrusaSlicerPrivate/commit/7858b5d3cd9ea1148a1da9ac5a87eb6ea98a7b53 - Merge option is suppressed to cut objects --- src/slic3r/GUI/GUI_ObjectList.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 54fa624bc..81ec956ee 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2628,6 +2628,9 @@ void ObjectList::delete_all_connectors_for_object(int obj_idx) bool ObjectList::can_merge_to_multipart_object() const { + if (has_selected_cut_object()) + return false; + wxDataViewItemArray sels; GetSelections(sels); if (sels.IsEmpty()) From 388ecdd0aaa26a0dcad526ca026e56bfa019a96e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 Jan 2023 10:12:47 +0100 Subject: [PATCH 180/206] Experimental new implementation for Selection::synchronize_unselected_instances() --- src/slic3r/GUI/Selection.cpp | 311 +++++++++++++++++++++++++++++++---- 1 file changed, 283 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 261604524..96e8f52e0 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -2889,6 +2889,225 @@ static void verify_instances_rotation_synchronized(const Model &model, const GLV } #endif /* NDEBUG */ +#if ENABLE_WORLD_COORDINATE +#define NO_TEST 0 +#define TEST_1 1 +#define TEST_2 2 +#define TEST_3 3 +#define USE_ALGORITHM TEST_3 + +#if USE_ALGORITHM == TEST_1 +void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + + const GLVolume& volume_i = *(*m_volumes)[i]; + if (volume_i.is_wipe_tower) + continue; + + const Geometry::Transformation& trafo_inst_i = volume_i.get_instance_transformation(); + const Vec3d offset_i = trafo_inst_i.get_offset(); + const double rotation_z_i = trafo_inst_i.get_rotation().z(); + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume& volume_j = *(*m_volumes)[j]; + if (volume_j.object_idx() != volume_i.object_idx() || volume_j.instance_idx() == volume_i.instance_idx()) + continue; + + const Geometry::Transformation& trafo_inst_j = m_cache.volumes_data[j].get_instance_transform(); + const Vec3d offset_j = trafo_inst_j.get_offset(); + const double rotation_z_j = trafo_inst_j.get_rotation().z(); + const double diff_rotation_z = rotation_z_j - rotation_z_i; + + const Transform3d new_matrix_inst_j = Geometry::translation_transform(offset_j) * Geometry::rotation_transform(diff_rotation_z * Vec3d::UnitZ()) * + Geometry::translation_transform(-offset_i) * trafo_inst_i.get_matrix(); + + volume_j.set_instance_transformation(Geometry::Transformation(new_matrix_inst_j)); + done.insert(j); + } + } + +//#ifndef NDEBUG +// verify_instances_rotation_synchronized(*m_model, *m_volumes); +//#endif /* NDEBUG */ +} +#elif USE_ALGORITHM == TEST_2 +void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ + auto fix_rotation = [](const Vec3d& rotation) { + const bool x = std::abs(std::abs(rotation.x()) - (double)PI) < EPSILON; + const bool y = std::abs(std::abs(rotation.y()) - (double)PI) < EPSILON; + const bool z = std::abs(std::abs(rotation.z()) - (double)PI) < EPSILON; + + Vec3d ret = rotation; + if ((x && y) || (x && z) || (y && z)) { + ret += (double)PI * Vec3d::Ones(); + if (ret.x() >= 2.0f * (double)PI) ret.x() -= 2.0f * (double)PI; + if (ret.y() >= 2.0f * (double)PI) ret.y() -= 2.0f * (double)PI; + if (ret.z() >= 2.0f * (double)PI) ret.z() -= 2.0f * (double)PI; + } + + return ret; + }; + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + + const GLVolume& volume_i = *(*m_volumes)[i]; + if (volume_i.is_wipe_tower) + continue; + + const Geometry::Transformation& trafo_inst_i = volume_i.get_instance_transformation(); + const Vec3d offset_i = trafo_inst_i.get_offset(); + const double rotation_z_i = fix_rotation(trafo_inst_i.get_rotation()).z(); + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume& volume_j = *(*m_volumes)[j]; + if (volume_j.object_idx() != volume_i.object_idx() || volume_j.instance_idx() == volume_i.instance_idx()) + continue; + + const Geometry::Transformation& trafo_inst_j = m_cache.volumes_data[j].get_instance_transform(); + const Vec3d offset_j = trafo_inst_j.get_offset(); + const double rotation_z_j = fix_rotation(trafo_inst_j.get_rotation()).z(); + const double diff_rotation_z = rotation_z_j - rotation_z_i; + + const Transform3d new_matrix_inst_j = Geometry::translation_transform(offset_j) * Geometry::rotation_transform(diff_rotation_z * Vec3d::UnitZ()) * + Geometry::translation_transform(-offset_i) * trafo_inst_i.get_matrix(); + + volume_j.set_instance_transformation(Geometry::Transformation(new_matrix_inst_j)); + done.insert(j); + } + } + +//#ifndef NDEBUG +// verify_instances_rotation_synchronized(*m_model, *m_volumes); +//#endif /* NDEBUG */ +} +#elif USE_ALGORITHM == TEST_3 +#define APPLY_FIX_ROTATION 1 +#define APPLY_FIX_ROTATION_2 2 && APPLY_FIX_ROTATION +void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ +#if APPLY_FIX_ROTATION + auto fix_rotation = [](const Vec3d& rotation) { + const bool x = std::abs(std::abs(rotation.x()) - (double)PI) < EPSILON; + const bool y = std::abs(std::abs(rotation.y()) - (double)PI) < EPSILON; + const bool z = std::abs(std::abs(rotation.z()) - (double)PI) < EPSILON; + + Vec3d ret = rotation; + if ((x && y) || (x && z) || (y && z)) { + ret += (double)PI * Vec3d::Ones(); + if (ret.x() >= 2.0f * (double)PI) ret.x() -= 2.0f * (double)PI; + if (ret.y() >= 2.0f * (double)PI) ret.y() -= 2.0f * (double)PI; + if (ret.z() >= 2.0f * (double)PI) ret.z() -= 2.0f * (double)PI; + } + + return ret; + }; + +#if APPLY_FIX_ROTATION_2 + auto fix_rotation_2 = [](const Vec3d& rotation) { + Vec3d ret = rotation; + if (0.5 * (double)PI <= rotation.y() && rotation.y() <= 1.5 * (double)PI) + ret.y() = 2.0 * (double)PI - ret.y(); + return ret; + }; +#endif // APPLY_FIX_ROTATION_2 +#endif // APPLY_FIX_ROTATION + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + + const GLVolume& volume_i = *(*m_volumes)[i]; + if (volume_i.is_wipe_tower) + continue; + + const Geometry::Transformation& cached_trafo_inst_i = m_cache.volumes_data[i].get_instance_transform(); + const Geometry::Transformation& trafo_inst_i = volume_i.get_instance_transformation(); + Geometry::Transformation trafo_i = Geometry::Transformation(trafo_inst_i.get_matrix() * cached_trafo_inst_i.get_matrix().inverse()); + + Matrix3d rotation_comp_i; + Matrix3d scale_comp_i; + trafo_i.get_matrix().computeRotationScaling(&rotation_comp_i, &scale_comp_i); +#if APPLY_FIX_ROTATION +#if APPLY_FIX_ROTATION_2 + const Vec3d rotation_i = fix_rotation_2(fix_rotation(Geometry::extract_rotation(Transform3d(rotation_comp_i)))); +#else + const Vec3d rotation_i = fix_rotation(Geometry::extract_rotation(Transform3d(rotation_comp_i))); +#endif // APPLY_FIX_ROTATION_2 +#else + const Vec3d rotation_i = Geometry::extract_rotation(Transform3d(rotation_comp_i)); +#endif // APPLY_FIX_ROTATION + + std::cout << "rotation_i: " << to_string(rotation_i); + + const Vec3d rotation_no_z_i(rotation_i.x(), rotation_i.y(), 0.0); + + trafo_i = Geometry::Transformation(Geometry::rotation_transform(rotation_no_z_i) * Transform3d(scale_comp_i)); + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume& volume_j = *(*m_volumes)[j]; + if (volume_j.object_idx() != volume_i.object_idx() || volume_j.instance_idx() == volume_i.instance_idx()) + continue; + + const Geometry::Transformation& cached_trafo_inst_j = m_cache.volumes_data[j].get_instance_transform(); +#if APPLY_FIX_ROTATION + const Vec3d rotation_cached_trafo_inst_j = fix_rotation(cached_trafo_inst_j.get_rotation()); +#else + const Vec3d rotation_cached_trafo_inst_j = cached_trafo_inst_j.get_rotation(); +#endif // APPLY_FIX_ROTATION + + std::cout << " - rotation_cached_trafo_inst_j: " << to_string(rotation_cached_trafo_inst_j) << "\n"; + + const Transform3d rotation_z_cached_trafo_inst_j = Geometry::rotation_transform({ 0.0, 0.0, rotation_cached_trafo_inst_j.z() }); + + const Transform3d new_matrix_inst_j = cached_trafo_inst_j.get_offset_matrix() * rotation_z_cached_trafo_inst_j * trafo_i.get_matrix() * + rotation_z_cached_trafo_inst_j.inverse() * cached_trafo_inst_j.get_matrix_no_offset(); + + volume_j.set_instance_transformation(Geometry::Transformation(new_matrix_inst_j)); + done.insert(j); + } + } + +//#ifndef NDEBUG +// verify_instances_rotation_synchronized(*m_model, *m_volumes); +//#endif /* NDEBUG */ +} +#else void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) { std::set done; // prevent processing volumes twice @@ -2904,17 +3123,11 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ const int object_idx = volume_i->object_idx(); const int instance_idx = volume_i->instance_idx(); -#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& curr_inst_trafo_i = volume_i->get_instance_transformation(); const Vec3d curr_inst_rotation_i = curr_inst_trafo_i.get_rotation(); const Vec3d& curr_inst_scaling_factor_i = curr_inst_trafo_i.get_scaling_factor(); const Vec3d& curr_inst_mirror_i = curr_inst_trafo_i.get_mirror(); const Vec3d old_inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); -#else - const Vec3d& rotation = volume_i->get_instance_rotation(); - const Vec3d& scaling_factor = volume_i->get_instance_scaling_factor(); - const Vec3d& mirror = volume_i->get_instance_mirror(); -#endif // ENABLE_WORLD_COORDINATE // Process unselected instances. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -2928,44 +3141,28 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) continue; -#if ENABLE_WORLD_COORDINATE const Vec3d old_inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); assert(is_rotation_xy_synchronized(old_inst_rotation_i, old_inst_rotation_j)); const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); const Vec3d curr_inst_rotation_j = curr_inst_trafo_j.get_rotation(); Vec3d new_inst_offset_j = curr_inst_trafo_j.get_offset(); Vec3d new_inst_rotation_j = curr_inst_rotation_j; -#else - assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); -#endif // ENABLE_WORLD_COORDINATE switch (sync_rotation_type) { case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. -#if ENABLE_WORLD_COORDINATE assert(is_rotation_xy_synchronized(curr_inst_rotation_i, curr_inst_rotation_j)); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) new_inst_offset_j.z() = curr_inst_trafo_i.get_offset().z(); -#else - assert(is_rotation_xy_synchronized(rotation, volume_j->get_instance_rotation())); - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) - volume_j->set_instance_offset(Z, volume_i->get_instance_offset().z()); -#endif // ENABLE_WORLD_COORDINATE break; } case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. -#if ENABLE_WORLD_COORDINATE const double z_diff = Geometry::rotation_diff_z(old_inst_rotation_i, old_inst_rotation_j); new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); -#else - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); - volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); -#endif // ENABLE_WORLD_COORDINATE break; } -#if ENABLE_WORLD_COORDINATE case SyncRotationType::FULL: { // generic rotation -> update instance z with the delta of the rotation. const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(curr_inst_rotation_i, old_inst_rotation_j)); @@ -2976,16 +3173,10 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); break; } -#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_WORLD_COORDINATE volume_j->set_instance_transformation(Geometry::assemble_transform(new_inst_offset_j, new_inst_rotation_j, curr_inst_scaling_factor_i, curr_inst_mirror_i)); -#else - volume_j->set_instance_scaling_factor(scaling_factor); - volume_j->set_instance_mirror(mirror); -#endif // ENABLE_WORLD_COORDINATE done.insert(j); } @@ -2995,6 +3186,70 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ verify_instances_rotation_synchronized(*m_model, *m_volumes); #endif /* NDEBUG */ } +#endif // USE_ALGORITHM +#else +void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + + const GLVolume* volume_i = (*m_volumes)[i]; + if (volume_i->is_wipe_tower) + continue; + + const int object_idx = volume_i->object_idx(); + const int instance_idx = volume_i->instance_idx(); + const Vec3d& rotation = volume_i->get_instance_rotation(); + const Vec3d& scaling_factor = volume_i->get_instance_scaling_factor(); + const Vec3d& mirror = volume_i->get_instance_mirror(); + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* volume_j = (*m_volumes)[j]; + if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) + continue; + + assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); + + switch (sync_rotation_type) { + case SyncRotationType::NONE: { + // z only rotation -> synch instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, volume_j->get_instance_rotation())); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + volume_j->set_instance_offset(Z, volume_i->get_instance_offset().z()); + break; + } + case SyncRotationType::GENERAL: { + // generic rotation -> update instance z with the delta of the rotation. + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); + break; + } + } + + volume_j->set_instance_scaling_factor(scaling_factor); + volume_j->set_instance_mirror(mirror); + + done.insert(j); + } + } + +#ifndef NDEBUG + verify_instances_rotation_synchronized(*m_model, *m_volumes); +#endif /* NDEBUG */ +} +#endif // ENABLE_WORLD_COORDINATE void Selection::synchronize_unselected_volumes() { From a1445da22ca63ed71f5a74a3ccd591ff271efefc Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:20:07 +0100 Subject: [PATCH 181/206] Fixed PVB --- resources/profiles/PrusaResearch.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 188e744b6..90486e438 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -4499,6 +4499,8 @@ filament_soluble = 1 filament_type = PVB filament_colour = #FFFF6F filament_spool_weight = 201 +bed_temperature = 75 +first_layer_bed_temperature = 75 slowdown_below_layer_time = 20 filament_ramming_parameters = "120 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.05{else}0.08{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K24{elsif nozzle_diameter[0]==0.8};{else}M900 K45{endif} ; Filament gcode LA 1.0" From 842229842f7fe7ce48215d6cfe55aaf4b46cfe56 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 25 Jan 2023 15:46:22 +0100 Subject: [PATCH 182/206] WIP Synchronization of mirroring --- src/libslic3r/Geometry.cpp | 3 +- src/libslic3r/Geometry.hpp | 3 +- src/slic3r/GUI/Selection.cpp | 109 +++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 7366d2233..25db3cce0 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -877,7 +877,8 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); const Vec3d& axis = angle_axis.axis(); const double angle = angle_axis.angle(); -#ifndef NDEBUG +#if 0 +//#ifndef NDEBUG if (std::abs(angle) > 1e-8) { assert(std::abs(axis.x()) < 1e-8); assert(std::abs(axis.y()) < 1e-8); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index a5df1e679..acaad6af3 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -470,8 +470,7 @@ public: Transform3d get_mirror_matrix() const; bool is_left_handed() const { - const Vec3d mirror = get_mirror(); - return mirror.x() * mirror.y() * mirror.z() < 0.0; + return m_matrix.affine().determinant() < 0; } #else bool is_scaling_uniform() const { return std::abs(m_scaling_factor.x() - m_scaling_factor.y()) < 1e-8 && std::abs(m_scaling_factor.x() - m_scaling_factor.z()) < 1e-8; } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 96e8f52e0..76bbd825c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -2852,6 +2852,16 @@ void Selection::render_debug_window() const } #endif // ENABLE_WORLD_COORDINATE_DEBUG +static bool is_left_handed(const Transform3d::ConstLinearPart& m) +{ + return m.determinant() < 0; +} + +static bool is_left_handed(const Transform3d& m) +{ + return is_left_handed(m.linear()); +} + #ifndef NDEBUG static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { @@ -2866,6 +2876,7 @@ static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d & return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; } +#if 0 static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) { for (int idx_object = 0; idx_object < int(model.objects.size()); ++idx_object) { @@ -2887,6 +2898,55 @@ static void verify_instances_rotation_synchronized(const Model &model, const GLV } } } +#endif + +static bool is_rotation_xy_synchronized(const Transform3d::ConstLinearPart &trafo_from, const Transform3d::ConstLinearPart &trafo_to) +{ + auto rot = trafo_to * trafo_from.inverse(); + static constexpr const double eps = EPSILON; + return + // Looks like a rotation around Z: block(0..1, 0..1) + no change of Z component. + is_approx(rot(0, 0), rot(1, 1), eps) && + is_approx(rot(0, 1), - rot(1, 0), eps) && + is_approx(rot(2, 2), 1., eps) && + // Rest should be zeros. + is_approx(rot(0, 2), 0., eps) && + is_approx(rot(1, 2), 0., eps) && + is_approx(rot(2, 0), 0., eps) && + is_approx(rot(2, 1), 0., eps) && + // Determinant equals 1 + is_approx(rot.determinant(), 1., eps) && + // and finally the rotated X and Y axes shall be perpendicular. + is_approx(rot(0, 0) * rot(0, 1) + rot(1, 0) * rot(1, 1), 0., eps); +} + +static bool is_rotation_xy_synchronized(const Transform3d& trafo_from, const Transform3d& trafo_to) +{ + return is_rotation_xy_synchronized(trafo_from.linear(), trafo_to.linear()); +} + +static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) +{ + for (int idx_object = 0; idx_object < int(model.objects.size()); ++idx_object) { + int idx_volume_first = -1; + for (int i = 0; i < (int)volumes.size(); ++i) { + if (volumes[i]->object_idx() == idx_object) { + idx_volume_first = i; + break; + } + } + assert(idx_volume_first != -1); // object without instances? + if (idx_volume_first == -1) + continue; + const Transform3d::ConstLinearPart &rotation0 = volumes[idx_volume_first]->get_instance_transformation().get_matrix().linear(); + for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i) + if (volumes[i]->object_idx() == idx_object) { + const Transform3d::ConstLinearPart &rotation = volumes[i]->get_instance_transformation().get_matrix().linear(); + assert(is_rotation_xy_synchronized(rotation, rotation0)); + } + } +} + #endif /* NDEBUG */ #if ENABLE_WORLD_COORDINATE @@ -3007,6 +3067,9 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ //#endif /* NDEBUG */ } #elif USE_ALGORITHM == TEST_3 + + +#if 0 #define APPLY_FIX_ROTATION 1 #define APPLY_FIX_ROTATION_2 2 && APPLY_FIX_ROTATION void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) @@ -3109,6 +3172,52 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ } #else void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + const GLVolume* volume_i = (*m_volumes)[i]; + if (volume_i->is_wipe_tower) + continue; + + const int object_idx = volume_i->object_idx(); + const int instance_idx = volume_i->instance_idx(); + const Transform3d& curr_inst_trafo_i = volume_i->get_instance_transformation().get_matrix(); + const bool curr_inst_left_handed = is_left_handed(curr_inst_trafo_i); + const Transform3d& old_inst_trafo_i = m_cache.volumes_data[i].get_instance_transform().get_matrix(); + bool mirrored = is_left_handed(curr_inst_trafo_i) != is_left_handed(old_inst_trafo_i); +// bool mirrored = curr_inst_trafo_i.linear().determinant() * old_inst_trafo_i.linear().determinant() < 0; + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + if (done.find(j) != done.end()) + continue; + GLVolume* volume_j = (*m_volumes)[j]; + if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) + continue; + const Transform3d& old_inst_trafo_j = m_cache.volumes_data[j].get_instance_transform().get_matrix(); + assert(is_rotation_xy_synchronized(old_inst_trafo_i, old_inst_trafo_j)); + Transform3d new_inst_trafo_j = volume_j->get_instance_transformation().get_matrix(); + if (sync_rotation_type != SyncRotationType::NONE || mirrored) + new_inst_trafo_j.linear() = (old_inst_trafo_j.linear() * old_inst_trafo_i.linear().inverse()) * curr_inst_trafo_i.linear(); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + new_inst_trafo_j.translation().z() = curr_inst_trafo_i.translation().z(); + assert(is_rotation_xy_synchronized(curr_inst_trafo_i, new_inst_trafo_j)); + volume_j->set_instance_transformation(new_inst_trafo_j); + done.insert(j); + } + } +#ifndef NDEBUG + verify_instances_rotation_synchronized(*m_model, *m_volumes); +#endif /* NDEBUG */ +} +#endif +#else +void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) { std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); From b20188c99481e0e926644f96556c107ccf9d467c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 25 Jan 2023 16:13:05 +0100 Subject: [PATCH 183/206] Disable debug benchmarks in sla print --- src/libslic3r/SLAPrint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d4023f64d..60c1209e6 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -13,7 +13,7 @@ #include #include - #define SLAPRINT_DO_BENCHMARK +// #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK #include From c4bd071295cb3d39ebbe9a9b665df46eaf60dd07 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 25 Jan 2023 16:25:42 +0100 Subject: [PATCH 184/206] typo in text --- src/slic3r/GUI/ConfigWizard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 6ba866ac1..224c0e30b 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1751,7 +1751,7 @@ PageBuildVolume::PageBuildVolume(ConfigWizard* parent) : ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1) , build_volume(new DiamTextCtrl(this)) { - append_text(_L("Set verctical size of your printer.")); + append_text(_L("Set vertical size of your printer.")); wxString value = "200"; build_volume->SetValue(value); From 821d2391b4224bfa3d8c8c3c50eb7ebb9059f1e7 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 20 Jan 2023 16:37:54 +0100 Subject: [PATCH 185/206] Added SupportPointCause describing the reason for the support point --- src/libslic3r/SupportSpotsGenerator.cpp | 96 +++++++++++++++---------- src/libslic3r/SupportSpotsGenerator.hpp | 51 ++++++++----- 2 files changed, 93 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 7c1c4f3c6..ecc03e3db 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +69,7 @@ public: float len; const ExtrusionEntity *origin_entity; - bool support_point_generated = false; + std::optional support_point_generated = {}; float form_quality = 1.0f; float curled_up_height = 0.0f; @@ -81,10 +82,6 @@ auto get_b(ExtrusionLine &&l) { return l.b; } namespace SupportSpotsGenerator { -SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction) - : position(position), force(force), spot_radius(spot_radius), direction(direction) -{} - using LD = AABBTreeLines::LinesDistancer; struct SupportGridFilter @@ -270,21 +267,27 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; curr_point.distance *= sign; + SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion; + if (entity->role().is_bridge() && !entity->role().is_perimeter()) { + potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor : SupportPointCause::LongBridge; + } + float max_bridge_len = params.bridge_distance / - ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature))); + ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) * + (1.0f + std::abs(curr_point.curvature))); if (curr_point.distance > 2.0f * flow_width) { line_out.form_quality = 0.8f; bridged_distance += line_len; if (bridged_distance > max_bridge_len) { - line_out.support_point_generated = true; + line_out.support_point_generated = potential_cause; bridged_distance = 0.0f; } } else if (curr_point.distance > flow_width * (1.0 + std::clamp(curr_point.curvature, -0.30f, 0.20f))) { bridged_distance += line_len; line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f; if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) { - line_out.support_point_generated = true; + line_out.support_point_generated = potential_cause; line_out.form_quality = 0.5f; bridged_distance = 0.0f; } @@ -349,11 +352,13 @@ public: Vec3f sticking_centroid_accumulator = Vec3f::Zero(); Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); float sticking_second_moment_of_area_covariance_accumulator{}; + bool connected_to_bed = false; ObjectPart() = default; void add(const ObjectPart &other) { + this->connected_to_bed = this->connected_to_bed || other.connected_to_bed; this->volume_centroid_accumulator += other.volume_centroid_accumulator; this->volume += other.volume; this->sticking_area += other.sticking_area; @@ -416,7 +421,7 @@ public: return elastic_section_modulus; } - float is_stable_while_extruding(const SliceConnection &connection, + std::tuple is_stable_while_extruding(const SliceConnection &connection, const ExtrusionLine &extruded_line, const Vec3f &extreme_point, float layer_z, @@ -434,7 +439,7 @@ public: // section for bed calculations { - if (this->sticking_area < EPSILON) return 1.0f; + if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator, @@ -475,16 +480,19 @@ public: BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; #endif - if (bed_total_torque > 0) return bed_total_torque / bed_conflict_torque_arm; + if (bed_total_torque > 0) { + return {bed_total_torque / bed_conflict_torque_arm, + (this->connected_to_bed ? SupportPointCause::SeparationFromBed : SupportPointCause::UnstableFloatingPart)}; + } } // section for weak connection calculations { - if (connection.area < EPSILON) return 1.0f; + if (connection.area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; Vec3f conn_centroid = connection.centroid_accumulator / connection.area; - if (layer_z - conn_centroid.z() < 3.0f) { return -1.0f; } + if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; } float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, connection.centroid_accumulator, connection.second_moment_of_area_accumulator, connection.second_moment_of_area_covariance_accumulator, @@ -514,7 +522,7 @@ public: BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; #endif - return conn_total_torque / conn_conflict_torque_arm; + return {conn_total_torque / conn_conflict_torque_arm, SupportPointCause::WeakObjectPart}; } } }; @@ -538,6 +546,7 @@ std::tuple build_object_part_from_slice(const LayerSlice &sli new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume; if (l->bottom_z() < EPSILON) { // layer attached on bed + new_object_part.connected_to_bed = true; float sticking_area = line.len * flow_width; new_object_part.sticking_area += sticking_area; Vec2f middle = Vec2f((line.a + line.b) / 2.0f); @@ -731,24 +740,25 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance // Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info, // and the support presence grid and add the point to the issues. auto reckon_new_support_point = [&part, &weakest_conn, &supp_points, &supports_presence_grid, ¶ms, - &layer_idx](const Vec3f &support_point, float force, const Vec2f &dir) { + &layer_idx](SupportPointCause cause, const Vec3f &support_point, float force, + const Vec2f &dir) { // if position is taken and point is for global stability (force > 0) or we are too close to the bed, do not add - // This allows local support points (e.g. bridging) to be generated densely + // This allows local support points (e.g. bridging) to be generated densely if ((supports_presence_grid.position_taken(support_point) && force > 0) || layer_idx <= 1) { return; } float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); - // add the stability effect of the point only if the spot is not taken, so that the densely created local support points do not add - // unrealistic amount of stability to the object (due to overlaping of local support points) + // add the stability effect of the point only if the spot is not taken, so that the densely created local support points do + // not add unrealistic amount of stability to the object (due to overlaping of local support points) if (!(supports_presence_grid.position_taken(support_point))) { part.add_support_point(support_point, area); } float radius = params.support_points_interface_radius; - supp_points.emplace_back(support_point, force, radius, dir); + supp_points.emplace_back(cause, support_point, force, radius, dir); supports_presence_grid.take_position(support_point); - + // The support point also increases the stability of the weakest connection of the object, which should be reflected if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist weakest_conn.area += area; @@ -768,9 +778,11 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx]; if (entity->role() == ExtrusionRole::BridgeInfill) { for (const ExtrusionLine &bridge : - check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines,prev_layer_boundary, params)) { - if (bridge.support_point_generated) { - reckon_new_support_point(create_support_point_position(bridge.b), -EPSILON, Vec2f::Zero()); + check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, prev_layer_boundary, + params)) { + if (bridge.support_point_generated.has_value()) { + reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b), + -EPSILON, Vec2f::Zero()); } } } @@ -783,10 +795,13 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance std::vector perims = check_extrusion_entity_stability(entity, perimeter_region, prev_layer_ext_perim_lines,prev_layer_boundary, params); for (const ExtrusionLine &perim : perims) { - if (perim.support_point_generated) { - reckon_new_support_point(create_support_point_position(perim.b), -EPSILON, Vec2f::Zero()); + if (perim.support_point_generated.has_value()) { + reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON, + Vec2f::Zero()); + } + if (perim.is_external_perimeter()) { + current_slice_ext_perims_lines.push_back(perim); } - if (perim.is_external_perimeter()) { current_slice_ext_perims_lines.push_back(perim); } } } } @@ -795,7 +810,8 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance float unchecked_dist = params.min_distance_between_support_points + 1.0f; for (const ExtrusionLine &line : current_slice_ext_perims_lines) { - if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) || line.len < EPSILON) { + if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) || + line.len < EPSILON) { unchecked_dist += line.len; } else { unchecked_dist = line.len; @@ -803,8 +819,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance auto [dist, nidx, nearest_point] = current_slice_lines_distancer.distance_from_lines_extra(pivot_site_search_point); Vec3f support_point = create_support_point_position(nearest_point); - auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params); - if (force > 0) { reckon_new_support_point(support_point, force, (line.b - line.a).normalized()); } + auto [force, cause] = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params); + if (force > 0) { + reckon_new_support_point(cause, support_point, force, (line.b - line.a).normalized()); + } } } current_layer_ext_perims_lines.insert(current_layer_ext_perims_lines.end(), current_slice_ext_perims_lines.begin(), @@ -827,13 +845,18 @@ void debug_export(SupportPoints support_points, std::string file_name) } for (size_t i = 0; i < support_points.size(); ++i) { - if (support_points[i].force <= 0) { - fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1), - support_points[i].position(2), 0.0, 1.0, 0.0); - } else { - fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1), - support_points[i].position(2), 1.0, 0.0, 0.0); + Vec3f color{1.0f, 1.0f, 1.0f}; + switch (support_points[i].cause) { + case SupportPointCause::FloatingBridgeAnchor: color = {0.863281f, 0.109375f, 0.113281f}; break; //RED + case SupportPointCause::LongBridge: color = {0.960938f, 0.90625f, 0.0625f}; break; // YELLOW + case SupportPointCause::FloatingExtrusion: color = {0.921875f, 0.515625f, 0.101563f}; break; // ORANGE + case SupportPointCause::SeparationFromBed: color = {0.0f, 1.0f, 0.0}; break; // GREEN + case SupportPointCause::UnstableFloatingPart: color = {0.105469f, 0.699219f, 0.84375f}; break; // BLUE + case SupportPointCause::WeakObjectPart: color = {0.609375f, 0.210938f, 0.621094f}; break; // PURPLE } + + fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1), + support_points[i].position(2), color[0], color[1], color[2]); } fclose(fp); @@ -841,9 +864,6 @@ void debug_export(SupportPoints support_points, std::string file_name) } #endif -// std::vector quick_search(const PrintObject *po, const Params ¶ms) { -// return {}; -// } SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms) { SupportPoints supp_points = check_stability(po, cancel_func, params); diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 8e8983ac1..b36566b04 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -35,7 +35,6 @@ struct Params { const float min_distance_between_support_points = 3.0f; //mm const float support_points_interface_radius = 1.5f; // mm - const float connections_min_considerable_area = 1.5f; //mm^2 const float min_distance_to_allow_local_supports = 1.0f; //mm std::string filament_type; @@ -52,6 +51,8 @@ struct Params { return 0.018 * 1e6; } else if (filament_type == "PET" || filament_type == "PETG") { return 0.3 * 1e6; + } else if (filament_type == "ABS" || filament_type == "ASA") { + return 0.1 * 1e6; //TODO do measurements } else { //PLA default value - defensive approach, PLA has quite low adhesion return 0.018 * 1e6; } @@ -63,30 +64,48 @@ struct Params { } }; -// The support points are generated for two reasons: -// 1. Local extrusion support for extrusions that are printed in the air and would not +enum class SupportPointCause { + LongBridge, // point generated on bridge extrusion longer than the allowed length + FloatingBridgeAnchor, // point generated on unsupported bridge endpoint + FloatingExtrusion, // point generated on extrusion that does not hold on its own - huge overhangs + SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too low and there is risk of separation (brim may help) + UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here) + WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass) + }; + +// The support points can be sorted into two groups +// 1. Local extrusion support for extrusions that are printed in the air and would not // withstand on their own (too long bridges, sharp turns in large overhang, concave bridge holes, etc.) // These points have negative force (-EPSILON) and Vec2f::Zero() direction -// The algorithm still expects that these points will be supported and accounts for them in the global stability check -// 2. Global stability support points are generated at each spot, where the algorithm detects that extruding the current line -// may cause separation of the object part from the bed and/or its support spots or crack in the weak connection of the object parts -// The generated point's direction is the estimated falling direction of the object part, and the force is equal to te difference +// The algorithm still expects that these points will be supported and accounts for them in the global stability check. +// 2. Global stability support points are generated at each spot, where the algorithm detects that extruding the current line +// may cause separation of the object part from the bed and/or its support spots or crack in the weak connection of the object parts. +// The generated point's direction is the estimated falling direction of the object part, and the force is equal to te difference // between forces that destabilize the object (extruder conflicts with curled filament, weight if instable center of mass, bed movements etc) -// and forces that stabilize the object (bed adhesion, other support spots adhesion, weight if stable center of mass) +// and forces that stabilize the object (bed adhesion, other support spots adhesion, weight if stable center of mass). // Note that the force is only the difference - the amount needed to stabilize the object again. -struct SupportPoint { - SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction); - bool is_local_extrusion_support() const { return force < 0; } +struct SupportPoint +{ + SupportPoint(SupportPointCause cause, const Vec3f &position, float force, float spot_radius, const Vec2f &direction) + : cause(cause), position(position), force(force), spot_radius(spot_radius), direction(direction) + {} + + bool is_local_extrusion_support() const + { + return cause == SupportPointCause::LongBridge || cause == SupportPointCause::FloatingExtrusion; + } bool is_global_object_support() const { return !is_local_extrusion_support(); } - //position is in unscaled coords. The z coordinate is aligned with the layers bottom_z coordiantes + SupportPointCause cause; // reason why this support point was generated. Used for the user alerts + // position is in unscaled coords. The z coordinate is aligned with the layers bottom_z coordiantes Vec3f position; - // force that destabilizes the object to the point of falling/breaking. It is in g*mm/s^2 units - // values gathered from large XL print: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103 + // force that destabilizes the object to the point of falling/breaking. g*mm/s^2 units + // It is valid only for global_object_support. For local extrusion support points, the force is -EPSILON + // values gathered from large XL model: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103 // For reference 18713800 is weight of 1.8 Kg object, 329103 is weight of 0.03 Kg - // The final printed object weight was approx 0.5 Kg + // The final sliced object weight was approx 0.5 Kg float force; - // Expected spot size. The support point strength is calculated from the area defined by this value. + // Expected spot size. The support point strength is calculated from the area defined by this value. // Currently equal to the support_points_interface_radius parameter above float spot_radius; // direction of the fall of the object (z part is neglected) From f2deefd1dec7d95b95c41299510f16f9d1a58857 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 23 Jan 2023 13:00:36 +0100 Subject: [PATCH 186/206] Support spot generator improvement - supporting bridges only in one direction --- src/libslic3r/GCode/ExtrusionProcessor.hpp | 56 ++++++++++--------- src/libslic3r/SupportSpotsGenerator.cpp | 65 +++++++++++++++++++--- src/libslic3r/SupportSpotsGenerator.hpp | 2 +- 3 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 5a13e5484..7d8cc4c73 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -122,39 +122,18 @@ std::vector estimate_points_properties(const std::vector

CurvatureEstimator cestim; auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast(); }; - std::vector

extrusion_points; - { - if (max_line_length <= 0) { - extrusion_points = input_points; - } else { - extrusion_points.reserve(input_points.size() * 2); - for (size_t i = 0; i + 1 < input_points.size(); i++) { - const P &curr = input_points[i]; - const P &next = input_points[i + 1]; - extrusion_points.push_back(curr); - auto len = maybe_unscale(next - curr).squaredNorm(); - double t = sqrt((max_line_length * max_line_length) / len); - size_t new_point_count = 1.0 / (t + EPSILON); - for (size_t j = 1; j < new_point_count + 1; j++) { - extrusion_points.push_back(curr * (1.0 - j * t) + next * (j * t)); - } - } - extrusion_points.push_back(input_points.back()); - } - } - std::vector points; - points.reserve(extrusion_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1)); + points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1)); { - ExtendedPoint start_point{maybe_unscale(extrusion_points.front())}; + ExtendedPoint start_point{maybe_unscale(input_points.front())}; auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(start_point.position.cast()); start_point.distance = distance + boundary_offset; start_point.nearest_prev_layer_line = nearest_line; points.push_back(start_point); } - for (size_t i = 1; i < extrusion_points.size(); i++) { - ExtendedPoint next_point{maybe_unscale(extrusion_points[i])}; + for (size_t i = 1; i < input_points.size(); i++) { + ExtendedPoint next_point{maybe_unscale(input_points[i])}; auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(next_point.position.cast()); next_point.distance = distance + boundary_offset; next_point.nearest_prev_layer_line = nearest_line; @@ -172,7 +151,7 @@ std::vector estimate_points_properties(const std::vector

if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) { std::vector new_points; - new_points.reserve(points.size() * 2); + new_points.reserve(points.size()*2); new_points.push_back(points.front()); for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) { const ExtendedPoint &curr = points[point_idx]; @@ -201,7 +180,30 @@ std::vector estimate_points_properties(const std::vector

} new_points.push_back(next); } - points = std::move(new_points); + points = new_points; + } + + if (max_line_length > 0) { + std::vector new_points; + new_points.reserve(points.size()*2); + { + for (size_t i = 0; i + 1 < points.size(); i++) { + const ExtendedPoint &curr = points[i]; + const ExtendedPoint &next = points[i + 1]; + new_points.push_back(curr); + double len = (next.position - curr.position).squaredNorm(); + double t = sqrt((max_line_length * max_line_length) / len); + size_t new_point_count = 1.0 / t; + for (size_t j = 1; j < new_point_count + 1; j++) { + Vec2d pos = curr.position * (1.0 - j * t) + next.position * (j * t); + auto [p_dist, p_near_l, + p_x] = unscaled_prev_layer.template distance_from_lines_extra(pos.cast()); + new_points.push_back(ExtendedPoint{pos, float(p_dist + boundary_offset), p_near_l}); + } + } + new_points.push_back(new_points.back()); + } + points = new_points; } for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) { diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index ecc03e3db..73eae069f 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -237,13 +237,66 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end()); } return checked_lines_out; + } else if (entity->role().is_bridge() && !entity->role().is_perimeter()) { + // pure bridges are handled separately, beacuse we need to align the forward and backward direction support points + if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { + return {}; + } + const float flow_width = get_flow_width(layer_region, entity->role()); + std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, + prev_layer_boundary, flow_width, + params.bridge_distance); + + std::vector lines_out; + lines_out.reserve(annotated_points.size()); + float bridged_distance = 0.0f; + + std::optional bridging_dir{}; + + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i - 1]; + + SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor : + SupportPointCause::LongBridge; + float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f; + Vec2d line_dir = (curr_point.position - prev_point.position).normalized(); + + ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast() : curr_point.position.cast(), + curr_point.position.cast(), line_len, entity}; + + float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f, + params.bridge_distance / + ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) * + (1.0f + std::abs(curr_point.curvature)))); + + if (!bridging_dir.has_value() && curr_point.distance > flow_width && line_len > params.bridge_distance * 0.6) { + bridging_dir = (prev_point.position - curr_point.position).normalized(); + } + + if (curr_point.distance > flow_width && potential_cause == SupportPointCause::LongBridge && bridging_dir.has_value() && + bridging_dir->dot(line_dir) < 0.8) { // skip backward direction of bridge - supported by forward points enough + bridged_distance += line_len; + } else if (curr_point.distance > flow_width) { + bridged_distance += line_len; + if (bridged_distance > max_bridge_len) { + bridged_distance = 0.0f; + line_out.support_point_generated = potential_cause; + } + } else { + bridged_distance = 0.0f; + } + + lines_out.push_back(line_out); + } + return lines_out; + } else { // single extrusion path, with possible varying parameters if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { return {}; } const float flow_width = get_flow_width(layer_region, entity->role()); - // Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, prev_layer_lines, flow_width, @@ -268,13 +321,11 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit curr_point.distance *= sign; SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion; - if (entity->role().is_bridge() && !entity->role().is_perimeter()) { - potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor : SupportPointCause::LongBridge; - } - float max_bridge_len = params.bridge_distance / - ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) * - (1.0f + std::abs(curr_point.curvature))); + float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f, + params.bridge_distance / + ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) * + (1.0f + std::abs(curr_point.curvature)))); if (curr_point.distance > 2.0f * flow_width) { line_out.form_quality = 0.8f; diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index b36566b04..9ef3b8637 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -68,7 +68,7 @@ enum class SupportPointCause { LongBridge, // point generated on bridge extrusion longer than the allowed length FloatingBridgeAnchor, // point generated on unsupported bridge endpoint FloatingExtrusion, // point generated on extrusion that does not hold on its own - huge overhangs - SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too low and there is risk of separation (brim may help) + SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too small and there is a risk of separation (brim may help) UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here) WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass) }; From 41f1b83ae490acdc607aacb38e0aa3c3771bc28c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 23 Jan 2023 16:45:06 +0100 Subject: [PATCH 187/206] raft layers, partial objects memory, params acceleration --- src/libslic3r/PrintObject.cpp | 10 +++-- src/libslic3r/SupportSpotsGenerator.cpp | 56 ++++++++++++++++++------- src/libslic3r/SupportSpotsGenerator.hpp | 47 +++++++++++++++------ 3 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dc75fb8a3..83d25aefb 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -427,9 +427,10 @@ void PrintObject::generate_support_spots() BOOST_LOG_TRIVIAL(debug) << "Searching support spots - start"; m_print->set_status(75, L("Searching support spots")); if (!this->shared_regions()->generated_support_points.has_value()) { - PrintTryCancel cancel_func = m_print->make_try_cancel(); - SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; - SupportSpotsGenerator::SupportPoints supp_points = SupportSpotsGenerator::full_search(this, cancel_func, params); + PrintTryCancel cancel_func = m_print->make_try_cancel(); + SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values, + float(this->print()->m_config.perimeter_acceleration.getFloat())}; + auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params); this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points}; m_print->throw_if_canceled(); } @@ -468,7 +469,8 @@ void PrintObject::estimate_curled_extrusions() // Estimate curling of support material and add it to the malformaition lines of each layer float support_flow_width = support_material_flow(this, this->config().layer_height).width(); - SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; + SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values, + float(this->print()->config().perimeter_acceleration.getFloat())}; SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params); SupportSpotsGenerator::estimate_malformations(this->layers(), params); m_print->throw_if_canceled(); diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 73eae069f..0b0c1b457 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -579,12 +579,13 @@ public: }; // return new object part and actual area covered by extrusions -std::tuple build_object_part_from_slice(const LayerSlice &slice, const Layer *layer) +std::tuple build_object_part_from_slice(const LayerSlice &slice, const Layer *layer, const Params& params) { ObjectPart new_object_part; float area_covered_by_extrusions = 0; - auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions](const ExtrusionEntity *e, const LayerRegion *region) { + auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions, ¶ms](const ExtrusionEntity *e, + const LayerRegion *region) { float flow_width = get_flow_width(region, e->role()); const Layer *l = region->layer(); float slice_z = l->slice_z; @@ -596,9 +597,9 @@ std::tuple build_object_part_from_slice(const LayerSlice &sli new_object_part.volume += volume; new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume; - if (l->bottom_z() < EPSILON) { // layer attached on bed + if (l->id() == params.raft_layers_count) { // layer attached on bed/raft new_object_part.connected_to_bed = true; - float sticking_area = line.len * flow_width; + float sticking_area = line.len * flow_width; new_object_part.sticking_area += sticking_area; Vec2f middle = Vec2f((line.a + line.b) / 2.0f); new_object_part.sticking_centroid_accumulator += sticking_area * to_3d(middle, slice_z); @@ -681,11 +682,12 @@ public: } }; -SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms) +std::tuple check_stability(const PrintObject *po, const PrintTryCancel &cancel_func, const Params ¶ms) { - SupportPoints supp_points{}; + SupportPoints supp_points{}; SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points); ActiveObjectParts active_object_parts{}; + PartialObjects partial_objects{}; LD prev_layer_ext_perim_lines; std::unordered_map prev_slice_idx_to_object_part_mapping; @@ -693,6 +695,14 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance std::unordered_map prev_slice_idx_to_weakest_connection; std::unordered_map next_slice_idx_to_weakest_connection; + auto remember_partial_object = [&active_object_parts, &partial_objects](size_t object_part_id) { + auto object_part = active_object_parts.access(object_part_id); + if (object_part.volume > EPSILON) { + partial_objects.emplace_back(object_part.volume_centroid_accumulator / object_part.volume, object_part.volume, + object_part.connected_to_bed); + } + }; + for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) { cancel_func(); const Layer *layer = po->get_layer(layer_idx); @@ -701,7 +711,7 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { const LayerSlice &slice = layer->lslices_ex.at(slice_idx); - auto [new_part, covered_area] = build_object_part_from_slice(slice, layer); + auto [new_part, covered_area] = build_object_part_from_slice(slice, layer, params); SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer); #ifdef DETAILED_DEBUG_LOGS @@ -730,7 +740,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance final_part_id = *parts_ids.begin(); for (size_t part_id : parts_ids) { - if (final_part_id != part_id) { active_object_parts.merge(part_id, final_part_id); } + if (final_part_id != part_id) { + remember_partial_object(part_id); + active_object_parts.merge(part_id, final_part_id); + } } } auto estimate_conn_strength = [bottom_z](const SliceConnection &conn) { @@ -881,11 +894,16 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance } // slice iterations prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines); } // layer iterations - return supp_points; + + for (const auto& active_obj_pair : prev_slice_idx_to_object_part_mapping) { + remember_partial_object(active_obj_pair.second); + } + + return {supp_points, partial_objects}; } #ifdef DEBUG_FILES -void debug_export(SupportPoints support_points, std::string file_name) +void debug_export(const SupportPoints& support_points,const PartialObjects& objects, std::string file_name) { Slic3r::CNumericLocalesSetter locales_setter; { @@ -910,19 +928,29 @@ void debug_export(SupportPoints support_points, std::string file_name) support_points[i].position(2), color[0], color[1], color[2]); } + for (size_t i = 0; i < objects.size(); ++i) { + Vec3f color{1.0f, 0.0f, 1.0f}; + if (objects[i].connected_to_bed) { + color = {1.0f, 0.0f, 0.0f}; + } + fprintf(fp, "v %f %f %f %f %f %f\n", objects[i].centroid(0), objects[i].centroid(1), objects[i].centroid(2), color[0], + color[1], color[2]); + } + fclose(fp); } } #endif -SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms) +std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms) { - SupportPoints supp_points = check_stability(po, cancel_func, params); + auto results = check_stability(po, cancel_func, params); #ifdef DEBUG_FILES - debug_export(supp_points, "issues"); + auto [supp_points, objects] = results; + debug_export(supp_points, objects, "issues"); #endif - return supp_points; + return results; } void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, const Params ¶ms) diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 9ef3b8637..498795993 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -5,31 +5,38 @@ #include "Line.hpp" #include "PrintBase.hpp" #include +#include #include namespace Slic3r { namespace SupportSpotsGenerator { -struct Params { - Params(const std::vector &filament_types) { +struct Params +{ + Params(const std::vector &filament_types, float max_acceleration, size_t raft_layers_count) + : max_acceleration(max_acceleration), raft_layers_count(raft_layers_count) + { if (filament_types.size() > 1) { BOOST_LOG_TRIVIAL(warning) - << "SupportSpotsGenerator does not currently handle different materials properly, only first will be used"; + << "SupportSpotsGenerator does not currently handle different materials properly, only first will be used"; } if (filament_types.empty() || filament_types[0].empty()) { - BOOST_LOG_TRIVIAL(error) - << "SupportSpotsGenerator error: empty filament_type"; + BOOST_LOG_TRIVIAL(error) << "SupportSpotsGenerator error: empty filament_type"; filament_type = std::string("PLA"); } else { filament_type = filament_types[0]; - BOOST_LOG_TRIVIAL(debug) - << "SupportSpotsGenerator: applying filament type: " << filament_type; + BOOST_LOG_TRIVIAL(debug) << "SupportSpotsGenerator: applying filament type: " << filament_type; } } // the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2] - const float bridge_distance = 12.0f; //mm + const float bridge_distance = 12.0f; // mm + const float max_acceleration; // mm/s^2 ; max acceleration of object (bed) in XY (NOTE: The max hit is received by the object in the + // jerk phase, so the usual machine limits are too low) + const size_t raft_layers_count; + std::string filament_type; + const std::pair malformation_distance_factors = std::pair { 0.4, 1.2 }; const float max_curled_height_factor = 10.0f; @@ -37,9 +44,7 @@ struct Params { const float support_points_interface_radius = 1.5f; // mm const float min_distance_to_allow_local_supports = 1.0f; //mm - std::string filament_type; const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position. - const float max_acceleration = 9 * 1000.0f; // mm/s^2 ; max acceleration of object (bed) in XY (NOTE: The max hit is received by the object in the jerk phase, so the usual machine limits are too low) const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials. const float standard_extruder_conflict_force = 20.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... ); @@ -47,6 +52,10 @@ struct Params { // MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface double get_bed_adhesion_yield_strength() const { + if (raft_layers_count > 0) { + return get_support_spots_adhesion_strength(); + } + if (filament_type == "PLA") { return 0.018 * 1e6; } else if (filament_type == "PET" || filament_type == "PETG") { @@ -67,7 +76,7 @@ struct Params { enum class SupportPointCause { LongBridge, // point generated on bridge extrusion longer than the allowed length FloatingBridgeAnchor, // point generated on unsupported bridge endpoint - FloatingExtrusion, // point generated on extrusion that does not hold on its own - huge overhangs + FloatingExtrusion, // point generated on extrusion that does not hold on its own SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too small and there is a risk of separation (brim may help) UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here) WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass) @@ -118,8 +127,20 @@ struct Malformations { std::vector layers; //for each layer }; -// std::vector quick_search(const PrintObject *po, const Params ¶ms); -SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); +struct PartialObject +{ + PartialObject(Vec3f centroid, float volume, bool connected_to_bed) + : centroid(centroid), volume(volume), connected_to_bed(connected_to_bed) + {} + + Vec3f centroid; + float volume; + bool connected_to_bed; +}; + +using PartialObjects = std::vector; + +std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); void estimate_malformations(std::vector &layers, const Params ¶ms); From fb4c1bf61275d986fb3c70e19547fb0f2d7f8b3c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 23 Jan 2023 16:59:45 +0100 Subject: [PATCH 188/206] compilation fix --- src/libslic3r/PrintObject.cpp | 7 +++++-- src/libslic3r/SupportSpotsGenerator.hpp | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 83d25aefb..95e34bd05 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -429,7 +429,8 @@ void PrintObject::generate_support_spots() if (!this->shared_regions()->generated_support_points.has_value()) { PrintTryCancel cancel_func = m_print->make_try_cancel(); SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values, - float(this->print()->m_config.perimeter_acceleration.getFloat())}; + float(this->print()->m_config.perimeter_acceleration.getFloat()), + this->config().raft_layers.getInt()}; auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params); this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points}; m_print->throw_if_canceled(); @@ -470,7 +471,9 @@ void PrintObject::estimate_curled_extrusions() // Estimate curling of support material and add it to the malformaition lines of each layer float support_flow_width = support_material_flow(this, this->config().layer_height).width(); SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values, - float(this->print()->config().perimeter_acceleration.getFloat())}; + float(this->print()->config().perimeter_acceleration.getFloat()), + this->config().raft_layers.getInt() + }; SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params); SupportSpotsGenerator::estimate_malformations(this->layers(), params); m_print->throw_if_canceled(); diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 498795993..98dda20e2 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -14,7 +14,7 @@ namespace SupportSpotsGenerator { struct Params { - Params(const std::vector &filament_types, float max_acceleration, size_t raft_layers_count) + Params(const std::vector &filament_types, float max_acceleration, int raft_layers_count) : max_acceleration(max_acceleration), raft_layers_count(raft_layers_count) { if (filament_types.size() > 1) { @@ -34,7 +34,7 @@ struct Params const float bridge_distance = 12.0f; // mm const float max_acceleration; // mm/s^2 ; max acceleration of object (bed) in XY (NOTE: The max hit is received by the object in the // jerk phase, so the usual machine limits are too low) - const size_t raft_layers_count; + const int raft_layers_count; std::string filament_type; const std::pair malformation_distance_factors = std::pair { 0.4, 1.2 }; From a4de5c6553678f7ccc631c1c74c129aae69e699a Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 24 Jan 2023 11:54:16 +0100 Subject: [PATCH 189/206] initial warnings version --- src/libslic3r/PrintObject.cpp | 138 +++++++++++++++++++++--- src/libslic3r/SupportSpotsGenerator.cpp | 7 ++ src/libslic3r/SupportSpotsGenerator.hpp | 7 +- 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 95e34bd05..f163bfbc8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,4 +1,5 @@ #include "Exception.hpp" +#include "Point.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -21,9 +22,13 @@ #include "SupportSpotsGenerator.hpp" #include "TriangleSelectorWrapper.hpp" #include "format.hpp" +#include "libslic3r.h" +#include +#include #include #include +#include #include #include @@ -406,21 +411,6 @@ void PrintObject::ironing() } } - -/* -std::vector problematic_layers = SupportSpotsGenerator::quick_search(this); - if (!problematic_layers.empty()) { - std::cout << "Object needs supports" << std::endl; - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - L("Supportable issues found. Consider enabling supports for this object")); - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - L("Supportable issues found. Consider enabling supports for this object")); - for (size_t index = 0; index < std::min(problematic_layers.size(), size_t(4)); ++index) { - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - format(L("Layer with issues: %1%"), problematic_layers[index] + 1)); - } - } - */ void PrintObject::generate_support_spots() { if (this->set_started(posSupportSpotsSearch)) { @@ -434,6 +424,124 @@ void PrintObject::generate_support_spots() auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params); this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points}; m_print->throw_if_canceled(); + + auto check_problems = [&]() { + std::unordered_map sp_by_cause{}; + for (const SupportSpotsGenerator::SupportPoint &sp : supp_points) { + sp_by_cause[sp.cause].push_back(sp); + } + + if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::SeparationFromBed].empty()) { + this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + L("Object part may break from the bed. Consider adding brim and/or supports.")); + } + + std::reverse(partial_objects.begin(), partial_objects.end()); + std::sort(partial_objects.begin(), partial_objects.end(), + [](const SupportSpotsGenerator::PartialObject &left, const SupportSpotsGenerator::PartialObject &right) { + return left.volume > right.volume; + }); + + float max_volume_part = partial_objects.front().volume; + for (const SupportSpotsGenerator::PartialObject &p : partial_objects) { + if (p.volume > max_volume_part / 1000.0f && !p.connected_to_bed) { + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + L("Floating object parts detected. Please add supports.")); + return; + } + } + + if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::WeakObjectPart].empty()) { + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + L("Thin parts of the object may break. Please add supports.")); + return; + } + + if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor].empty()) { + Vec3f last_pos = Vec3f::Zero(); + size_t count = 0; + for (const SupportSpotsGenerator::SupportPoint &sp : + sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor]) { + if ((sp.position - last_pos).squaredNorm() < 9.0f) { + count++; + last_pos = sp.position; + } else { + last_pos = sp.position; + count = 1; + } + if (count > 1) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, + L("Bridges without supported endpoints will collapse. Please add supports. ")); + break; + } + } + } + + if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::LongUnsupportedExtrusion].empty()) { + Vec3f last_pos = Vec3f::Zero(); + size_t count = 0; + for (const SupportSpotsGenerator::SupportPoint &sp : + sp_by_cause[SupportSpotsGenerator::SupportPointCause::LongUnsupportedExtrusion]) { + if ((sp.position - last_pos).squaredNorm() < 9.0f) { + count++; + last_pos = sp.position; + } else { + last_pos = sp.position; + count = 1; + } + if (count > 1) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, + L("Long unsupported extrusions will collapse. Please add supports. ")); + break; + } + } + } + + if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::LongBridge].empty()) { + this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + L("There are bridges longer than allowed distance. Consider adding supports. ")); + } + + if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingExtrusion].empty()) { + Vec3f last_pos = Vec3f::Zero(); + size_t count = 0; + bool small_warning = false; + for (const SupportSpotsGenerator::SupportPoint &sp : + sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingExtrusion]) { + if ((sp.position - last_pos).squaredNorm() < + params.bridge_distance + EPSILON) { + count++; + last_pos = sp.position; + } else { + if (count > 6) { + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + L("Object has large part with loose extrusions. Please enable supports. ")); + small_warning = false; + break; + } + if (count > 3) { + small_warning = true; + } + + last_pos = sp.position; + count = 1; + } + } + if (small_warning) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::NON_CRITICAL, + L("Object has parts with loose extrusions and may look bad. Consider enabling supports. ")); + } else if (sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingExtrusion].size() > max_volume_part / 100) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::NON_CRITICAL, + L("There are many loose surface extrusions on this object. Consider enabling supports. ")); + } + } + }; + + check_problems(); } BOOST_LOG_TRIVIAL(debug) << "Searching support spots - end"; this->set_done(posSupportSpotsSearch); diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 0b0c1b457..464ee1d67 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -321,6 +321,12 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit curr_point.distance *= sign; SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion; + if (curr_point.distance > flow_width * 5.0) { + if (std::abs(curr_point.curvature) > 0.1) + potential_cause = SupportPointCause::LongUnsupportedExtrusion; + else + potential_cause = SupportPointCause::LongBridge; + } float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f, params.bridge_distance / @@ -919,6 +925,7 @@ void debug_export(const SupportPoints& support_points,const PartialObjects& obje case SupportPointCause::FloatingBridgeAnchor: color = {0.863281f, 0.109375f, 0.113281f}; break; //RED case SupportPointCause::LongBridge: color = {0.960938f, 0.90625f, 0.0625f}; break; // YELLOW case SupportPointCause::FloatingExtrusion: color = {0.921875f, 0.515625f, 0.101563f}; break; // ORANGE + case SupportPointCause::LongUnsupportedExtrusion: color = {0.863281f, 0.109375f, 0.113281f}; break; // RED case SupportPointCause::SeparationFromBed: color = {0.0f, 1.0f, 0.0}; break; // GREEN case SupportPointCause::UnstableFloatingPart: color = {0.105469f, 0.699219f, 0.84375f}; break; // BLUE case SupportPointCause::WeakObjectPart: color = {0.609375f, 0.210938f, 0.621094f}; break; // PURPLE diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 98dda20e2..56762d544 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -32,8 +32,8 @@ struct Params // the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2] const float bridge_distance = 12.0f; // mm - const float max_acceleration; // mm/s^2 ; max acceleration of object (bed) in XY (NOTE: The max hit is received by the object in the - // jerk phase, so the usual machine limits are too low) + const float max_acceleration; // mm/s^2 ; max acceleration of object in XY -- should be applicable only to printers with bed slinger, + // however we do not have such info yet. The force is usually small anyway, so not such a big deal to include it everytime const int raft_layers_count; std::string filament_type; @@ -74,9 +74,10 @@ struct Params }; enum class SupportPointCause { - LongBridge, // point generated on bridge extrusion longer than the allowed length + LongBridge, // point generated on bridge and straight perimeter extrusion longer than the allowed length FloatingBridgeAnchor, // point generated on unsupported bridge endpoint FloatingExtrusion, // point generated on extrusion that does not hold on its own + LongUnsupportedExtrusion, // similar to above, but with large distance to object. This really needs supports. SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too small and there is a risk of separation (brim may help) UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here) WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass) From c31e3ec1a2436b3fda69ad343bc6a45c96faabb0 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 25 Jan 2023 14:18:14 +0100 Subject: [PATCH 190/206] Bugfix in extrusion quality estimator, Refactoring of alerts, rename of autogenerate button --- src/libslic3r/GCode/ExtrusionProcessor.hpp | 2 +- src/libslic3r/PrintObject.cpp | 140 ++++--------------- src/libslic3r/SupportSpotsGenerator.cpp | 111 +++++++++++++-- src/libslic3r/SupportSpotsGenerator.hpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 12 +- 5 files changed, 143 insertions(+), 134 deletions(-) diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 7d8cc4c73..1525624da 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -201,7 +201,7 @@ std::vector estimate_points_properties(const std::vector

new_points.push_back(ExtendedPoint{pos, float(p_dist + boundary_offset), p_near_l}); } } - new_points.push_back(new_points.back()); + new_points.push_back(points.back()); } points = new_points; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f163bfbc8..09b35157c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,4 +1,5 @@ #include "Exception.hpp" +#include "KDTreeIndirect.hpp" #include "Point.hpp" #include "Print.hpp" #include "BoundingBox.hpp" @@ -34,6 +35,7 @@ #include #include +#include using namespace std::literals; @@ -425,123 +427,37 @@ void PrintObject::generate_support_spots() this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points}; m_print->throw_if_canceled(); - auto check_problems = [&]() { - std::unordered_map sp_by_cause{}; - for (const SupportSpotsGenerator::SupportPoint &sp : supp_points) { - sp_by_cause[sp.cause].push_back(sp); - } - - if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::SeparationFromBed].empty()) { - this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, - L("Object part may break from the bed. Consider adding brim and/or supports.")); - } - - std::reverse(partial_objects.begin(), partial_objects.end()); - std::sort(partial_objects.begin(), partial_objects.end(), - [](const SupportSpotsGenerator::PartialObject &left, const SupportSpotsGenerator::PartialObject &right) { - return left.volume > right.volume; - }); - - float max_volume_part = partial_objects.front().volume; - for (const SupportSpotsGenerator::PartialObject &p : partial_objects) { - if (p.volume > max_volume_part / 1000.0f && !p.connected_to_bed) { - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - L("Floating object parts detected. Please add supports.")); - return; + auto alert_fn = [&](PrintStateBase::WarningLevel level, SupportSpotsGenerator::SupportPointCause cause) { + switch (cause) { + case SupportSpotsGenerator::SupportPointCause::LongBridge: + this->active_step_add_warning(level, L("There are bridges longer than allowed distance. Consider adding supports. ")); + break; + case SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor: + this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed.")); + break; + case SupportSpotsGenerator::SupportPointCause::FloatingExtrusion: + if (level == PrintStateBase::WarningLevel::CRITICAL) { + this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed.")); + } else { + this->active_step_add_warning(level, L("Some unspported extrusions found. Consider adding supports. ")); } - } - - if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::WeakObjectPart].empty()) { + break; + case SupportSpotsGenerator::SupportPointCause::SeparationFromBed: + this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports.")); + break; + case SupportSpotsGenerator::SupportPointCause::UnstableFloatingPart: + this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed.")); + break; + case SupportSpotsGenerator::SupportPointCause::WeakObjectPart: this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - L("Thin parts of the object may break. Please add supports.")); - return; - } - - if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor].empty()) { - Vec3f last_pos = Vec3f::Zero(); - size_t count = 0; - for (const SupportSpotsGenerator::SupportPoint &sp : - sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor]) { - if ((sp.position - last_pos).squaredNorm() < 9.0f) { - count++; - last_pos = sp.position; - } else { - last_pos = sp.position; - count = 1; - } - if (count > 1) { - this->active_step_add_warning( - PrintStateBase::WarningLevel::CRITICAL, - L("Bridges without supported endpoints will collapse. Please add supports. ")); - break; - } - } - } - - if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::LongUnsupportedExtrusion].empty()) { - Vec3f last_pos = Vec3f::Zero(); - size_t count = 0; - for (const SupportSpotsGenerator::SupportPoint &sp : - sp_by_cause[SupportSpotsGenerator::SupportPointCause::LongUnsupportedExtrusion]) { - if ((sp.position - last_pos).squaredNorm() < 9.0f) { - count++; - last_pos = sp.position; - } else { - last_pos = sp.position; - count = 1; - } - if (count > 1) { - this->active_step_add_warning( - PrintStateBase::WarningLevel::CRITICAL, - L("Long unsupported extrusions will collapse. Please add supports. ")); - break; - } - } - } - - if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::LongBridge].empty()) { - this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, - L("There are bridges longer than allowed distance. Consider adding supports. ")); - } - - if (!sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingExtrusion].empty()) { - Vec3f last_pos = Vec3f::Zero(); - size_t count = 0; - bool small_warning = false; - for (const SupportSpotsGenerator::SupportPoint &sp : - sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingExtrusion]) { - if ((sp.position - last_pos).squaredNorm() < - params.bridge_distance + EPSILON) { - count++; - last_pos = sp.position; - } else { - if (count > 6) { - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - L("Object has large part with loose extrusions. Please enable supports. ")); - small_warning = false; - break; - } - if (count > 3) { - small_warning = true; - } - - last_pos = sp.position; - count = 1; - } - } - if (small_warning) { - this->active_step_add_warning( - PrintStateBase::WarningLevel::NON_CRITICAL, - L("Object has parts with loose extrusions and may look bad. Consider enabling supports. ")); - } else if (sp_by_cause[SupportSpotsGenerator::SupportPointCause::FloatingExtrusion].size() > max_volume_part / 100) { - this->active_step_add_warning( - PrintStateBase::WarningLevel::NON_CRITICAL, - L("There are many loose surface extrusions on this object. Consider enabling supports. ")); - } + L("Thin parts of the object may break. Supports are needed.")); + break; } }; - check_problems(); + if (!this->has_support()) { + SupportSpotsGenerator::raise_alerts_for_issues(supp_points, partial_objects, alert_fn); + } } BOOST_LOG_TRIVIAL(debug) << "Searching support spots - end"; this->set_done(posSupportSpotsSearch); diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 464ee1d67..7eec832c1 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -37,7 +37,7 @@ #include "Geometry/ConvexHull.hpp" // #define DETAILED_DEBUG_LOGS -// #define DEBUG_FILES +#define DEBUG_FILES #ifdef DEBUG_FILES #include @@ -313,19 +313,16 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit curr_point.position.cast(), line_len, entity}; const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ? - prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : - ExtrusionLine{}; + prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : + ExtrusionLine{}; // correctify the distance sign using slice polygons float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; curr_point.distance *= sign; SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion; - if (curr_point.distance > flow_width * 5.0) { - if (std::abs(curr_point.curvature) > 0.1) - potential_cause = SupportPointCause::LongUnsupportedExtrusion; - else - potential_cause = SupportPointCause::LongBridge; + if (bridged_distance + line_len > params.bridge_distance * 0.8 && std::abs(curr_point.curvature) < 0.1) { + potential_cause = SupportPointCause::FloatingExtrusion; } float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f, @@ -337,6 +334,14 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit line_out.form_quality = 0.8f; bridged_distance += line_len; if (bridged_distance > max_bridge_len) { + std::cout << "Problem found A: " << std::endl; + std::cout << "bridged_distance: " << bridged_distance << std::endl; + std::cout << "max_bridge_len: " << max_bridge_len << std::endl; + std::cout << "line_out.form_quality: " << line_out.form_quality << std::endl; + std::cout << "curr_point.distance: " << curr_point.distance << std::endl; + std::cout << "curr_point.curvature: " << curr_point.curvature << std::endl; + std::cout << "flow_width: " << flow_width << std::endl; + line_out.support_point_generated = potential_cause; bridged_distance = 0.0f; } @@ -344,6 +349,14 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit bridged_distance += line_len; line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f; if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) { + std::cout << "Problem found B: " << std::endl; + std::cout << "bridged_distance: " << bridged_distance << std::endl; + std::cout << "max_bridge_len: " << max_bridge_len << std::endl; + std::cout << "line_out.form_quality: " << line_out.form_quality << std::endl; + std::cout << "curr_point.distance: " << curr_point.distance << std::endl; + std::cout << "curr_point.curvature: " << curr_point.curvature << std::endl; + std::cout << "flow_width: " << flow_width << std::endl; + line_out.support_point_generated = potential_cause; line_out.form_quality = 0.5f; bridged_distance = 0.0f; @@ -557,7 +570,7 @@ public: params.material_yield_strength; float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); - float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z); + float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z) * (1.0f - conn_centroid.z() / layer_z); float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); float conn_movement_torque = movement_force * conn_movement_arm; @@ -925,7 +938,6 @@ void debug_export(const SupportPoints& support_points,const PartialObjects& obje case SupportPointCause::FloatingBridgeAnchor: color = {0.863281f, 0.109375f, 0.113281f}; break; //RED case SupportPointCause::LongBridge: color = {0.960938f, 0.90625f, 0.0625f}; break; // YELLOW case SupportPointCause::FloatingExtrusion: color = {0.921875f, 0.515625f, 0.101563f}; break; // ORANGE - case SupportPointCause::LongUnsupportedExtrusion: color = {0.863281f, 0.109375f, 0.113281f}; break; // RED case SupportPointCause::SeparationFromBed: color = {0.0f, 1.0f, 0.0}; break; // GREEN case SupportPointCause::UnstableFloatingPart: color = {0.105469f, 0.699219f, 0.84375f}; break; // BLUE case SupportPointCause::WeakObjectPart: color = {0.609375f, 0.210938f, 0.621094f}; break; // PURPLE @@ -1104,5 +1116,84 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) #endif } +void raise_alerts_for_issues(const SupportPoints &support_points, + PartialObjects &partial_objects, + std::function alert_fn) +{ + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::SeparationFromBed) { + alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::SeparationFromBed); + break; + } + } + + std::reverse(partial_objects.begin(), partial_objects.end()); + std::sort(partial_objects.begin(), partial_objects.end(), + [](const PartialObject &left, const PartialObject &right) { return left.volume > right.volume; }); + + float max_volume_part = partial_objects.front().volume; + for (const PartialObject &p : partial_objects) { + if (p.volume > max_volume_part / 500.0f && !p.connected_to_bed) { + alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart); + return; + } + } + + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::UnstableFloatingPart) { + alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart); + return; + } + } + + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::WeakObjectPart) { + alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::WeakObjectPart); + return; + } + } + + std::vector ext_supp_points{}; + ext_supp_points.reserve(support_points.size()); + for (const SupportPoint &sp : support_points) { + switch (sp.cause) { + case SupportPointCause::FloatingBridgeAnchor: + case SupportPointCause::FloatingExtrusion: ext_supp_points.push_back(sp); break; + default: break; + } + } + + auto coord_fn = [&ext_supp_points](size_t idx, size_t dim) { return ext_supp_points[idx].position[dim]; }; + KDTreeIndirect<3, float, decltype(coord_fn)> ext_points_tree{coord_fn, ext_supp_points.size()}; + for (const SupportPoint &sp : ext_supp_points) { + auto cluster = find_nearby_points(ext_points_tree, sp.position, 3.0); + int score = 0; + bool floating_bridge = false; + for (size_t idx : cluster) { + score += ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor ? 3 : 1; + floating_bridge = floating_bridge || ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor; + } + if (score > 5) { + if (floating_bridge) { + alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingBridgeAnchor); + } else { + alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingExtrusion); + } + return; + } + } + + if (ext_supp_points.size() > 5) { + alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::FloatingExtrusion); + } + + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::LongBridge) { + alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::LongBridge); + return; + } + } +} + } // namespace SupportSpotsGenerator } // namespace Slic3r diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 56762d544..bd3301874 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -77,7 +77,6 @@ enum class SupportPointCause { LongBridge, // point generated on bridge and straight perimeter extrusion longer than the allowed length FloatingBridgeAnchor, // point generated on unsupported bridge endpoint FloatingExtrusion, // point generated on extrusion that does not hold on its own - LongUnsupportedExtrusion, // similar to above, but with large distance to object. This really needs supports. SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too small and there is a risk of separation (brim may help) UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here) WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass) @@ -143,10 +142,13 @@ using PartialObjects = std::vector; std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); -void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); -void estimate_malformations(std::vector &layers, const Params ¶ms); +void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); +void estimate_malformations(std::vector &layers, const Params ¶ms); -} // namespace SupportSpotsGenerator -} +void raise_alerts_for_issues(const SupportPoints &support_points, + PartialObjects &partial_objects, + std::function alert_fn); + +}} // namespace Slic3r::SupportSpotsGenerator #endif /* SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ */ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 6e12527d5..ec993fcd4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -43,8 +43,8 @@ bool GLGizmoFdmSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; - m_desc["auto_generate"] = _L("Auto-generate supports"); - m_desc["generating"] = _L("Generating supports..."); + m_desc["autopaint"] = _L("Automatic painting"); + m_desc["painting"] = _L("painting..."); m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size"] = _L("Brush size") + ": "; @@ -160,9 +160,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); if (waiting_for_autogenerated_supports) { - m_imgui->text(m_desc.at("generating")); + m_imgui->text(m_desc.at("painting")); } else { - bool generate = m_imgui->button(m_desc.at("auto_generate")); + bool generate = m_imgui->button(m_desc.at("autopaint")); if (generate) auto_generate(); } @@ -525,12 +525,12 @@ void GLGizmoFdmSupports::auto_generate() }); MessageDialog dlg(GUI::wxGetApp().plater(), - _L("Autogeneration will erase all currently painted areas.") + "\n\n" + + _L("Automatic painting will erase all currently painted areas.") + "\n\n" + _L("Are you sure you want to do it?") + "\n", _L("Warning"), wxICON_WARNING | wxYES | wxNO); if (not_painted || dlg.ShowModal() == wxID_YES) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Automatic painting support points")); int mesh_id = -1.0f; for (ModelVolume *mv : mo->volumes) { if (mv->is_model_part()) { From c09a44779d2094711c60b889d82a3ca8b5239042 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 25 Jan 2023 16:57:52 +0100 Subject: [PATCH 191/206] brim integration into SupportSpotGenerator --- src/libslic3r/ExPolygon.hpp | 4 +- src/libslic3r/PrintObject.cpp | 32 +++++++----- src/libslic3r/SupportSpotsGenerator.cpp | 67 +++++++++++++++++++++---- src/libslic3r/SupportSpotsGenerator.hpp | 11 ++-- 4 files changed, 86 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 98ad9e2e6..83b264803 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -32,8 +32,8 @@ public: ExPolygon& operator=(const ExPolygon &other) = default; ExPolygon& operator=(ExPolygon &&other) = default; - Polygon contour; - Polygons holes; + Polygon contour; //CCW + Polygons holes; //CW void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 09b35157c..887caa8bf 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -422,7 +422,8 @@ void PrintObject::generate_support_spots() PrintTryCancel cancel_func = m_print->make_try_cancel(); SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values, float(this->print()->m_config.perimeter_acceleration.getFloat()), - this->config().raft_layers.getInt()}; + this->config().raft_layers.getInt(), this->config().brim_type.value, + float(this->config().brim_width.getFloat())}; auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params); this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points}; m_print->throw_if_canceled(); @@ -430,27 +431,33 @@ void PrintObject::generate_support_spots() auto alert_fn = [&](PrintStateBase::WarningLevel level, SupportSpotsGenerator::SupportPointCause cause) { switch (cause) { case SupportSpotsGenerator::SupportPointCause::LongBridge: - this->active_step_add_warning(level, L("There are bridges longer than allowed distance. Consider adding supports. ")); + this->active_step_add_warning(level, L("There are bridges longer than recommended length. Consider adding supports.") + + (L("Object name")) + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor: - this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed.")); + this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed.") + (L("Object name")) + + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::FloatingExtrusion: if (level == PrintStateBase::WarningLevel::CRITICAL) { - this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed.")); + this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed.") + + (L("Object name")) + ": " + this->model_object()->name); } else { - this->active_step_add_warning(level, L("Some unspported extrusions found. Consider adding supports. ")); + this->active_step_add_warning(level, L("Some unspported extrusions found. Consider adding supports. ") + + (L("Object name")) + ": " + this->model_object()->name); } break; case SupportSpotsGenerator::SupportPointCause::SeparationFromBed: - this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports.")); + this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports.") + + (L("Object name")) + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::UnstableFloatingPart: - this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed.")); + this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed.") + (L("Object name")) + + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::WeakObjectPart: - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - L("Thin parts of the object may break. Supports are needed.")); + this->active_step_add_warning(level, L("Thin parts of the object may break. Consider adding supports.") + + (L("Object name")) + ": " + this->model_object()->name); break; } }; @@ -495,9 +502,9 @@ void PrintObject::estimate_curled_extrusions() // Estimate curling of support material and add it to the malformaition lines of each layer float support_flow_width = support_material_flow(this, this->config().layer_height).width(); SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values, - float(this->print()->config().perimeter_acceleration.getFloat()), - this->config().raft_layers.getInt() - }; + float(this->print()->m_config.perimeter_acceleration.getFloat()), + this->config().raft_layers.getInt(), this->config().brim_type.value, + float(this->config().brim_width.getFloat())}; SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params); SupportSpotsGenerator::estimate_malformations(this->layers(), params); m_print->throw_if_canceled(); @@ -609,6 +616,7 @@ bool PrintObject::invalidate_state_by_config_options( if ( opt_key == "brim_width" || opt_key == "brim_separation" || opt_key == "brim_type") { + steps.emplace_back(posSupportSpotsSearch); // Brim is printed below supports, support invalidates brim and skirt. steps.emplace_back(posSupportMaterial); } else if ( diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 7eec832c1..7a77ae767 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -11,6 +11,7 @@ #include "PrincipalComponents2D.hpp" #include "Print.hpp" #include "PrintBase.hpp" +#include "PrintConfig.hpp" #include "Tesselate.hpp" #include "libslic3r.h" #include "tbb/parallel_for.h" @@ -598,10 +599,11 @@ public: }; // return new object part and actual area covered by extrusions -std::tuple build_object_part_from_slice(const LayerSlice &slice, const Layer *layer, const Params& params) +std::tuple build_object_part_from_slice(const size_t &slice_idx, const Layer *layer, const Params& params) { ObjectPart new_object_part; float area_covered_by_extrusions = 0; + const LayerSlice& slice = layer->lslices_ex.at(slice_idx); auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions, ¶ms](const ExtrusionEntity *e, const LayerRegion *region) { @@ -659,6 +661,49 @@ std::tuple build_object_part_from_slice(const LayerSlice &sli } } + // BRIM HANDLING + if (layer->id() == params.raft_layers_count && params.raft_layers_count == 0 && params.brim_type != BrimType::btNoBrim) { + // TODO: The algorithm here should take into account that multiple slices may have coliding Brim areas and the final brim area is + // smaller, + // thus has lower adhesion. For now this effect will be neglected. + ExPolygon slice_poly = layer->lslices[slice_idx]; + ExPolygons brim; + if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btOuterOnly) { + Polygon brim_hole = slice_poly.contour; + brim_hole.reverse(); + brim.push_back(ExPolygon{expand(slice_poly.contour, scale_(params.brim_width)).front(), brim_hole}); + } + if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btInnerOnly) { + Polygons brim_contours = slice_poly.holes; + polygons_reverse(brim_contours); + for (const Polygon &brim_contour : brim_contours) { + Polygons brim_holes = shrink({brim_contour}, scale_(params.brim_width)); + polygons_reverse(brim_holes); + ExPolygon inner_brim{brim_contour}; + inner_brim.holes = brim_holes; + brim.push_back(inner_brim); + } + } + + for (const Polygon &poly : to_polygons(brim)) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + new_object_part.sticking_area += sign * area; + new_object_part.sticking_centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), + layer->print_z * area); + new_object_part.sticking_second_moment_of_area_accumulator += sign * second_moment_area; + new_object_part.sticking_second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; + } + } + } + return {new_object_part, area_covered_by_extrusions}; } @@ -730,7 +775,7 @@ std::tuple check_stability(const PrintObject *po, for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { const LayerSlice &slice = layer->lslices_ex.at(slice_idx); - auto [new_part, covered_area] = build_object_part_from_slice(slice, layer, params); + auto [new_part, covered_area] = build_object_part_from_slice(slice_idx, layer, params); SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer); #ifdef DETAILED_DEBUG_LOGS @@ -1146,13 +1191,6 @@ void raise_alerts_for_issues(const SupportPoints } } - for (const SupportPoint &sp : support_points) { - if (sp.cause == SupportPointCause::WeakObjectPart) { - alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::WeakObjectPart); - return; - } - } - std::vector ext_supp_points{}; ext_supp_points.reserve(support_points.size()); for (const SupportPoint &sp : support_points) { @@ -1189,8 +1227,15 @@ void raise_alerts_for_issues(const SupportPoints for (const SupportPoint &sp : support_points) { if (sp.cause == SupportPointCause::LongBridge) { - alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::LongBridge); - return; + alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::LongBridge); + break; + } + } + + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::WeakObjectPart) { + alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::WeakObjectPart); + break; } } } diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index bd3301874..23fb5dad0 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -4,6 +4,7 @@ #include "Layer.hpp" #include "Line.hpp" #include "PrintBase.hpp" +#include "PrintConfig.hpp" #include #include #include @@ -14,8 +15,9 @@ namespace SupportSpotsGenerator { struct Params { - Params(const std::vector &filament_types, float max_acceleration, int raft_layers_count) - : max_acceleration(max_acceleration), raft_layers_count(raft_layers_count) + Params( + const std::vector &filament_types, float max_acceleration, int raft_layers_count, BrimType brim_type, float brim_width) + : max_acceleration(max_acceleration), raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width) { if (filament_types.size() > 1) { BOOST_LOG_TRIVIAL(warning) @@ -37,6 +39,9 @@ struct Params const int raft_layers_count; std::string filament_type; + BrimType brim_type; + const float brim_width; + const std::pair malformation_distance_factors = std::pair { 0.4, 1.2 }; const float max_curled_height_factor = 10.0f; @@ -53,7 +58,7 @@ struct Params // MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface double get_bed_adhesion_yield_strength() const { if (raft_layers_count > 0) { - return get_support_spots_adhesion_strength(); + return get_support_spots_adhesion_strength() * 2.0; } if (filament_type == "PLA") { From c38bd9adde9d73639b32a09722d568596e7cbc37 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 25 Jan 2023 17:05:13 +0100 Subject: [PATCH 192/206] missing space in description --- src/libslic3r/PrintObject.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 887caa8bf..474e02f58 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -431,16 +431,16 @@ void PrintObject::generate_support_spots() auto alert_fn = [&](PrintStateBase::WarningLevel level, SupportSpotsGenerator::SupportPointCause cause) { switch (cause) { case SupportSpotsGenerator::SupportPointCause::LongBridge: - this->active_step_add_warning(level, L("There are bridges longer than recommended length. Consider adding supports.") + + this->active_step_add_warning(level, L("There are bridges longer than recommended length. Consider adding supports. ") + (L("Object name")) + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor: - this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed.") + (L("Object name")) + + this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed. ") + (L("Object name")) + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::FloatingExtrusion: if (level == PrintStateBase::WarningLevel::CRITICAL) { - this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed.") + + this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed. ") + (L("Object name")) + ": " + this->model_object()->name); } else { this->active_step_add_warning(level, L("Some unspported extrusions found. Consider adding supports. ") + @@ -448,15 +448,15 @@ void PrintObject::generate_support_spots() } break; case SupportSpotsGenerator::SupportPointCause::SeparationFromBed: - this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports.") + + this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports. ") + (L("Object name")) + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::UnstableFloatingPart: - this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed.") + (L("Object name")) + + this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed. ") + (L("Object name")) + ": " + this->model_object()->name); break; case SupportSpotsGenerator::SupportPointCause::WeakObjectPart: - this->active_step_add_warning(level, L("Thin parts of the object may break. Consider adding supports.") + + this->active_step_add_warning(level, L("Thin parts of the object may break. Consider adding supports. ") + (L("Object name")) + ": " + this->model_object()->name); break; } From 37ca6e30bdcd0a747742753b2b3f3b9e0ae07ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Miku=C5=A1?= Date: Wed, 25 Jan 2023 17:24:53 +0100 Subject: [PATCH 193/206] Update SupportSpotsGenerator.cpp --- src/libslic3r/SupportSpotsGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 7a77ae767..3820aa4d5 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -38,7 +38,7 @@ #include "Geometry/ConvexHull.hpp" // #define DETAILED_DEBUG_LOGS -#define DEBUG_FILES +// #define DEBUG_FILES #ifdef DEBUG_FILES #include From 70a9520cc3e2caf5f9062eac565cc3a297b881a5 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 25 Jan 2023 17:45:40 +0100 Subject: [PATCH 194/206] App udpater fixes - checks of path, error reporting and translations --- src/slic3r/GUI/DownloaderFileGet.cpp | 4 +- src/slic3r/GUI/UpdateDialogs.cpp | 42 +++++++++++++----- src/slic3r/Utils/AppUpdater.cpp | 66 ++++++++++++++++++++-------- 3 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index 2e591a75a..d81261296 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -119,8 +119,8 @@ void FileGet::priv::get_perform() wxString temp_path_wstring(m_tmp_path.wstring()); - std::cout << "dest_path: " << dest_path.string() << std::endl; - std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl; + //std::cout << "dest_path: " << dest_path.string() << std::endl; + //std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl; BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path); diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 9c4f12eaf..5663262ca 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -167,10 +167,14 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos wxString wxext = boost::nowide::widen(extension); wildcard = GUI::format_wxstr("%1% Files (*.%2%)|*.%2%", wxext.Upper(), wxext); } + boost::system::error_code ec; + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(GUI::format(txtctrl_path->GetValue())), ec); + if (ec) + dir = GUI::format(txtctrl_path->GetValue()); wxDirDialog save_dlg( this , _L("Select directory:") - , txtctrl_path->GetValue() + , GUI::format_wxstr(dir.string()) /* , filename //boost::nowide::widen(AppUpdater::get_filename_from_url(txtctrl_path->GetValue().ToUTF8().data())) , wildcard @@ -188,35 +192,46 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) { btn_ok->SetLabel(_L("Download")); btn_ok->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e){ - boost::filesystem::path path = boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename); boost::system::error_code ec; - if (path.parent_path().string().empty()) { + std::string input = GUI::format(txtctrl_path->GetValue()); + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(input), ec); + if (ec) + dir = boost::filesystem::path(input); + bool show_change = (dir.string() != input); + boost::filesystem::path path = dir / GUI::format(filename); + ec.clear(); + if (dir.string().empty()) { MessageDialog msgdlg(nullptr, _L("Directory path is empty."), _L("Notice"), wxOK); msgdlg.ShowModal(); return; } ec.clear(); - if (!boost::filesystem::exists(path.parent_path(), ec) || !boost::filesystem::is_directory(path.parent_path(),ec) || ec) { + if (!boost::filesystem::exists(dir, ec) || !boost::filesystem::is_directory(dir,ec) || ec) { ec.clear(); - if (!boost::filesystem::exists(path.parent_path().parent_path(), ec) || !boost::filesystem::is_directory(path.parent_path().parent_path(), ec) || ec) { + if (!boost::filesystem::exists(dir.parent_path(), ec) || !boost::filesystem::is_directory(dir.parent_path(), ec) || ec) { MessageDialog msgdlg(nullptr, _L("Directory path is incorrect."), _L("Notice"), wxOK); msgdlg.ShowModal(); return; } - - MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Directory %1% doesn't exists. Do you wish to create it?"), GUI::format_wxstr(path.parent_path().string())), _L("Notice"), wxYES_NO); + show_change = false; + MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Directory %1% doesn't exists. Do you wish to create it?"), dir.string()), _L("Notice"), wxYES_NO); if (msgdlg.ShowModal() != wxID_YES) return; ec.clear(); - if(!boost::filesystem::create_directory(path.parent_path(), ec) || ec) - { + if(!boost::filesystem::create_directory(dir, ec) || ec) { MessageDialog msgdlg(nullptr, _L("Failed to create directory."), _L("Notice"), wxOK); msgdlg.ShowModal(); return; } } if (boost::filesystem::exists(path)) { - MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("File %1% already exists. Do you wish to overwrite it?"), GUI::format_wxstr(path.string())),_L("Notice"), wxYES_NO); + show_change = false; + MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("File %1% already exists. Do you wish to overwrite it?"), path.string()),_L("Notice"), wxYES_NO); + if (msgdlg.ShowModal() != wxID_YES) + return; + } + if (show_change) { + MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Download path is %1%. Do you wish to continue?"), path.string()), _L("Notice"), wxYES_NO); if (msgdlg.ShowModal() != wxID_YES) return; } @@ -241,7 +256,12 @@ bool AppUpdateDownloadDialog::run_after_download() const boost::filesystem::path AppUpdateDownloadDialog::get_download_path() const { - return boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename); + boost::system::error_code ec; + std::string input = GUI::format(txtctrl_path->GetValue()); + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(input), ec); + if (ec) + dir = boost::filesystem::path(input); + return dir / GUI::format(filename); } // MsgUpdateConfig diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index fc847ec53..d054db4b5 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -13,6 +13,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" #include "slic3r/Utils/Http.hpp" #include "libslic3r/Utils.hpp" @@ -36,7 +37,7 @@ namespace { std::string msg; bool res = GUI::create_process(path, std::wstring(), msg); if (!res) { - std::string full_message = GUI::format("Running downloaded instaler of %1% has failed:\n%2%", SLIC3R_APP_NAME, msg); + std::string full_message = GUI::format(_utf8("Running downloaded instaler of %1% has failed:\n%2%"), SLIC3R_APP_NAME, msg); BOOST_LOG_TRIVIAL(error) << full_message; // lm: maybe UI error msg? // dk: bellow. (maybe some general show error evt would be better?) wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); evt->SetString(full_message); @@ -170,8 +171,10 @@ bool AppUpdater::priv::http_get_file(const std::string& url, size_t size_limit, // progress function returns true as success (to continue) cancel = (m_cancel ? true : !progress_fn(std::move(progress))); if (cancel) { - error_message = GUI::format("Error getting: `%1%`: Download was canceled.", //lm:typo //dk: am i blind? :) - url); + // Lets keep error_message empty here - if there is need to show error dialog, the message will be probably shown by whatever caused the cancel. + /* + error_message = GUI::format(_utf8("Error getting: `%1%`: Download was canceled."), url); + */ BOOST_LOG_TRIVIAL(debug) << "AppUpdater::priv::http_get_file message: "<< error_message; } }) @@ -200,7 +203,30 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d assert(!dest_path.empty()); if (dest_path.empty()) { - BOOST_LOG_TRIVIAL(error) << "Download from " << data.url << " could not start. Destination path is empty."; + std::string line1 = GUI::format(_utf8("Internal download error for url %1%:"), data.url); + std::string line2 = _utf8("Destination path is empty."); + std::string message = GUI::format("%1%\n%2%", line1, line2); + BOOST_LOG_TRIVIAL(error) << message; + wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); + evt->SetString(message); + GUI::wxGetApp().QueueEvent(evt); + return boost::filesystem::path(); + } + + boost::filesystem::path tmp_path = dest_path; + tmp_path += format(".%1%%2%", get_current_pid(), ".download"); + FILE* file; + wxString temp_path_wstring(tmp_path.wstring()); + file = fopen(temp_path_wstring.c_str(), "wb"); + assert(file != NULL); + if (file == NULL) { + std::string line1 = GUI::format(_utf8("Download from %1% couldn't start:"), data.url); + std::string line2 = GUI::format(_utf8("Can't create file at %1%."), tmp_path.string()); + std::string message = GUI::format("%1%\n%2%", line1, line2); + BOOST_LOG_TRIVIAL(error) << message; + wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); + evt->SetString(message); + GUI::wxGetApp().QueueEvent(evt); return boost::filesystem::path(); } @@ -217,7 +243,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d GUI::wxGetApp().QueueEvent(evt); return false; } else if (progress.dltotal > 0 && progress.dltotal < expected_size) { - //lm:When will this happen? Is that not an error? // dk: It is possible error, but we cannot know until the download is finished. Somehow the total size can grow during the download. + // This is possible error, but we cannot know until the download is finished. Somehow the total size can grow during the download. BOOST_LOG_TRIVIAL(info) << GUI::format("Downloading new %1% has incorrect size. The download will continue. \nExpected size: %2%\nDownload size: %3%", SLIC3R_APP_NAME, expected_size, progress.dltotal); } // progress event @@ -232,27 +258,26 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d return true; } // on_complete - , [dest_path, expected_size](std::string body, std::string& error_message){ + , [&file, dest_path, tmp_path, expected_size](std::string body, std::string& error_message){ // Size check. Does always 1 char == 1 byte? size_t body_size = body.size(); if (body_size != expected_size) { - //lm:UI message? // dk: changed. Now it propagates to UI. - error_message = GUI::format("Downloaded file has wrong size. Expected size: %1% Downloaded size: %2%", expected_size, body_size); + error_message = GUI::format(_utf8("Downloaded file has wrong size. Expected size: %1% Downloaded size: %2%"), expected_size, body_size); + return false; + } + if (file == NULL) { + error_message = GUI::format(_utf8("Can't create file at %1%."), tmp_path.string()); return false; } - boost::filesystem::path tmp_path = dest_path; - tmp_path += format(".%1%%2%", get_current_pid(), ".download"); try { - FILE* file; - file = fopen(tmp_path.string().c_str(), "wb"); fwrite(body.c_str(), 1, body.size(), file); fclose(file); boost::filesystem::rename(tmp_path, dest_path); } catch (const std::exception& e) { - error_message = GUI::format("Failed to write and move %1% to %2%:/n%3%", tmp_path, dest_path, e.what()); + error_message = GUI::format(_utf8("Failed to write to file or to move %1% to %2%:\n%3%"), tmp_path, dest_path, e.what()); return false; } return true; @@ -261,16 +286,19 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d ); if (!res) { - if (m_cancel) - { - BOOST_LOG_TRIVIAL(info) << error_message; //lm:Is this an error? // dk: changed to info + if (m_cancel) { + BOOST_LOG_TRIVIAL(info) << error_message; wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification GUI::wxGetApp().QueueEvent(evt); } else { - std::string message = GUI::format("Downloading new %1% has failed:\n%2%", SLIC3R_APP_NAME, error_message); - BOOST_LOG_TRIVIAL(error) << message; + std::string message = (error_message.empty() + ? std::string() + : GUI::format(_utf8("Downloading new %1% has failed:\n%2%"), SLIC3R_APP_NAME, error_message)); wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); - evt->SetString(message); + if (!message.empty()) { + BOOST_LOG_TRIVIAL(error) << message; + evt->SetString(message); + } GUI::wxGetApp().QueueEvent(evt); } return boost::filesystem::path(); From a784be24e7b4e2883bc64726b39cdcc7ee413640 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 25 Jan 2023 18:51:53 +0100 Subject: [PATCH 195/206] Follow-up to 842229842f7fe7ce48215d6cfe55aaf4b46cfe56 WIP Synchronization of mirroring Fixed mirroring at the FDM and SLA back-end. --- src/libslic3r/Geometry.cpp | 14 ++++++++++++-- src/libslic3r/Geometry.hpp | 1 + src/libslic3r/Print.cpp | 2 +- src/libslic3r/SLAPrint.cpp | 5 ++--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 25db3cce0..7bab0ed7a 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -877,8 +877,7 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); const Vec3d& axis = angle_axis.axis(); const double angle = angle_axis.angle(); -#if 0 -//#ifndef NDEBUG +#ifndef NDEBUG if (std::abs(angle) > 1e-8) { assert(std::abs(axis.x()) < 1e-8); assert(std::abs(axis.y()) < 1e-8); @@ -887,4 +886,15 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) return (axis.z() < 0) ? -angle : angle; } +double rotation_diff_z(const Transform3d &trafo_from, const Transform3d &trafo_to) +{ + auto m = trafo_to.linear() * trafo_from.linear().inverse(); + assert(std::abs(m.determinant() - 1)); + Vec3d vx = m * Vec3d(1., 0., 0); + // Verify that the linear part of rotation from trafo_from to trafo_to rotates around Z and is unity. + assert(std::abs(std::hypot(vx.x(), vx.y()) - 1.) < 1e-5); + assert(std::abs(vx.z()) < 1e-5); + return atan2(vx.y(), vx.x()); +} + }} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index acaad6af3..d7cf32611 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -547,6 +547,7 @@ extern Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec // Rotation by Z to align rot_xyz_from to rot_xyz_to. // This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to); +extern double rotation_diff_z(const Transform3d &trafo_from, const Transform3d &trafo_to); // Is the angle close to a multiple of 90 degrees? inline bool is_rotation_ninety_degrees(double a) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e7305234f..eff0bcd45 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -404,7 +404,7 @@ bool Print::sequential_print_horizontal_clearance_valid(const Print& print, Poly } // Make a copy, so it may be rotated for instances. Polygon convex_hull0 = it_convex_hull->second; - const double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation()); + const double z_diff = Geometry::rotation_diff_z(model_instance0->get_matrix(), print_object->instances().front().model_instance->get_matrix()); if (std::abs(z_diff) > EPSILON) convex_hull0.rotate(z_diff); // Now we check that no instance of convex_hull intersects any of the previously checked object instances. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d4023f64d..dc5e2b519 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -190,14 +190,13 @@ static std::vector sla_instances(const ModelObject &mo std::vector instances; assert(! model_object.instances.empty()); if (! model_object.instances.empty()) { - Vec3d rotation0 = model_object.instances.front()->get_rotation(); - rotation0(2) = 0.; + const Transform3d& trafo0 = model_object.instances.front()->get_matrix(); for (ModelInstance *model_instance : model_object.instances) if (model_instance->is_printable()) { instances.emplace_back( model_instance->id(), Point::new_scale(model_instance->get_offset(X), model_instance->get_offset(Y)), - float(Geometry::rotation_diff_z(rotation0, model_instance->get_rotation()))); + float(Geometry::rotation_diff_z(trafo0, model_instance->get_matrix()))); } } return instances; From 90bd46e30a1d47d7c37fc2b8f3bbd116c2f6842f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 21 Oct 2022 09:49:57 +0200 Subject: [PATCH 196/206] Added 'is_extruder_used' placeholder accessible from Custom Start G-Code --- src/libslic3r/GCode.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ad0d3c43f..4da8af567 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1263,6 +1263,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + + std::vector is_extruder_used(print.config().nozzle_diameter.size(), 0); + for (unsigned int extruder_id : print.extruders()) + is_extruder_used[extruder_id] = true; + m_placeholder_parser.set("is_extruder_used", new ConfigOptionBools(is_extruder_used)); } std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. From b3664179f62db797d9a29767b497dfc7ff62d1e5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 22 Dec 2022 15:46:25 +0100 Subject: [PATCH 197/206] Wipe tower: remove a move to the wipe tower when not needed --- src/libslic3r/GCode.cpp | 5 +++-- src/libslic3r/GCode/WipeTower.cpp | 35 +++++++++++++++++++++---------- src/libslic3r/GCode/WipeTower.hpp | 1 - 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4da8af567..47bd6df40 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -238,8 +238,9 @@ namespace Slic3r { std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); - if (! tcr.priming) { - // Move over the wipe tower. + if (gcodegen.config().single_extruder_multi_material && ! tcr.priming) { + // Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the + // toolchange will travel there anyway. gcode += gcodegen.retract(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcode += gcodegen.travel_to( diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index f24311a13..87f04f08c 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -71,6 +71,8 @@ public: return *this; } + WipeTowerWriter& set_position(const Vec2f &pos) { m_current_pos = pos; return *this; } + WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; } WipeTowerWriter& set_z(float z) @@ -802,19 +804,24 @@ void WipeTower::toolchange_Unload( { float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; - - const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness + + const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm + const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f); + writer.append("; CP TOOLCHANGE UNLOAD\n") .change_analyzer_line_width(line_width); unsigned i = 0; // iterates through ramming_speed m_left_to_right = true; // current direction of ramming float remaining = xr - xl ; // keeps track of distance to the next turnaround - float e_done = 0; // measures E move done from each segment + float e_done = 0; // measures E move done from each segment - writer.travel(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f ); // move to starting position + if (m_semm) + writer.travel(ramming_start_pos); // move to starting position + else + writer.set_position(ramming_start_pos); // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { @@ -849,7 +856,7 @@ void WipeTower::toolchange_Unload( writer.disable_linear_advance(); // now the ramming itself: - while (i < m_filpar[m_current_tool].ramming_speed.size()) + while (m_semm && i < m_filpar[m_current_tool].ramming_speed.size()) { const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move; @@ -898,7 +905,7 @@ void WipeTower::toolchange_Unload( // Cooling: const int& number_of_moves = m_filpar[m_current_tool].cooling_moves; - if (number_of_moves > 0) { + if (m_semm && number_of_moves > 0) { const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed; const float& final_speed = m_filpar[m_current_tool].cooling_final_speed; @@ -916,14 +923,20 @@ void WipeTower::toolchange_Unload( } } - // let's wait is necessary: - writer.wait(m_filpar[m_current_tool].delay); - // we should be at the beginning of the cooling tube again - let's move to parking position: - writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000); + if (m_semm) { + // let's wait is necessary: + writer.wait(m_filpar[m_current_tool].delay); + // we should be at the beginning of the cooling tube again - let's move to parking position: + writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000); + } // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material - writer.travel(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); + Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width); + if (m_semm) + writer.travel(pos, 2400.f); + else + writer.set_position(pos); writer.resume_preview() .flush_planner_queue(); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 397b5ab7d..ab3af507d 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -290,7 +290,6 @@ private: // Extruder specific parameters. std::vector m_filpar; - // State of the wipe tower generator. unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. From 7fb1bc2c16e880a7e357031185c4cdd9ed225bd9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 6 Jan 2023 01:49:04 +0100 Subject: [PATCH 198/206] Placeholders 'layer_num', 'layer_z' and 'max_layer_z' were not accessible in fil. start gcode when the wipe tower was off --- src/libslic3r/GCode.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 47bd6df40..13b21174a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3192,6 +3192,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the filament. DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); check_add_eol(gcode); @@ -3263,6 +3266,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the new filament. DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); check_add_eol(gcode); From 98fea2f6ee64f8f4e0456bcd528208118a2fa65c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 6 Jan 2023 13:25:42 +0100 Subject: [PATCH 199/206] Wipe tower: use GCode::set_extruder, allow ooze prevention: this removes duplicated code and fixes toolchange retraction The ooze prevention part needs further work, now it does not work as advertised (the tall skirt) --- src/libslic3r/GCode.cpp | 49 +++++++++++-------------------- src/libslic3r/GCode/WipeTower.cpp | 5 ++-- src/libslic3r/Print.cpp | 6 ++-- tests/fff_print/test_multi.cpp | 24 +++++++-------- 4 files changed, 35 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 13b21174a..7c786cf89 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -260,21 +260,10 @@ namespace Slic3r { } - // Process the end filament gcode. - std::string end_filament_gcode_str; - if (gcodegen.writer().extruder() != nullptr) { - // Process the custom end_filament_gcode in case of single_extruder_multi_material. - unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); - const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id); - if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) { - end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); - check_add_eol(end_filament_gcode_str); - } - } // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. // Otherwise, leave control to the user completely. - std::string toolchange_gcode_str; + /*std::string toolchange_gcode_str; const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; if (! toolchange_gcode.empty()) { DynamicConfig config; @@ -296,29 +285,26 @@ namespace Slic3r { toolchange_gcode_str += toolchange_command; else { // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. + }*/ + + std::string toolchange_gcode_str; + std::string deretraction_str; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) { + if (gcodegen.config().single_extruder_multi_material) + gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. + toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z + if (gcodegen.config().wipe_tower) + deretraction_str = gcodegen.unretract(); } - gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - // Process the start filament gcode. - std::string start_filament_gcode_str; - const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); - if (!start_filament_gcode.empty()) { - // Process the start_filament_gcode for the active filament only. - DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(gcodegen.writer().get_position()(2) - gcodegen.m_config.z_offset.value)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); - config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); - start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); - check_add_eol(start_filament_gcode_str); - } + - // Insert the end filament, toolchange, and start filament gcode into the generated gcode. + + // Insert the toolchange and deretraction gcode into the generated gcode. DynamicConfig config; - config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str)); config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); - config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); + config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str)); std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); gcode += tcr_gcode; @@ -915,7 +901,7 @@ namespace DoExport { skirts.emplace_back(std::move(s)); } ooze_prevention.enable = true; - ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); + //ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); #if 0 require "Slic3r/SVG.pm"; Slic3r::SVG::output( @@ -3210,8 +3196,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) m_wipe.reset_path(); if (m_writer.extruder() != nullptr) { - // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower - // so it should not be injected twice. + // Process the custom end_filament_gcode. unsigned int old_extruder_id = m_writer.extruder()->id(); const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); if (! end_filament_gcode.empty()) { diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 87f04f08c..fec2eb86b 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -954,7 +954,7 @@ void WipeTower::toolchange_Change( // This is where we want to place the custom gcodes. We will use placeholders for this. // These will be substituted by the actual gcodes when the gcode is generated. - writer.append("[end_filament_gcode]\n"); + //writer.append("[end_filament_gcode]\n"); writer.append("[toolchange_gcode]\n"); // Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc) @@ -965,11 +965,12 @@ void WipeTower::toolchange_Change( .append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x()) + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y()) + never_skip_tag() + "\n"); + writer.append("[deretraction_from_wipe_tower_generator]"); // The toolchange Tn command will be inserted later, only in case that the user does // not provide a custom toolchange gcode. writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed. - writer.append("[start_filament_gcode]\n"); + //writer.append("[start_filament_gcode]\n"); writer.flush_planner_queue(); m_current_tool = new_tool; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e7305234f..3f0f5c2cd 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -347,7 +347,7 @@ std::vector Print::print_object_ids() const bool Print::has_infinite_skirt() const { - return (m_config.draft_shield == dsEnabled && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1); + return (m_config.draft_shield == dsEnabled && m_config.skirts > 0)/* || (m_config.ooze_prevention && this->extruders().size() > 1)*/; } bool Print::has_skirt() const @@ -505,8 +505,8 @@ std::string Print::validate(std::string* warning) const return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); - if (m_config.ooze_prevention) - return L("Ooze prevention is currently not supported with the wipe tower enabled."); + if (m_config.ooze_prevention && m_config.single_extruder_multi_material) + return L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off."); if (m_config.use_volumetric_e) return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)."); if (m_config.complete_objects && extruders.size() > 1) diff --git a/tests/fff_print/test_multi.cpp b/tests/fff_print/test_multi.cpp index 03c7f644b..90f311c39 100644 --- a/tests/fff_print/test_multi.cpp +++ b/tests/fff_print/test_multi.cpp @@ -108,18 +108,18 @@ SCENARIO("Ooze prevention", "[Multi]") Polygon convex_hull = Geometry::convex_hull(extrusion_points); - THEN("all nozzles are outside skirt at toolchange") { - Points t; - sort_remove_duplicates(toolchange_points); - size_t inside = 0; - for (const auto &point : toolchange_points) - for (const Vec2d &offset : print_config.extruder_offset.values) { - Point p = point + scaled(offset); - if (convex_hull.contains(p)) - ++ inside; - } - REQUIRE(inside == 0); - } + // THEN("all nozzles are outside skirt at toolchange") { + // Points t; + // sort_remove_duplicates(toolchange_points); + // size_t inside = 0; + // for (const auto &point : toolchange_points) + // for (const Vec2d &offset : print_config.extruder_offset.values) { + // Point p = point + scaled(offset); + // if (convex_hull.contains(p)) + // ++ inside; + // } + // REQUIRE(inside == 0); + // } #if 0 require "Slic3r/SVG.pm"; From a067da6d53196727c6bc610bca5134e743c73aff Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 10 Jan 2023 14:26:53 +0100 Subject: [PATCH 200/206] Ooze prevention: - remove the infinite skirt - added 'idle_temperature' in Filament Settings as an optional parameter - the logic is changed: if idle_temp is present, it is used, otherwise it uses the old delta value from Print Settings - TODO: the optional parameter is not well supported in UI --- src/libslic3r/GCode.cpp | 82 +++++++++++------------------------ src/libslic3r/GCode.hpp | 3 +- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 1 + src/libslic3r/PrintConfig.cpp | 27 ++++++++++-- src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/GUI/Tab.cpp | 2 + 7 files changed, 54 insertions(+), 64 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7c786cf89..ffe19a9cb 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -113,26 +113,19 @@ namespace Slic3r { { std::string gcode; - // move to the nearest standby point - if (!this->standby_points.empty()) { - // get current position in print coordinates - Vec3d writer_pos = gcodegen.writer().get_position(); - Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); - - // find standby point - Point standby_point = nearest_point(this->standby_points, pos).first; - - /* We don't call gcodegen.travel_to() because we don't need retraction (it was already - triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates - of the destination point must not be transformed by origin nor current extruder offset. */ - gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), - "move to standby position"); - } - - if (gcodegen.config().standby_temperature_delta.value != 0) { - // we assume that heating is always slower than cooling, so no need to block - gcode += gcodegen.writer().set_temperature - (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id()); + unsigned int extruder_id = gcodegen.writer().extruder()->id(); + const ConfigOptionIntsNullable& filament_idle_temp = gcodegen.config().idle_temperature; + if (filament_idle_temp.is_nil(extruder_id)) { + // There is no idle temperature defined in filament settings. + // Use the delta value from print config. + if (gcodegen.config().standby_temperature_delta.value != 0) { + // we assume that heating is always slower than cooling, so no need to block + gcode += gcodegen.writer().set_temperature + (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id); + } + } else { + // Use the value from filament settings. That one is absolute, not delta. + gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id); } return gcode; @@ -145,8 +138,7 @@ namespace Slic3r { std::string(); } - int - OozePrevention::_get_temp(GCode& gcodegen) + int OozePrevention::_get_temp(const GCode& gcodegen) const { return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0) ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id()) @@ -885,34 +877,7 @@ namespace DoExport { static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) { - // Calculate wiping points if needed - if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) { - Points skirt_points; - for (const ExtrusionEntity *ee : print.skirt().entities) - for (const ExtrusionPath &path : dynamic_cast(ee)->paths) - append(skirt_points, path.polyline.points); - if (! skirt_points.empty()) { - Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); - Polygons skirts; - for (unsigned int extruder_id : print.extruders()) { - const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id); - Polygon s(outer_skirt); - s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); - skirts.emplace_back(std::move(s)); - } - ooze_prevention.enable = true; - //ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); - #if 0 - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "ooze_prevention.svg", - red_polygons => \@skirts, - polygons => [$outer_skirt], - points => $gcodegen->ooze_prevention->standby_points, - ); - #endif - } - } + ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material; } // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. @@ -1274,8 +1239,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Set other general things. file.write(this->preamble()); - // Calculate wiping points if needed + // Enable ooze prevention if configured so. DoExport::init_ooze_prevention(print, m_ooze_prevention); + print.throw_if_canceled(); // Collect custom seam data from all objects. @@ -1806,7 +1772,7 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr m_writer.set_temperature(temp, wait, first_printing_extruder_id); } else { // Custom G-code does not set the extruder temperature. Do it now. - if (print.config().single_extruder_multi_material.value) { + if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) { // Set temperature of the first printing extruder only. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp > 0) @@ -2128,11 +2094,14 @@ LayerResult GCode::process_layer( // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent // first_layer_temperature vs. temperature settings. for (const Extruder &extruder : m_writer.extruders()) { - if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id()) + if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) { // In single extruder multi material mode, set the temperature for the current extruder only. - continue; + // The same applies when ooze prevention is enabled. + if (extruder.id() != m_writer.extruder()->id()) + continue; + } int temperature = print.config().temperature.get_at(extruder.id()); - if (temperature > 0 && temperature != print.config().first_layer_temperature.get_at(extruder.id())) + if (temperature > 0 && (temperature != print.config().first_layer_temperature.get_at(extruder.id()))) gcode += m_writer.set_temperature(temperature, false, extruder.id()); } gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id)); @@ -3206,8 +3175,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) } - // If ooze prevention is enabled, park current extruder in the nearest - // standby point and set it to the standby temperature. + // If ooze prevention is enabled, set current extruder to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 0741d7e37..ee50aefcc 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -39,14 +39,13 @@ struct PrintInstance; class OozePrevention { public: bool enable; - Points standby_points; OozePrevention() : enable(false) {} std::string pre_toolchange(GCode &gcodegen); std::string post_toolchange(GCode &gcodegen); private: - int _get_temp(GCode &gcodegen); + int _get_temp(const GCode &gcodegen) const; }; class Wipe { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d2f5256f9..d6a4692f3 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -470,7 +470,7 @@ static std::vector s_Preset_filament_options { "extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", - "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", + "temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode", // Retract overrides diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 3f0f5c2cd..055c6f730 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -191,6 +191,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "infill_first" || opt_key == "single_extruder_multi_material" || opt_key == "temperature" + || opt_key == "idle_temperature" || opt_key == "wipe_tower" || opt_key == "wipe_tower_width" || opt_key == "wipe_tower_brim_width" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4da75489c..86e0d8222 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -229,6 +229,11 @@ static void assign_printer_technology_to_unknown(t_optiondef_map &options, Print kvp.second.printer_technology = printer_technology; } +// Maximum extruder temperature, bumped to 1500 to support printing of glass. +namespace { + const int max_temp = 1500; +}; + PrintConfigDef::PrintConfigDef() { this->init_common_params(); @@ -1972,9 +1977,7 @@ void PrintConfigDef::init_fff_params() def = this->add("ooze_prevention", coBool); def->label = L("Enable"); - def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. " - "It will enable a tall skirt automatically and move extruders outside such " - "skirt when changing temperatures."); + def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. "); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); @@ -2475,7 +2478,8 @@ void PrintConfigDef::init_fff_params() def = this->add("standby_temperature_delta", coInt); def->label = L("Temperature variation"); def->tooltip = L("Temperature difference to be applied when an extruder is not active. " - "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."); + "The value is not used when 'idle_temperature' in filament settings " + "is defined."); def->sidetext = "∆°C"; def->min = -max_temp; def->max = max_temp; @@ -3731,6 +3735,15 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->set_default_value(new ConfigOptionFloat(0.3)); + def = this->add_nullable("idle_temperature", coInts); + def->label = L("Idle temperature"); + def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups." + "This is only used when 'Ooze prevention is active in Print Settings.'"); + def->sidetext = L("°C"); + //def->min = 0; + //def->max = max_temp; + def->set_default_value(new ConfigOptionIntsNullable { ConfigOptionIntsNullable::nil_value() }); + def = this->add("bottle_volume", coFloat); def->label = L("Bottle volume"); def->tooltip = L("Bottle volume"); @@ -4511,6 +4524,12 @@ std::string validate(const FullPrintConfig &cfg) assert(opt != nullptr); const ConfigOptionDef *optdef = print_config_def.get(opt_key); assert(optdef != nullptr); + + if (opt->nullable() && opt->is_nil()) { + // Do not check nil values + continue; + } + bool out_of_range = false; switch (opt->type()) { case coFloat: diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 42ac9c0e2..6bfc7723d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -769,6 +769,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloatOrPercent, first_layer_height)) ((ConfigOptionFloatOrPercent, first_layer_speed)) ((ConfigOptionInts, first_layer_temperature)) + ((ConfigOptionIntsNullable, idle_temperature)) ((ConfigOptionInts, full_fan_speed_layer)) ((ConfigOptionFloat, infill_acceleration)) ((ConfigOptionBool, infill_first)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7d91f689d..52d83eff9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1957,6 +1957,8 @@ void TabFilament::build() line.append_option(optgroup->get_option("temperature")); optgroup->append_line(line); + optgroup->append_single_option_line("idle_temperature"); + line = { L("Bed"), "" }; line.append_option(optgroup->get_option("first_layer_bed_temperature")); line.append_option(optgroup->get_option("bed_temperature")); From 71cedd2eeab6f040bad17f5638a7f24cc0beb48e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 11 Jan 2023 12:29:15 +0100 Subject: [PATCH 201/206] Implemented UI to "Idle temperature" parameter --- src/libslic3r/PrintConfig.cpp | 4 +- src/slic3r/GUI/Field.cpp | 60 ++++++++++++++++--- src/slic3r/GUI/Field.hpp | 18 ++---- src/slic3r/GUI/Tab.cpp | 106 +++++++++++++++++++--------------- src/slic3r/GUI/Tab.hpp | 2 + 5 files changed, 122 insertions(+), 68 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 86e0d8222..1d0446ce2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3740,8 +3740,8 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups." "This is only used when 'Ooze prevention is active in Print Settings.'"); def->sidetext = L("°C"); - //def->min = 0; - //def->max = max_temp; + def->min = 0; + def->max = max_temp; def->set_default_value(new ConfigOptionIntsNullable { ConfigOptionIntsNullable::nil_value() }); def = this->add("bottle_volume", coFloat); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 419d48d5b..4e087c98f 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -762,28 +762,26 @@ void SpinCtrl::BUILD() { if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit); wxString text_value = wxString(""); - int default_value = 0; + int default_value = UNDEF_VALUE; switch (m_opt.type) { case coInt: default_value = m_opt.default_value->getInt(); - text_value = wxString::Format(_T("%i"), default_value); break; case coInts: { - const ConfigOptionInts *vec = m_opt.get_default_value(); - if (vec == nullptr || vec->empty()) break; - for (size_t id = 0; id < vec->size(); ++id) - { - default_value = vec->get_at(id); - text_value += wxString::Format(_T("%i"), default_value); - } + default_value = m_opt.get_default_value()->get_at(m_opt_idx); + if (m_opt.nullable) + m_last_meaningful_value = default_value == ConfigOptionIntsNullable::nil_value() ? static_cast(m_opt.max) : default_value; break; } default: break; } + if (default_value != UNDEF_VALUE) + text_value = wxString::Format(_T("%i"), default_value); + const int min_val = m_opt.min == -FLT_MAX #ifdef __WXOSX__ // We will forcibly set the input value for SpinControl, since the value @@ -882,6 +880,50 @@ void SpinCtrl::BUILD() { window = dynamic_cast(temp); } +void SpinCtrl::set_value(const boost::any& value, bool change_event/* = false*/) +{ + m_disable_change_event = !change_event; + tmp_value = boost::any_cast(value); + m_value = value; + if (m_opt.nullable) { + const bool m_is_na_val = tmp_value == ConfigOptionIntsNullable::nil_value(); + if (m_is_na_val) + dynamic_cast(window)->SetValue(na_value()); + else { + m_last_meaningful_value = value; + dynamic_cast(window)->SetValue(tmp_value); + } + } + else + dynamic_cast(window)->SetValue(tmp_value); + m_disable_change_event = false; +} + +void SpinCtrl::set_last_meaningful_value() +{ + const int val = boost::any_cast(m_last_meaningful_value); + dynamic_cast(window)->SetValue(val); + tmp_value = val; + propagate_value(); +} + +void SpinCtrl::set_na_value() +{ + dynamic_cast(window)->SetValue(na_value()); + m_value = ConfigOptionIntsNullable::nil_value(); + propagate_value(); +} + +boost::any& SpinCtrl::get_value() +{ + wxSpinCtrl* spin = static_cast(window); + if (spin->GetTextValue() == na_value()) + return m_value; + + int value = spin->GetValue(); + return m_value = value; +} + void SpinCtrl::propagate_value() { // check if value was really changed diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index eaa4fe481..73af0ef53 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -330,24 +330,18 @@ public: void BUILD() override; /// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER void propagate_value() ; - +/* void set_value(const std::string& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetValue(value); m_disable_change_event = false; } - void set_value(const boost::any& value, bool change_event = false) override { - m_disable_change_event = !change_event; - tmp_value = boost::any_cast(value); - m_value = value; - dynamic_cast(window)->SetValue(tmp_value); - m_disable_change_event = false; - } +*/ + void set_value(const boost::any& value, bool change_event = false) override; + void set_last_meaningful_value() override; + void set_na_value() override; - boost::any& get_value() override { - int value = static_cast(window)->GetValue(); - return m_value = value; - } + boost::any& get_value() override; void msw_rescale() override; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 52d83eff9..3a69bcee1 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1826,45 +1826,59 @@ static void validate_custom_gcode_cb(Tab* tab, const wxString& title, const t_co tab->on_value_change(opt_key, value); } +void TabFilament::create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/) +{ + Line line {"",""}; + if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") { + Option opt = optgroup->get_option(opt_key); + opt.opt.label = opt.opt.full_label; + line = optgroup->create_single_option_line(opt); + } + else + line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); + + line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) { + wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); + + check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) { + const bool is_checked = evt.IsChecked(); + if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) { + if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) { + field->toggle(is_checked); + if (is_checked) + field->set_last_meaningful_value(); + else + field->set_na_value(); + } + } + }, check_box->GetId()); + + m_overrides_options[opt_key] = check_box; + return check_box; + }; + + optgroup->append_line(line); +} + +void TabFilament::update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/, bool is_checked/* = true*/) +{ + if (!m_overrides_options[opt_key]) + return; + m_overrides_options[opt_key]->Enable(is_checked); + + is_checked &= !m_config->option(opt_key)->is_nil(); + m_overrides_options[opt_key]->SetValue(is_checked); + + Field* field = optgroup->get_fieldc(opt_key, opt_index); + if (field != nullptr) + field->toggle(is_checked); +} + void TabFilament::add_filament_overrides_page() { PageShp page = add_options_page(L("Filament Overrides"), "wrench"); ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction")); - auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index) - { - Line line {"",""}; - if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") { - Option opt = optgroup->get_option(opt_key); - opt.opt.label = opt.opt.full_label; - line = optgroup->create_single_option_line(opt); - } - else - line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); - - line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) { - wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); - - check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) { - const bool is_checked = evt.IsChecked(); - if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) { - if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) { - field->toggle(is_checked); - if (is_checked) - field->set_last_meaningful_value(); - else - field->set_na_value(); - } - } - }, check_box->GetId()); - - m_overrides_options[opt_key] = check_box; - return check_box; - }; - - optgroup->append_line(line); - }; - const int extruder_idx = 0; // #ys_FIXME for (const std::string opt_key : { "filament_retract_length", @@ -1879,7 +1893,7 @@ void TabFilament::add_filament_overrides_page() "filament_wipe", "filament_retract_before_wipe" }) - append_single_option_line(opt_key, extruder_idx); + create_line_with_near_label_widget(optgroup, opt_key, extruder_idx); } void TabFilament::update_filament_overrides_page() @@ -1914,14 +1928,7 @@ void TabFilament::update_filament_overrides_page() for (const std::string& opt_key : opt_keys) { bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; - m_overrides_options[opt_key]->Enable(is_checked); - - is_checked &= !m_config->option(opt_key)->is_nil(); - m_overrides_options[opt_key]->SetValue(is_checked); - - Field* field = optgroup->get_fieldc(opt_key, extruder_idx); - if (field != nullptr) - field->toggle(is_checked); + update_line_with_near_label_widget(optgroup, opt_key, extruder_idx, is_checked); } } @@ -1952,13 +1959,14 @@ void TabFilament::build() }; optgroup = page->new_optgroup(L("Temperature")); + + create_line_with_near_label_widget(optgroup, "idle_temperature"); + Line line = { L("Nozzle"), "" }; line.append_option(optgroup->get_option("first_layer_temperature")); line.append_option(optgroup->get_option("temperature")); optgroup->append_line(line); - optgroup->append_single_option_line("idle_temperature"); - line = { L("Bed"), "" }; line.append_option(optgroup->get_option("first_layer_bed_temperature")); line.append_option(optgroup->get_option("bed_temperature")); @@ -2144,6 +2152,14 @@ void TabFilament::toggle_options() if (m_active_page->title() == "Filament Overrides") update_filament_overrides_page(); + + if (m_active_page->title() == "Filament") { + Page* page = m_active_page; + + const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Temperature"; }); + if (og_it != page->m_optgroups.end()) + update_line_with_near_label_widget(*og_it, "idle_temperature"); + } } void TabFilament::update() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index c060eb7fd..b131761e6 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -436,6 +436,8 @@ private: ogStaticText* m_volumetric_speed_description_line {nullptr}; ogStaticText* m_cooling_description_line {nullptr}; + void create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0); + void update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0, bool is_checked = true); void add_filament_overrides_page(); void update_filament_overrides_page(); void update_volumetric_flow_preset_hints(); From ae15032e0f5c973acf60e4590f8739521b72ff00 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Sat, 21 Jan 2023 15:11:20 +0100 Subject: [PATCH 202/206] Wipe tower: fixed missing travels to wipe tower on layers with no toolchanges --- src/libslic3r/GCode.cpp | 49 ++++++++----------------------- src/libslic3r/GCode/WipeTower.cpp | 3 +- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ffe19a9cb..ffffd9d31 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -230,9 +230,16 @@ namespace Slic3r { std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); - if (gcodegen.config().single_extruder_multi_material && ! tcr.priming) { + double current_z = gcodegen.writer().get_position().z(); + if (z == -1.) // in case no specific z was provided, print at current_z pos + z = current_z; + + const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id); + const bool will_go_down = ! is_approx(z, current_z); + + if (! needs_toolchange || (gcodegen.config().single_extruder_multi_material && ! tcr.priming)) { // Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the - // toolchange will travel there anyway. + // toolchange will travel there anyway (if there is a toolchange). gcode += gcodegen.retract(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcode += gcodegen.travel_to( @@ -241,47 +248,16 @@ namespace Slic3r { "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); } - - double current_z = gcodegen.writer().get_position().z(); - if (z == -1.) // in case no specific z was provided, print at current_z pos - z = current_z; - if (! is_approx(z, current_z)) { + + if (will_go_down) { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); gcode += gcodegen.writer().unretract(); } - - - // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. - // Otherwise, leave control to the user completely. - /*std::string toolchange_gcode_str; - const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; - if (! toolchange_gcode.empty()) { - DynamicConfig config; - int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; - config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); - config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); - toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); - check_add_eol(toolchange_gcode_str); - } - - std::string toolchange_command; - if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) - toolchange_command = gcodegen.writer().toolchange(new_extruder_id); - if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) - toolchange_gcode_str += toolchange_command; - else { - // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. - }*/ - std::string toolchange_gcode_str; std::string deretraction_str; - if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) { + if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { if (gcodegen.config().single_extruder_multi_material) gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z @@ -302,7 +278,6 @@ namespace Slic3r { gcode += tcr_gcode; check_add_eol(toolchange_gcode_str); - // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(end_pos.cast()); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index fec2eb86b..04fab3fcd 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -822,9 +822,8 @@ void WipeTower::toolchange_Unload( writer.travel(ramming_start_pos); // move to starting position else writer.set_position(ramming_start_pos); - // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: - if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { + if (m_semm && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { // this is y of the center of previous sparse infill border float sparse_beginning_y = 0.f; From 24a91d7420b4d723b8fab5483695f2ff490bc7a1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 23 Jan 2023 13:23:50 +0100 Subject: [PATCH 203/206] Fixed is_nil(size_t) when checking out-of-range element --- src/libslic3r/Config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ef3dc32c8..23be26ee7 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -776,7 +776,7 @@ public: static int nil_value() { return std::numeric_limits::max(); } // A scalar is nil, or all values of a vector are nil. bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } - bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } + bool is_nil(size_t idx) const override { return values[idx < this->values.size() ? idx : 0] == nil_value(); } std::string serialize() const override { From 5b0a0897f44b8b122e88fcd81e75f7422231854d Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 26 Jan 2023 09:43:08 +0100 Subject: [PATCH 204/206] followup of 70a9520cc3e2caf5f9062eac565cc3a297b881a5 into_u8 instead of format --- src/slic3r/GUI/UpdateDialogs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 5663262ca..7640f3220 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -193,7 +193,7 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos btn_ok->SetLabel(_L("Download")); btn_ok->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e){ boost::system::error_code ec; - std::string input = GUI::format(txtctrl_path->GetValue()); + std::string input = GUI::into_u8(txtctrl_path->GetValue()); boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(input), ec); if (ec) dir = boost::filesystem::path(input); From 643d50813d62004f8ad7cf3f2aa6ecfc582d839c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 26 Jan 2023 10:00:35 +0100 Subject: [PATCH 205/206] Removed function double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) and remaining code using it --- src/libslic3r/Geometry.cpp | 16 +--------------- src/libslic3r/Geometry.hpp | 1 - src/libslic3r/Model.cpp | 2 +- src/libslic3r/PrintObject.cpp | 4 ++-- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 7bab0ed7a..441825654 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -872,24 +872,10 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot } // This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. -double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) -{ - const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - const Vec3d& axis = angle_axis.axis(); - const double angle = angle_axis.angle(); -#ifndef NDEBUG - if (std::abs(angle) > 1e-8) { - assert(std::abs(axis.x()) < 1e-8); - assert(std::abs(axis.y()) < 1e-8); - } -#endif /* NDEBUG */ - return (axis.z() < 0) ? -angle : angle; -} - double rotation_diff_z(const Transform3d &trafo_from, const Transform3d &trafo_to) { auto m = trafo_to.linear() * trafo_from.linear().inverse(); - assert(std::abs(m.determinant() - 1)); + assert(std::abs(m.determinant() - 1) < EPSILON); Vec3d vx = m * Vec3d(1., 0., 0); // Verify that the linear part of rotation from trafo_from to trafo_to rotates around Z and is unity. assert(std::abs(std::hypot(vx.x(), vx.y()) - 1.) < 1e-5); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index d7cf32611..0421c0806 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -546,7 +546,6 @@ extern Transform3d transform3d_from_string(const std::string& transform_str); extern Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to); // Rotation by Z to align rot_xyz_from to rot_xyz_to. // This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. -extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to); extern double rotation_diff_z(const Transform3d &trafo_from, const Transform3d &trafo_to); // Is the angle close to a multiple of 90 degrees? diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b823eb4fa..bdd9bfd70 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1780,7 +1780,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) // Adjust the instances. for (size_t i = 0; i < this->instances.size(); ++ i) { ModelInstance &model_instance = *this->instances[i]; - model_instance.set_rotation(Vec3d(0., 0., Geometry::rotation_diff_z(reference_trafo.get_rotation(), model_instance.get_rotation()))); + model_instance.set_rotation(Vec3d(0., 0., Geometry::rotation_diff_z(reference_trafo.get_matrix(), model_instance.get_matrix()))); model_instance.set_scaling_factor(Vec3d(new_scaling_factor, new_scaling_factor, new_scaling_factor)); model_instance.set_mirror(Vec3d(1., 1., 1.)); } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dc75fb8a3..dfa280f45 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -69,8 +69,8 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor BoundingBoxf3 bbox = model_object->raw_bounding_box(); Vec3d bbox_center = bbox.center(); // We may need to rotate the bbox / bbox_center from the original instance to the current instance. - double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_rotation(), instances.front().model_instance->get_rotation()); - if (std::abs(z_diff) > EPSILON) { + double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_matrix(), instances.front().model_instance->get_matrix()); + if (std::abs(z_diff) > EPSILON) { auto z_rot = Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()); bbox = bbox.transformed(Transform3d(z_rot)); bbox_center = (z_rot * bbox_center).eval(); From 25a941d8e7939f7adb38f8870524bb59cbefdded Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 26 Jan 2023 10:23:50 +0100 Subject: [PATCH 206/206] Code cleanup --- src/slic3r/GUI/Selection.cpp | 302 ----------------------------------- 1 file changed, 302 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 76bbd825c..5678008d3 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -2950,227 +2950,6 @@ static void verify_instances_rotation_synchronized(const Model &model, const GLV #endif /* NDEBUG */ #if ENABLE_WORLD_COORDINATE -#define NO_TEST 0 -#define TEST_1 1 -#define TEST_2 2 -#define TEST_3 3 -#define USE_ALGORITHM TEST_3 - -#if USE_ALGORITHM == TEST_1 -void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) -{ - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - - const GLVolume& volume_i = *(*m_volumes)[i]; - if (volume_i.is_wipe_tower) - continue; - - const Geometry::Transformation& trafo_inst_i = volume_i.get_instance_transformation(); - const Vec3d offset_i = trafo_inst_i.get_offset(); - const double rotation_z_i = trafo_inst_i.get_rotation().z(); - - // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& volume_j = *(*m_volumes)[j]; - if (volume_j.object_idx() != volume_i.object_idx() || volume_j.instance_idx() == volume_i.instance_idx()) - continue; - - const Geometry::Transformation& trafo_inst_j = m_cache.volumes_data[j].get_instance_transform(); - const Vec3d offset_j = trafo_inst_j.get_offset(); - const double rotation_z_j = trafo_inst_j.get_rotation().z(); - const double diff_rotation_z = rotation_z_j - rotation_z_i; - - const Transform3d new_matrix_inst_j = Geometry::translation_transform(offset_j) * Geometry::rotation_transform(diff_rotation_z * Vec3d::UnitZ()) * - Geometry::translation_transform(-offset_i) * trafo_inst_i.get_matrix(); - - volume_j.set_instance_transformation(Geometry::Transformation(new_matrix_inst_j)); - done.insert(j); - } - } - -//#ifndef NDEBUG -// verify_instances_rotation_synchronized(*m_model, *m_volumes); -//#endif /* NDEBUG */ -} -#elif USE_ALGORITHM == TEST_2 -void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) -{ - auto fix_rotation = [](const Vec3d& rotation) { - const bool x = std::abs(std::abs(rotation.x()) - (double)PI) < EPSILON; - const bool y = std::abs(std::abs(rotation.y()) - (double)PI) < EPSILON; - const bool z = std::abs(std::abs(rotation.z()) - (double)PI) < EPSILON; - - Vec3d ret = rotation; - if ((x && y) || (x && z) || (y && z)) { - ret += (double)PI * Vec3d::Ones(); - if (ret.x() >= 2.0f * (double)PI) ret.x() -= 2.0f * (double)PI; - if (ret.y() >= 2.0f * (double)PI) ret.y() -= 2.0f * (double)PI; - if (ret.z() >= 2.0f * (double)PI) ret.z() -= 2.0f * (double)PI; - } - - return ret; - }; - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - - const GLVolume& volume_i = *(*m_volumes)[i]; - if (volume_i.is_wipe_tower) - continue; - - const Geometry::Transformation& trafo_inst_i = volume_i.get_instance_transformation(); - const Vec3d offset_i = trafo_inst_i.get_offset(); - const double rotation_z_i = fix_rotation(trafo_inst_i.get_rotation()).z(); - - // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& volume_j = *(*m_volumes)[j]; - if (volume_j.object_idx() != volume_i.object_idx() || volume_j.instance_idx() == volume_i.instance_idx()) - continue; - - const Geometry::Transformation& trafo_inst_j = m_cache.volumes_data[j].get_instance_transform(); - const Vec3d offset_j = trafo_inst_j.get_offset(); - const double rotation_z_j = fix_rotation(trafo_inst_j.get_rotation()).z(); - const double diff_rotation_z = rotation_z_j - rotation_z_i; - - const Transform3d new_matrix_inst_j = Geometry::translation_transform(offset_j) * Geometry::rotation_transform(diff_rotation_z * Vec3d::UnitZ()) * - Geometry::translation_transform(-offset_i) * trafo_inst_i.get_matrix(); - - volume_j.set_instance_transformation(Geometry::Transformation(new_matrix_inst_j)); - done.insert(j); - } - } - -//#ifndef NDEBUG -// verify_instances_rotation_synchronized(*m_model, *m_volumes); -//#endif /* NDEBUG */ -} -#elif USE_ALGORITHM == TEST_3 - - -#if 0 -#define APPLY_FIX_ROTATION 1 -#define APPLY_FIX_ROTATION_2 2 && APPLY_FIX_ROTATION -void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) -{ -#if APPLY_FIX_ROTATION - auto fix_rotation = [](const Vec3d& rotation) { - const bool x = std::abs(std::abs(rotation.x()) - (double)PI) < EPSILON; - const bool y = std::abs(std::abs(rotation.y()) - (double)PI) < EPSILON; - const bool z = std::abs(std::abs(rotation.z()) - (double)PI) < EPSILON; - - Vec3d ret = rotation; - if ((x && y) || (x && z) || (y && z)) { - ret += (double)PI * Vec3d::Ones(); - if (ret.x() >= 2.0f * (double)PI) ret.x() -= 2.0f * (double)PI; - if (ret.y() >= 2.0f * (double)PI) ret.y() -= 2.0f * (double)PI; - if (ret.z() >= 2.0f * (double)PI) ret.z() -= 2.0f * (double)PI; - } - - return ret; - }; - -#if APPLY_FIX_ROTATION_2 - auto fix_rotation_2 = [](const Vec3d& rotation) { - Vec3d ret = rotation; - if (0.5 * (double)PI <= rotation.y() && rotation.y() <= 1.5 * (double)PI) - ret.y() = 2.0 * (double)PI - ret.y(); - return ret; - }; -#endif // APPLY_FIX_ROTATION_2 -#endif // APPLY_FIX_ROTATION - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - - const GLVolume& volume_i = *(*m_volumes)[i]; - if (volume_i.is_wipe_tower) - continue; - - const Geometry::Transformation& cached_trafo_inst_i = m_cache.volumes_data[i].get_instance_transform(); - const Geometry::Transformation& trafo_inst_i = volume_i.get_instance_transformation(); - Geometry::Transformation trafo_i = Geometry::Transformation(trafo_inst_i.get_matrix() * cached_trafo_inst_i.get_matrix().inverse()); - - Matrix3d rotation_comp_i; - Matrix3d scale_comp_i; - trafo_i.get_matrix().computeRotationScaling(&rotation_comp_i, &scale_comp_i); -#if APPLY_FIX_ROTATION -#if APPLY_FIX_ROTATION_2 - const Vec3d rotation_i = fix_rotation_2(fix_rotation(Geometry::extract_rotation(Transform3d(rotation_comp_i)))); -#else - const Vec3d rotation_i = fix_rotation(Geometry::extract_rotation(Transform3d(rotation_comp_i))); -#endif // APPLY_FIX_ROTATION_2 -#else - const Vec3d rotation_i = Geometry::extract_rotation(Transform3d(rotation_comp_i)); -#endif // APPLY_FIX_ROTATION - - std::cout << "rotation_i: " << to_string(rotation_i); - - const Vec3d rotation_no_z_i(rotation_i.x(), rotation_i.y(), 0.0); - - trafo_i = Geometry::Transformation(Geometry::rotation_transform(rotation_no_z_i) * Transform3d(scale_comp_i)); - - // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& volume_j = *(*m_volumes)[j]; - if (volume_j.object_idx() != volume_i.object_idx() || volume_j.instance_idx() == volume_i.instance_idx()) - continue; - - const Geometry::Transformation& cached_trafo_inst_j = m_cache.volumes_data[j].get_instance_transform(); -#if APPLY_FIX_ROTATION - const Vec3d rotation_cached_trafo_inst_j = fix_rotation(cached_trafo_inst_j.get_rotation()); -#else - const Vec3d rotation_cached_trafo_inst_j = cached_trafo_inst_j.get_rotation(); -#endif // APPLY_FIX_ROTATION - - std::cout << " - rotation_cached_trafo_inst_j: " << to_string(rotation_cached_trafo_inst_j) << "\n"; - - const Transform3d rotation_z_cached_trafo_inst_j = Geometry::rotation_transform({ 0.0, 0.0, rotation_cached_trafo_inst_j.z() }); - - const Transform3d new_matrix_inst_j = cached_trafo_inst_j.get_offset_matrix() * rotation_z_cached_trafo_inst_j * trafo_i.get_matrix() * - rotation_z_cached_trafo_inst_j.inverse() * cached_trafo_inst_j.get_matrix_no_offset(); - - volume_j.set_instance_transformation(Geometry::Transformation(new_matrix_inst_j)); - done.insert(j); - } - } - -//#ifndef NDEBUG -// verify_instances_rotation_synchronized(*m_model, *m_volumes); -//#endif /* NDEBUG */ -} -#else void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) { std::set done; // prevent processing volumes twice @@ -3215,87 +2994,6 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ verify_instances_rotation_synchronized(*m_model, *m_volumes); #endif /* NDEBUG */ } -#endif -#else -void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) -{ - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - - const GLVolume* volume_i = (*m_volumes)[i]; - if (volume_i->is_wipe_tower) - continue; - - const int object_idx = volume_i->object_idx(); - const int instance_idx = volume_i->instance_idx(); - const Geometry::Transformation& curr_inst_trafo_i = volume_i->get_instance_transformation(); - const Vec3d curr_inst_rotation_i = curr_inst_trafo_i.get_rotation(); - const Vec3d& curr_inst_scaling_factor_i = curr_inst_trafo_i.get_scaling_factor(); - const Vec3d& curr_inst_mirror_i = curr_inst_trafo_i.get_mirror(); - const Vec3d old_inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); - - // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume* volume_j = (*m_volumes)[j]; - if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) - continue; - - const Vec3d old_inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); - assert(is_rotation_xy_synchronized(old_inst_rotation_i, old_inst_rotation_j)); - const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); - const Vec3d curr_inst_rotation_j = curr_inst_trafo_j.get_rotation(); - Vec3d new_inst_offset_j = curr_inst_trafo_j.get_offset(); - Vec3d new_inst_rotation_j = curr_inst_rotation_j; - - switch (sync_rotation_type) { - case SyncRotationType::NONE: { - // z only rotation -> synch instance z - // The X,Y rotations should be synchronized from start to end of the rotation. - assert(is_rotation_xy_synchronized(curr_inst_rotation_i, curr_inst_rotation_j)); - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) - new_inst_offset_j.z() = curr_inst_trafo_i.get_offset().z(); - break; - } - case SyncRotationType::GENERAL: { - // generic rotation -> update instance z with the delta of the rotation. - const double z_diff = Geometry::rotation_diff_z(old_inst_rotation_i, old_inst_rotation_j); - new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); - break; - } - case SyncRotationType::FULL: { - // generic rotation -> update instance z with the delta of the rotation. - const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(curr_inst_rotation_i, old_inst_rotation_j)); - const Vec3d& axis = angle_axis.axis(); - const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? - angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(curr_inst_rotation_i, old_inst_rotation_j); - - new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); - break; - } - } - - volume_j->set_instance_transformation(Geometry::assemble_transform(new_inst_offset_j, new_inst_rotation_j, - curr_inst_scaling_factor_i, curr_inst_mirror_i)); - - done.insert(j); - } - } - -#ifndef NDEBUG - verify_instances_rotation_synchronized(*m_model, *m_volumes); -#endif /* NDEBUG */ -} -#endif // USE_ALGORITHM #else void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) {