diff --git a/resources/shaders/110/gouraud_light_clip.fs b/resources/shaders/110/gouraud_light_clip.fs new file mode 100644 index 0000000000..5c70687093 --- /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 0000000000..6d7c32e1b3 --- /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 0000000000..714e5bcaae --- /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 0000000000..8fca59380f --- /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 0000000000..45cae0ddb0 --- /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 0000000000..f3ab8c3dc0 --- /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/AABBMesh.cpp b/src/libslic3r/AABBMesh.cpp index 835df2ebec..ca7042c601 100644 --- a/src/libslic3r/AABBMesh.cpp +++ b/src/libslic3r/AABBMesh.cpp @@ -68,9 +68,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 +94,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 +102,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 312d8926db..3ef25977b4 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/libslic3r/AnyPtr.hpp b/src/libslic3r/AnyPtr.hpp new file mode 100644 index 0000000000..c40d10093e --- /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 7e02598d83..09a7ed65d2 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 @@ -22,6 +22,7 @@ set(SLIC3R_SOURCES AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp + AnyPtr.hpp BoundingBox.cpp BoundingBox.hpp BridgeDetector.cpp @@ -39,6 +40,13 @@ set(SLIC3R_SOURCES Color.hpp Config.cpp Config.hpp + CSGMesh/CSGMesh.hpp + CSGMesh/SliceCSGMesh.hpp + 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 new file mode 100644 index 0000000000..d14ed76595 --- /dev/null +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -0,0 +1,86 @@ +#ifndef CSGMESH_HPP +#define CSGMESH_HPP + +#include +#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 }; + +// 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 +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; +} + +// 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; +} + +// Default implementation +struct CSGPart { + AnyPtr its_ptr; + Transform3f trafo; + CSGType operation; + CSGStackOp stack_operation; + + CSGPart(AnyPtr ptr = {}, + CSGType op = CSGType::Union, + const Transform3f &tr = Transform3f::Identity()) + : its_ptr{std::move(ptr)} + , operation{op} + , stack_operation{CSGStackOp::Continue} + , trafo{tr} + {} +}; + +}} // namespace Slic3r::csg + +#endif // CSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/CSGMeshCopy.hpp b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp new file mode 100644 index 0000000000..78800f9bbb --- /dev/null +++ b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp @@ -0,0 +1,80 @@ +#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. 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{{}, + 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; + } +} + +// 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/ModelToCSGMesh.hpp b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp new file mode 100644 index 0000000000..9e413594ed --- /dev/null +++ b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp @@ -0,0 +1,88 @@ +#ifndef MODELTOCSGMESH_HPP +#define MODELTOCSGMESH_HPP + +#include "CSGMesh.hpp" + +#include "libslic3r/Model.hpp" +#include "libslic3r/SLA/Hollowing.hpp" +#include "libslic3r/MeshSplitImpl.hpp" + +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 +}; + +template +void model_to_csgmesh(const ModelObject &mo, + const Transform3d &trafo, // Applies to all exported parts + OutIt out, // Output iterator + // values of ModelParts OR-ed + 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; + 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()))) { + + 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) { + if (its.empty()) + return; + + 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; + } + } + } + + 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 0000000000..aabe9a2de3 --- /dev/null +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -0,0 +1,205 @@ +#ifndef PERFORMCSGMESHBOOLEANS_HPP +#define PERFORMCSGMESHBOOLEANS_HPP + +#include +#include + +#include "CSGMesh.hpp" + +#include "libslic3r/Execution/ExecutionTBB.hpp" +//#include "libslic3r/Execution/ExecutionSeq.hpp" +#include "libslic3r/MeshBoolean.hpp" + +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); + indexed_triangle_set dummy; + + if (!its) + its = &dummy; + + MeshBoolean::cgal::CGALMeshPtr ret; + + indexed_triangle_set m = *its; + auto tr = get_transform(csgpart); + its_transform(m, get_transform(csgpart), true); + + try { + ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m); + } catch (...) { + // errors are ignored, simply return null + ret = nullptr; + } + + return ret; +} + +namespace detail_cgal { + +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); + 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 + +// Process the sequence of CSG parts with CGAL. +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; 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{}}; + + opstack.push(Frame{}); + + 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(Frame{op}); + op = CSGType::Union; + } + + Frame *top = &opstack.top(); + + 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); + + // 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; + } + + 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); + }; + 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 (ret == csgrange.end()) + ret = it; + } + } + + return ret; +} + +template +It check_csgmesh_booleans(const Range &csgrange) +{ + return check_csgmesh_booleans(csgrange, [](auto &) {}); +} + +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 new file mode 100644 index 0000000000..9d7b9a077d --- /dev/null +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -0,0 +1,131 @@ +#ifndef SLICECSGMESH_HPP +#define SLICECSGMESH_HPP + +#include "CSGMesh.hpp" + +#include + +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" + +namespace Slic3r { namespace csg { + +namespace detail { + +inline 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; + } +} + +inline 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, + const std::vector &slicegrid, + const MeshSlicingParamsEx ¶ms, + const std::function &throw_on_cancel = [] {}) +{ + using namespace detail; + + 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()); + + opstack.push({CSGType::Union, std::vector(slicegrid.size())}); + + for (const auto &csgpart : csgrange) { + const indexed_triangle_set *its = csg::get_mesh(csgpart); + + auto op = get_operation(csgpart); + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push({op, std::vector(slicegrid.size())}); + op = CSGType::Union; + } + + Frame *top = &opstack.top(); + + 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()); + + collect_nonempty_indices(op, slicegrid, slices, nonempty_indices); + + 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) { + std::vector popslices = std::move(top->slices); + auto popop = opstack.top().op; + opstack.pop(); + std::vector &prev_slices = opstack.top().slices; + + collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices); + + 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); + }); + + // 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; +} + +}} // namespace Slic3r::csg + +#endif // SLICECSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp b/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp new file mode 100644 index 0000000000..81b05b0463 --- /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/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp new file mode 100644 index 0000000000..f64d17b9a4 --- /dev/null +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -0,0 +1,116 @@ +#ifndef VOXELIZECSGMESH_HPP +#define VOXELIZECSGMESH_HPP + +#include +#include + +#include "CSGMesh.hpp" +#include "libslic3r/OpenVDBUtils.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" + +namespace Slic3r { namespace csg { + +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, 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(*its, params); + + return ret; +} + +namespace detail { + +inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src) +{ + if (!dst || !src) + return; + + switch (op) { + case CSGType::Union: + 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); + 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()); + + 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; + struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; }; + std::stack opstack{std::vector{}}; + + opstack.push({CSGType::Union, mesh_to_grid({}, params)}); + + for (auto &csgpart : csgrange) { + if (params.statusfn() && params.statusfn()(-1)) + break; + + auto &partgrid = grids[csgidx++]; + + auto op = get_operation(csgpart); + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push({op, mesh_to_grid({}, params)}); + op = CSGType::Union; + } + + Frame *top = &opstack.top(); + + perform_csg(get_operation(csgpart), top->grid, partgrid); + + 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; + perform_csg(popop, grid, popgrid); + } + } + + ret = std::move(opstack.top().grid); + + return ret; +} + +}} // namespace Slic3r::csg + +#endif // VOXELIZECSGMESH_HPP diff --git a/src/libslic3r/Execution/ExecutionTBB.hpp b/src/libslic3r/Execution/ExecutionTBB.hpp index 2250b8e322..db4ee243fd 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)); diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 86b50d1562..c7ebcbd19e 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; @@ -282,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 140c969310..e20425c1c7 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -26,27 +26,35 @@ 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 clone(const CGALMesh &m); -inline std::unique_ptr triangle_mesh_to_cgal(const indexed_triangle_set &M) +CGALMeshPtr triangle_mesh_to_cgal( + const std::vector &V, + const std::vector &F); + +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); } 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/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index c9df648fb9..0f1fe44c35 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/Model.cpp b/src/libslic3r/Model.cpp index 8829253e49..40a87cb69c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2626,6 +2626,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 0151cf61dc..4c6eaed1c2 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1387,6 +1387,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/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 2c207bb6a7..21409445fc 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[2]), int(v[1]), int(v[0])}; } + 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,26 @@ 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) +struct Interrupter +{ + std::function statusfn; + + void start(const char* name = nullptr) { (void)name; } + void end() {} + + inline bool wasInterrupted(int percent = -1) { return statusfn && statusfn(percent); } +}; + +VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh, + const MeshToGridParams ¶ms) { // Might not be needed but this is now proven to be working openvdb::initialize(); @@ -55,51 +93,63 @@ 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 = 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, voxel_scale}, tr, 1.f, 1.f); + interrupter, + TriangleMeshDataAdapter{m, trafo}, + openvdb::math::Transform{}, + params.exterior_bandwidth(), + params.interior_bandwidth()); - if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); - else if (subgrid) grid = std::move(subgrid); + if (interrupter.wasInterrupted()) + break; + + 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 (interrupter.wasInterrupted()) + return {}; + + if (meshparts.empty()) { // Splitting failed, fall back to hollow the original mesh grid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{mesh}, tr, 1.f, 1.f); + interrupter, + TriangleMeshDataAdapter{mesh, trafo}, + openvdb::math::Transform{}, + params.exterior_bandwidth(), + params.interior_bandwidth()); } - constexpr int DilateIterations = 1; + if (interrupter.wasInterrupted()) + return {}; - grid = openvdb::tools::dilateSdf( - *grid, interiorBandWidth, openvdb::tools::NN_FACE_EDGE, - DilateIterations, - openvdb::tools::FastSweepingDomain::SWEEP_LESS_THAN_ISOVALUE); + grid->transform().preScale(1./params.voxel_scale()); + grid->insertMeta("voxel_scale", openvdb::FloatMetadata(params.voxel_scale())); - grid = openvdb::tools::dilateSdf( - *grid, exteriorBandWidth, openvdb::tools::NN_FACE_EDGE, - DilateIterations, - openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE); + VoxelGridPtr ret = make_voxelgrid(std::move(*grid)); - grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); - - return 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 +157,152 @@ 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(3), quad(2), quad(0)); } 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); +} + +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 254ae35833..d4996854c2 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -3,21 +3,47 @@ #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); + +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 @@ -26,24 +52,32 @@ 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 MeshToGridParams ¶ms = {}); -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); + +bool is_grid_empty(const VoxelGrid &grid); } // namespace Slic3r diff --git a/src/libslic3r/OpenVDBUtilsLegacy.hpp b/src/libslic3r/OpenVDBUtilsLegacy.hpp new file mode 100644 index 0000000000..f292af1c89 --- /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/Preset.cpp b/src/libslic3r/Preset.cpp index 1f5fbc6b24..53eca40c6d 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -516,6 +516,7 @@ static std::vector s_Preset_sla_print_options { "support_max_weight_on_model", "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/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 607a7b5a55..4c009cac88 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -740,6 +740,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/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index cf2753b183..c19722a145 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3835,6 +3835,13 @@ void PrintConfigDef::init_sla_params() init_sla_support_params(""); init_sla_support_params("branching"); + 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_points_density_relative", coInt); def->label = L("Support points density"); def->category = L("Supports"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ca66790515..85c2ca794a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -864,6 +864,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_max_weight_on_model)) + // 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/QuadricEdgeCollapse.hpp b/src/libslic3r/QuadricEdgeCollapse.hpp index 956ad35113..0043fc24a3 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 diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 74eb076959..634cde4695 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -1,20 +1,25 @@ #include #include +#include +#include +#include #include #include #include #include +#include #include #include #include #include #include +#include + +#include #include -#include - #include #include @@ -27,19 +32,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,46 +61,43 @@ 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; - float in_range = 1.1f * float(offset + D); - auto narrowb = 1.; - float out_range = narrowb; + return *interior.gridptr; +} + +VoxelGrid &get_grid(Interior &interior) +{ + return *interior.gridptr; +} + +InteriorPtr generate_interior(const VoxelGrid &vgrid, + const HollowingConfig &hc, + const JobController &ctl) +{ + 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")); - 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")); 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); - 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, 1.1 * std::ceil(iso_surface), 0.f); out_range = iso_surface; + in_range = narrowb / voxsc; } else { iso_surface = -offset; } @@ -109,68 +109,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; } @@ -179,9 +125,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; @@ -208,7 +154,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 +276,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); @@ -344,7 +289,24 @@ 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); +} + +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 @@ -354,13 +316,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 +325,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 +335,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 @@ -434,14 +396,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) { @@ -477,8 +439,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 @@ -500,7 +462,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; @@ -540,7 +502,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)]}; @@ -548,7 +511,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)}; @@ -581,8 +545,356 @@ 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 + 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 (Vec3i face : its.indices) { + std::swap(face(0), face(2)); + 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 Transform3d& tr = trans.get_matrix(); + for (sla::DrainHole &hl : pts) { + Vec3d pos = hl.pos.cast(); + Vec3d nrm = hl.normal.cast(); + + 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. + pos -= nrm.normalized() * sla::HoleStickOutLength; + + hl.pos = pos.cast(); + hl.normal = nrm.cast(); + 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; +} + +// 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 b57513fe72..b6c07aff5a 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,26 +77,116 @@ 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 statusfn = [&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); }; + auto grid = mesh_to_grid(mesh, MeshToGridParams{} + .voxel_scale(voxel_scale) + .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, 3.f, 3.f); + + 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 = csgmesh_positive_maxvolume(csgparts); + double voxsc = get_voxel_scale(mesh_vol, hc); + + auto params = csg::VoxelizeParams{} + .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); + + if (!ptr || (ctl.stopcondition && ctl.stopcondition())) + return {}; + + // 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{}; +} + // Will do the hollowing 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 = {}); -double get_distance(const Vec3f &p, const Interior &interior); +void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior, + const std::vector &exclude_mask = {}); -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 +200,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/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 71849a3643..cf6dbabbaf 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 6d159b37b6..193333bc6a 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/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index e0d7db97de..53fb16f6e7 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -118,6 +118,7 @@ struct SupportableMesh SupportPoints pts; SupportTreeConfig cfg; PadConfig pad_cfg; + double zoffset = 0.; explicit SupportableMesh(const indexed_triangle_set &trmsh, const SupportPoints &sp, @@ -134,7 +135,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 d991dfe05f..d4023f64d3 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,13 +1,9 @@ #include "SLAPrint.hpp" #include "SLAPrintSteps.hpp" +#include "CSGMesh/CSGMeshCopy.hpp" +#include "CSGMesh/PerformCSGMeshBooleans.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 @@ -17,7 +13,7 @@ #include #include -// #define SLAPRINT_DO_BENCHMARK + #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK #include @@ -41,7 +37,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; @@ -423,7 +419,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() && @@ -632,7 +633,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 @@ -828,12 +829,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); - } - }) {} SLAPrintObject::~SLAPrintObject() {} @@ -870,6 +865,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vectorinvalidate_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); @@ -962,7 +960,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 { @@ -1053,113 +1051,71 @@ 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 slaposDrillHoles: - return m_hollowing_data && !m_hollowing_data->hollow_mesh_with_holes.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 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 +const std::shared_ptr & +SLAPrintObject::get_mesh_to_print() 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; + int s = last_completed_step(); + + while (s > 0 && ! m_preview_meshes[s]) + --s; + + return m_preview_meshes[s]; } -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. +std::vector SLAPrintObject::get_parts_to_slice() const +{ + return get_parts_to_slice(slaposCount); +} - return m_transformed_rmesh.get(); +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); - 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 { - 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(); + assert(model_object()); - // 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(*model_object(), trafo()); } DynamicConfig SLAPrintStatistics::config() const @@ -1216,4 +1172,17 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p, p.set_status(int(std::round(st)), msg, flags); } +namespace csg { + +MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartForStep &part) +{ + if (!part.cgalcache && csg::get_mesh(part)) { + part.cgalcache = csg::get_cgalmesh(static_cast(part)); + } + + return part.cgalcache? clone(*part.cgalcache) : nullptr; +} + +} // namespace csg + } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 2ebb7cc2fd..bd8424ca78 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,16 +3,18 @@ #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/CSGMesh/CSGMesh.hpp" +#include "libslic3r/MeshBoolean.hpp" +#include "libslic3r/OpenVDBUtils.hpp" -#include "libslic3r/Execution/ExecutionTBB.hpp" +#include namespace Slic3r { @@ -23,6 +25,7 @@ enum SLAPrintStep : unsigned int { }; enum SLAPrintObjectStep : unsigned int { + slaposAssembly, slaposHollowing, slaposDrillHoles, slaposObjectSlice, @@ -45,10 +48,47 @@ using _SLAPrintObjectBase = enum SliceOrigin { soSupport, soModel }; +} // namespace Slic3r + +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. +// 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 MeshBoolean::cgal::CGALMeshPtr cgalcache; + + 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 { + +MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartForStep &part); + +} // namespace csg + class SLAPrintObject : public _SLAPrintObjectBase { private: // Prevents erroneous use by other classes. using Inherited = _SLAPrintObjectBase; + using CSGContainer = std::multiset; public: @@ -72,31 +112,20 @@ 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 { - return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh(); - } + const std::shared_ptr& get_mesh_to_print() const; - const TriangleMesh & get_mesh_to_slice() const { - return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh(); - } + std::vector get_parts_to_slice() const; - // This will return the transformed mesh which is cached - const TriangleMesh& transformed_mesh() const; + std::vector get_parts_to_slice(SLAPrintObjectStep step) const; sla::SupportPoints transformed_support_points() const; sla::DrainHoles transformed_drainhole_points() const; @@ -275,7 +304,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); } @@ -306,9 +336,6 @@ private: std::vector m_model_height_levels; - // Caching the transformed (m_trafo) raw mesh of the object - mutable CachedObject m_transformed_rmesh; - struct SupportData { sla::SupportableMesh input; // the input @@ -318,6 +345,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) { @@ -329,16 +360,32 @@ private: pad_mesh = TriangleMesh{sla::create_pad(input, tree_mesh.its, ctl)}; } }; - - std::unique_ptr m_supportdata; - + + std::unique_ptr m_supportdata; + + // 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 + // or an approximation of that. + std::array, SLAPrintObjectStep::slaposCount + 1> m_preview_meshes; + 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; @@ -421,6 +468,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/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index bed4f02498..92260deecb 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -14,8 +14,17 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +//#include #include @@ -32,18 +41,20 @@ namespace Slic3r { namespace { const std::array OBJ_STEP_LEVELS = { - 10, // 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) { 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"); @@ -76,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)} @@ -119,9 +129,228 @@ 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), true); + 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 = 12.; + + // update preview mesh + double vscale = std::min(MaxPreviewVoxelScale, + 1. / po.m_config.layer_height.getFloat()); + + auto voxparams = csg::VoxelizeParams{} + .voxel_scale(vscale) + .exterior_bandwidth(1.f) + .interior_bandwidth(1.f); + + voxparams.statusfn([&po](int){ + 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{}; + + 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) { + 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, + // 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(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(); + + + 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()) { + // 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); + 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. " + "This does not affect the quality of slices or the physical print in any way.")); + m = generate_preview_vdb(po, step); + } + + po.m_preview_meshes[step] = + std::make_shared(std::move(m)); + + for (size_t i = size_t(step) + 1; i < slaposCount; ++i) + { + po.m_preview_meshes[i] = {}; + } + + bench.stop(); + + if (!m.empty()) + BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << bench.getElapsedSec(); + else + BOOST_LOG_TRIVIAL(error) << "Preview failed!"; + + 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); + s.erase(r.first, r.second); +} + +struct csg_inserter { + std::multiset &m; + SLAPrintObjectStep key; + + csg_inserter &operator*() { return *this; } + void operator=(csg::CSGPart &&part) + { + part.its_ptr.convert_unique_to_shared(); + 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_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 | csg::mpartsDoSplits); + + generate_preview(po, slaposAssembly); +} + 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); if (! po.m_config.hollowing_enable.getBool()) { BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; @@ -134,348 +363,104 @@ 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.mesh_to_slice(), 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; - - // 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); - } - } -} - -// 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; + its_compactify_vertices(m); + its_merge_vertices(m); } - if (exclude_mask[fi]) { - exclude_neighbors(face, exclude_mask, its, neighbor_index, 1); - continue; - } + // Put the interior into the target mesh as a negative + po.m_mesh_to_slice + .emplace(slaposHollowing, + csg::CSGPart{std::make_shared(m), + csg::CSGType::Difference}); - // 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); - } - } + generate_preview(po, slaposHollowing); } - - 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]); - } - - 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()); + po.m_supportdata.reset(); + 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()); + generate_preview(po, slaposDrillHoles); - // Hollowing and/or drilling is active, m_hollowing_data is valid. + // Release the data, won't be needed anymore, takes huge amount of ram + if (po.m_hollowing_data && po.m_hollowing_data->interior) + po.m_hollowing_data->interior.reset(); +} - // 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]); - // 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)) { - 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. + BoundingBoxf3 bb3d; + + 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) + bb3d.merge(bounding_box(*csg::get_mesh(m), csg::get_transform(m))); + + if (stackop == csg::CSGStackOp::Pop) + skip = false; } - 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 @@ -488,14 +473,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); @@ -537,27 +525,8 @@ 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)); - } + po.m_model_slices = slice_csgmesh_ex(po.mesh_to_slice(), slice_grid, params, thr); auto mit = slindex_it; for (size_t id = 0; @@ -569,10 +538,67 @@ 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())); +// po.m_preview_meshes[slaposObjectSlice] = po.get_mesh_to_print(); +// report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); +} + + +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((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()); + + 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 < mask.enforcers.size()) { + for (size_t enf_idx = 0; + !is_enforced && enf_idx < mask.enforcers[idx].size(); + ++enf_idx) + { + if (mask.enforcers[idx][enf_idx].contains(sp2d)) + is_enforced = true; + } + } + + bool is_blocked = false; + 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; + } + } + + 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 @@ -582,8 +608,15 @@ 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) - po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print())); + if (!po.m_supportdata) { + auto &meshp = po.get_mesh_to_print(); + assert(meshp); + po.m_supportdata = + std::make_unique(*meshp); + } + + po.m_supportdata->input.zoffset = csgmesh_positive_bb(po.m_mesh_to_slice) + .min.z(); const ModelObject& mo = *po.m_model_object; @@ -598,11 +631,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(); @@ -630,8 +658,28 @@ 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(); + }); + + 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; BOOST_LOG_TRIVIAL(debug) @@ -653,23 +701,17 @@ 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())) { remove_bottom_points(po.m_supportdata->input.pts, float( - po.m_supportdata->input.emesh.ground_level() + + po.m_supportdata->input.zoffset + EPSILON)); } 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; @@ -713,6 +755,13 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // repeated) if(po.m_config.pad_enable.getBool()) { + if (!po.m_supportdata) { + auto &meshp = po.get_mesh_to_print(); + assert(meshp); + po.m_supportdata = + std::make_unique(*meshp); + } + // 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 @@ -720,16 +769,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(); }; @@ -1162,6 +1201,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 19b64d4a98..32d10a424c 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -14,40 +14,43 @@ 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); + indexed_triangle_set generate_preview_vdb(SLAPrintObject &po, SLAPrintObjectStep step); + 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); @@ -55,20 +58,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 diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp index 969fa8dacf..9e290d472b 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. @@ -52,7 +54,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 +67,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 +84,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 8363cd6953..5a9e2f646b 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 26701bdf53..6473f77837 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -269,12 +269,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, @@ -346,6 +352,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 79945867bf..2b96994fa7 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -351,10 +351,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/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 236aedf66f..453d7eeb5a 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/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index d83890a40c..185ebb743e 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -476,51 +476,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. -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) -{ - 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_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); - v.mesh_raycaster = std::make_unique(std::make_shared(mesh)); -#endif // ENABLE_SMOOTH_NORMALS - 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_OPENGL_ES int GLVolumeCollection::load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, float height, diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 674c3ce824..4c19927ef3 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -181,7 +181,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; @@ -416,16 +416,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); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 85fe3e53e4..d442a86466 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -389,6 +389,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) 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_enforcers_only", supports_en); toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree); toggle_field("support_pillar_widening_factor", false); toggle_field("support_max_weight_on_model", false); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d8a005b34c..81194038e1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1823,15 +1823,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; @@ -1856,33 +1847,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].is_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::State::Fresh; - 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. @@ -1996,112 +1961,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. - volume.model.reset(); - if (state.step[istep].is_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 - volume.model.init_from(mesh, true); -#else - volume.model.init_from(mesh); - volume.mesh_raycaster = std::make_unique(std::make_shared(mesh)); -#endif // ENABLE_SMOOTH_NORMALS - } - else { - // Reload the original volume. -#if ENABLE_SMOOTH_NORMALS - volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#else - 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)); -#endif // ENABLE_SMOOTH_NORMALS - } - } - //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].is_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()) - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); - } - - // 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")) { // Should the wipe tower be visualized ? @@ -2212,6 +2071,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) @@ -3583,7 +3448,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 @@ -3763,7 +3628,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()); @@ -6803,7 +6668,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 @@ -6816,29 +6681,31 @@ 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()) - 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); + for (const SLAPrintObject* obj : print->objects()) { + unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); + for (const SLAPrintObject::Instance& instance : obj->instances()) { + 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 (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); - } - 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); + 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().its; !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(); } @@ -7228,5 +7095,21 @@ 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()) { + 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; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7b5a1084c1..198ecb1d81 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/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 804d489718..e0ee02a984 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -65,6 +65,8 @@ std::pair GLShadersManager::init() valid &= append_shader("toolpaths_cog", { prefix + "toolpaths_cog.vs", prefix + "toolpaths_cog.fs" }); // 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" }); // used to render options in gcode preview diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b901e7f10e..d5b2679275 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2981,6 +2981,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" + @@ -2995,6 +3003,7 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) caption); return false; } +*/ return true; } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index d71a128b0b..3e01de834f 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,22 +154,22 @@ 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 { +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() { @@ -533,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); @@ -570,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; @@ -580,7 +585,8 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu) }, m_parent); } - append_menu_item_layers_editing(menu); + if (menu_type == mtObjectFFF) + append_menu_item_layers_editing(menu); } wxMenuItem* MenuFactory::append_menu_item_layers_editing(wxMenu* menu) @@ -631,7 +637,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; @@ -1038,37 +1044,26 @@ 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::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); - 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_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); - - m_sla_object_menu.AppendSeparator(); } void MenuFactory::append_immutable_part_menu_items(wxMenu* menu) @@ -1130,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(); @@ -1140,7 +1135,7 @@ void MenuFactory::init(wxWindow* parent) void MenuFactory::update() { update_default_menu(); - update_object_menu(); + update_objects_menu(); } wxMenu* MenuFactory::default_menu() @@ -1277,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 ed27655be0..515311d0de 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(); @@ -43,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(); @@ -81,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(); @@ -91,7 +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_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); @@ -114,6 +110,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/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 954f61813b..5529c34b99 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2598,9 +2598,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()) @@ -3011,7 +3008,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), @@ -4281,17 +4279,35 @@ 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); + } + + 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(); + + auto choice = wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, selection); + const auto new_type = choice >= 0 ? types[choice] : ModelVolumeType::INVALID; - 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 (new_type == type || new_type == ModelVolumeType::INVALID) return; @@ -4376,8 +4392,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); @@ -4385,10 +4402,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); @@ -4430,6 +4463,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/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3e5822cfd3..4235bf9417 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1669,12 +1669,10 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) reset_cut_plane(); m_imgui->disabled_end(); - 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(); @@ -1804,7 +1802,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), diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index df22dde101..4d7685eeb9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -2134,10 +2134,10 @@ void GLGizmoEmboss::draw_model_type() else if (type != negative) ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str()); } - ImGui::SameLine(); // In simple mode are not modifiers - if (wxGetApp().get_mode() != ConfigOptionMode::comSimple) { + if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) { + ImGui::SameLine(); if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier)) new_type = modifier; else if (ImGui::IsItemHovered()) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 99994f7b6f..fc54622e84 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 @@ -10,15 +10,15 @@ #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/SLAPrint.hpp" #include "libslic3r/Model.hpp" - namespace Slic3r { namespace GUI { 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) { } @@ -53,12 +53,20 @@ 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_raycasters.empty()) - on_register_raycasters_for_picking(); + + const SLAPrintObject* po = m_c->selection_info()->print_object(); + 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(); + + if (m_hole_raycasters.empty()) + register_hole_raycasters_for_picking(); else - update_raycasters_for_picking_transform(); + update_hole_raycasters_for_picking_transform(); + + m_c->instances_hider()->set_hide_full_scene(true); } } @@ -80,39 +88,25 @@ void GLGizmoHollow::on_render() glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - if (selection.is_from_single_instance()) - render_points(selection); + render_volumes(); + render_points(selection); m_selection_rectangle.render(m_parent); m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); glsafe(::glDisable(GL_BLEND)); } 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(); } void GLGizmoHollow::render_points(const Selection& selection) @@ -124,11 +118,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()); + 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 @@ -145,26 +145,21 @@ void GLGizmoHollow::render_points(const Selection& selection) const bool point_selected = m_selected[i]; 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; // First decide about the color of the point. - 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 - render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + if (size_t(m_hover_id) == i) + render_color = ColorRGBA::CYAN(); + else + render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); m_cylinder.model.set_color(render_color); // 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. @@ -178,7 +173,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)); } } @@ -198,56 +193,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()->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, - 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; - } - } - - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - else - 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 @@ -261,9 +206,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]) @@ -295,8 +239,8 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos assert(m_selected.size() == mo->sla_drain_holes.size()); m_parent.set_as_dirty(); m_wait_for_up_event = true; - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + unregister_hole_raycasters_for_picking(); + register_hole_raycasters_for_picking(); } else return false; @@ -416,13 +360,14 @@ void GLGizmoHollow::delete_selected_points() } } - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + unregister_hole_raycasters_for_picking(); + register_hole_raycasters_for_picking(); select_point(NoPoints); } bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event) { + if (!is_input_enabled()) return true; if (mouse_event.Moving()) return false; if (use_grabbers(mouse_event)) return true; @@ -485,41 +430,49 @@ bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) +void GLGizmoHollow::register_hole_raycasters_for_picking() { - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_hollowing( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} + assert(m_hole_raycasters.empty()); -void GLGizmoHollow::set_sla_auxiliary_volumes_picking_state(bool state) -{ - 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); - } + 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::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(); + 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]; @@ -527,9 +480,9 @@ void GLGizmoHollow::update_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_raycasters[i]->set_transform(matrix); + m_hole_raycasters[i]->set_transform(matrix); } } } @@ -626,9 +579,11 @@ 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(!is_input_enabled()); + if (m_imgui->button(m_desc["preview"])) - hollow_mesh(); - + reslice_until_step(slaposDrillHoles); + bool config_changed = false; ImGui::Separator(); @@ -643,7 +598,10 @@ RENDER_AGAIN: } } - m_imgui->disabled_begin(! m_enable_hollowing); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(!is_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); @@ -686,7 +644,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); @@ -709,12 +667,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(!is_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. @@ -725,9 +686,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); @@ -763,24 +728,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(!is_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(!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: - // m_imgui->text(""); ImGui::Separator(); + 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")); @@ -799,16 +764,7 @@ 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); - if (show_sups) - // ensure supports and pad are disabled from picking even when they are visible - set_sla_auxiliary_volumes_picking_state(false); - force_refresh = true; - } - + m_imgui->disabled_end(); m_imgui->end(); @@ -841,7 +797,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. @@ -863,26 +819,17 @@ 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::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - 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; } @@ -997,6 +944,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 ea88c4291a..abfb2503fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_GLGizmoHollow_hpp_ #define slic3r_GLGizmoHollow_hpp_ -#include "GLGizmoBase.hpp" +#include "GLGizmoSlaBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" #include @@ -20,12 +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; @@ -51,14 +47,14 @@ protected: private: void render_points(const Selection& selection); - void hollow_mesh(bool postpone_error_messages = false); - 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 update_hole_raycasters_for_picking_transform(); ObjectID m_old_mo_id = -1; PickingModel m_cylinder; - std::vector> m_raycasters; + std::vector> m_hole_raycasters; float m_new_hole_radius = 2.f; // Size of a new hole. float m_new_hole_height = 6.f; @@ -106,7 +102,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 0000000000..444e3bf466 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp @@ -0,0 +1,180 @@ +#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(); +static const int VOLUME_RAYCASTERS_BASE_ID = (int)SceneRaycaster::EIdBase::Gizmo; + +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; + 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()); + 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); + } + } + } + + register_volume_raycasters_for_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(); + +} + +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(); +} + +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; + + auto *inst = m_c->selection_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, + trafo/*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 0000000000..9b5e9f4695 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp @@ -0,0 +1,57 @@ +#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(); + + void register_volume_raycasters_for_picking(); + void unregister_volume_raycasters_for_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); + + const GLVolumeCollection &volumes() const { return m_volumes; } + +private: + GLVolumeCollection m_volumes; + bool m_input_enabled{ false }; + int m_min_sla_print_object_step{ -1 }; + std::vector> m_volume_raycasters; +}; + +} // 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 c22cdf606a..e7ea945e36 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" @@ -29,7 +25,7 @@ namespace Slic3r { namespace GUI { 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() @@ -63,19 +59,28 @@ 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(); + 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(); + if (mo->sla_points_status == sla::PointsStatus::Generating) get_data_from_backend(); - 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(); } + +// m_parent.toggle_model_objects_visibility(false); } @@ -92,8 +97,6 @@ void GLGizmoSlaSupports::on_render() m_cone.model.init_from(its); m_cone.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } - 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(); @@ -109,35 +112,25 @@ void GLGizmoSlaSupports::on_render() glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - if (selection.is_from_single_instance()) - render_points(selection); + render_volumes(); + render_points(selection); m_selection_rectangle.render(m_parent); m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); glsafe(::glDisable(GL_BLEND)); } 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(); } void GLGizmoSlaSupports::render_points(const Selection& selection) @@ -145,10 +138,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) 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; GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); @@ -158,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.translation()(2) += shift_z; + const Geometry::Transformation transformation{trafo}; + #if ENABLE_WORLD_COORDINATE const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); #else @@ -175,9 +172,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false; 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; @@ -207,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. @@ -220,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 }); @@ -231,49 +228,17 @@ 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)); } - - // Now render the drain holes: - if (has_holes) { - render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; - m_cylinder.set_color(render_color); - 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; - - const Transform3d hole_matrix = Geometry::translation_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; - - 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); - 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); - m_cylinder.render(); - - if (vol->is_left_handed()) - glsafe(::glFrontFace(GL_CCW)); - } - } } - - bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const { if (m_c->object_clipper()->get_position() == 0.) @@ -289,59 +254,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; - - 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, - 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; - } - } - - 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 @@ -386,8 +298,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + unregister_point_raycasters_for_picking(); + register_point_raycasters_for_picking(); } else return false; @@ -426,7 +338,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous 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( - trafo, wxGetApp().plater()->get_camera(), points_inside, + 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 @@ -543,9 +455,8 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) } } - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); - + unregister_point_raycasters_for_picking(); + register_point_raycasters_for_picking(); select_point(NoPoints); } @@ -644,8 +555,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) @@ -676,6 +586,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); @@ -736,6 +647,8 @@ RENDER_AGAIN: } } else { // not in editing mode: + m_imgui->disabled_begin(!is_input_enabled()); + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("minimal_distance")); ImGui::SameLine(settings_sliders_left); @@ -785,7 +698,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(!is_input_enabled() || m_normal_cache.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); @@ -798,6 +713,7 @@ RENDER_AGAIN: // Following is rendered in both editing and non-editing mode: + m_imgui->disabled_begin(!is_input_enabled()); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); @@ -817,7 +733,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; @@ -825,6 +740,8 @@ RENDER_AGAIN: }); } + m_imgui->disabled_end(); + m_imgui->end(); if (remove_selected || remove_all) { @@ -857,7 +774,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. @@ -879,19 +796,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::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - - void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) { wxGetApp().CallAfter([on_yes, on_no]() { @@ -931,6 +835,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; } @@ -1076,7 +983,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); } } @@ -1108,15 +1015,9 @@ bool GLGizmoSlaSupports::has_backend_supports() const return false; } -void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const +bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event) { - 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 (!is_input_enabled()) return true; if (mouse_event.Moving()) return false; if (!mouse_event.ShiftDown() && !mouse_event.AltDown() && use_grabbers(mouse_event)) return true; @@ -1183,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; isla_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; } } @@ -1224,9 +1125,7 @@ void GLGizmoSlaSupports::switch_to_editing_mode() for (const sla::SupportPoint& sp : m_normal_cache) m_editing_cache.emplace_back(sp); select_point(NoPoints); - on_register_raycasters_for_picking(); - - m_c->instances_hider()->show_supports(false); + register_point_raycasters_for_picking(); m_parent.set_as_dirty(); } @@ -1236,9 +1135,8 @@ 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(); - on_unregister_raycasters_for_picking(); + unregister_point_raycasters_for_picking(); } wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); } @@ -1257,49 +1155,63 @@ bool GLGizmoSlaSupports::unsaved_changes() const return false; } -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::update_point_raycasters_for_picking_transform() +{ + if (m_editing_cache.empty()) + return; - 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); + assert(!m_point_raycasters.empty()); - 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); + const GLVolume* vol = m_parent.get_selection().get_first_volume(); + Geometry::Transformation transformation(vol->world_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_raycasters[i].first->set_transform(sphere_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; + + 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 = 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 = transformation.get_matrix() * support_matrix * Geometry::scale_transform(radius); + m_point_raycasters[i].first->set_transform(sphere_matrix); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index e8238c8884..578858b5bd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_GLGizmoSlaSupports_hpp_ #define slic3r_GLGizmoSlaSupports_hpp_ -#include "GLGizmoBase.hpp" +#include "GLGizmoSlaBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" #include "libslic3r/SLA/SupportPoint.hpp" @@ -19,13 +19,10 @@ 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); - - const float RenderPointScale = 1.f; + static constexpr float RenderPointScale = 1.f; class CacheEntry { public: @@ -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"); } @@ -86,8 +82,9 @@ private: void render_points(const Selection& selection); bool unsaved_changes() const; - 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 update_point_raycasters_for_picking_transform(); bool m_lock_unique_islands = false; bool m_editing_mode = false; // Is editing mode active? @@ -102,8 +99,7 @@ private: PickingModel m_sphere; PickingModel m_cone; - std::vector, std::shared_ptr>> m_raycasters; - GLModel m_cylinder; + std::vector, std::shared_ptr>> m_point_raycasters; // 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. @@ -138,8 +134,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; } @@ -151,7 +146,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/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 0e1d5ae0c7..a8dc16c558 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,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()); @@ -117,15 +110,16 @@ 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; - 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()]; + 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 = m_print_object ? m_print_object->get_current_elevation() : selection.get_first_volume()->get_sla_shift_z(); } } @@ -135,6 +129,13 @@ void SelectionInfo::on_release() m_model_volume = nullptr; } +ModelInstance *SelectionInfo::model_instance() const +{ + int inst_idx = get_active_instance(); + return inst_idx < int(m_model_object->instances.size()) ? + m_model_object->instances[get_active_instance()] : nullptr; +} + int SelectionInfo::get_active_instance() const { return get_pool()->get_canvas()->get_selection().get_instance_idx(); @@ -152,8 +153,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)); @@ -169,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; } @@ -186,9 +189,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(); } } @@ -236,81 +240,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->has_mesh(slaposDrillHoles)) { - 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(); - 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() { wxBusyCursor wait; @@ -327,20 +256,33 @@ 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()); - } - 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 + std::shared_ptr preview_mesh_ptr; + const SLAPrintObject* po = get_pool()->selection_info()->print_object(); + if (po) + 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); + 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) m_raycasters.emplace_back(new MeshRaycaster(std::make_shared(*mesh))); @@ -362,8 +304,9 @@ std::vector Raycaster::raycasters() const return mrcs; } +} // namespace GUI - +namespace GUI { void ObjectClipper::on_update() @@ -374,24 +317,42 @@ 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 (meshes.empty()) - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); - - if (meshes != m_old_meshes) { - m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_mesh(*mesh); + 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(); + 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()}; + } } - m_old_meshes = meshes; + } - if (has_hollowed) - m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); + if (!mc && meshes.empty()) { + for (const ModelVolume* mv : mo->volumes) { + meshes.emplace_back(&mv->mesh()); + trafos.emplace_back(mv->get_transformation()); + } + } + + 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]->its); + } + m_old_meshes = std::move(meshes); + + if (mc) { + m_clippers.emplace_back(std::move(mc), mc_tr); + } m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); @@ -413,37 +374,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(); + const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->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; - 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)); - clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); - clipper->render_contour({ 1.f, 1.f, 1.f, 1.f}); - - ++clipper_id; + 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())); + 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 }); + clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f }); } } 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) @@ -481,13 +432,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 @@ -532,7 +483,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; } } @@ -553,7 +504,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 c1b605726c..21e1bb73cf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -5,11 +5,12 @@ #include #include "slic3r/GUI/MeshUtils.hpp" -#include "libslic3r/SLA/Hollowing.hpp" namespace Slic3r { class ModelObject; +class ModelInstance; +class SLAPrintObject; class ModelVolume; namespace GUI { @@ -64,7 +65,6 @@ enum class CommonGizmosDataID { None = 0, SelectionInfo = 1 << 0, InstancesHider = 1 << 1, - HollowedMesh = 1 << 2, Raycaster = 1 << 3, ObjectClipper = 1 << 4, SupportsClipper = 1 << 5, @@ -86,7 +86,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,8 +158,10 @@ 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; } + ModelInstance *model_instance() const; int get_active_instance() const; float get_sla_shift() const { return m_z_shift; } @@ -169,6 +171,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; @@ -185,8 +188,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: @@ -194,42 +196,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: @@ -249,6 +222,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; }; @@ -283,7 +258,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/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 0a229caf91..001074a2c8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -657,7 +657,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); } void GLGizmosManager::render_background(float left, float top, float right, float bottom, float border_w, float border_h) const diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 93baac4489..8a4bee03ed 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -943,7 +943,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/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 428a83a64d..bb93afc8d5 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -5,12 +5,14 @@ #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 "slic3r/GUI/CameraUtils.hpp" + #include #include @@ -50,22 +52,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) @@ -173,13 +191,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 7c07b97c6a..b10fa5ff8f 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(); @@ -138,13 +157,17 @@ private: class MeshRaycaster { public: 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)) + {} + // DEPRICATED - use CameraUtils::ray_from_screen_pos static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4abc75ebdf..6e63c56344 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -555,11 +555,17 @@ 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)); + } } tab->load_config(new_conf); @@ -570,8 +576,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); @@ -1540,7 +1544,6 @@ void Sidebar::update_mode() p->object_list->unselect_objects(); p->object_list->update_selections(); -// p->object_list->update_object_menu(); Layout(); } @@ -2676,17 +2679,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()); @@ -4529,9 +4521,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() || @@ -4543,12 +4535,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) { @@ -5017,7 +5009,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 @@ -6559,7 +6551,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()) { @@ -6581,93 +6573,65 @@ void Plater::export_stl_obj(bool extended, bool selection_only) return mesh; }; - 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); - } + auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) { + TriangleMesh mesh; - if (!selection.is_single_full_object() || model_object->instances.size() == 1) - mesh.translate(-model_object->origin_translation.cast()); - } + const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); + + if (auto m = object->get_mesh_to_print(); !m || m->empty()) + mesh = mesh_to_export_fff(mo, instance_id); else { - for (const ModelObject* o : p->model.objects) { - mesh.merge(mesh_to_export(*o, -1)); - } - } - } - 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(); - 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.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(), - [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); - assert(it != model_object->instances.end()); + 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 != object->model_object()->instances.end()); - if (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 - model_object->instances.begin(); + 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(); + ? Transform3d::Identity() + : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); 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(); + 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); inst_mesh.merge(inst_object_mesh); - // ensure that the instance lays on the bed + // 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 + // merge instance with global mesh mesh.merge(inst_mesh); if (one_inst_only) @@ -6675,6 +6639,36 @@ void Plater::export_stl_obj(bool extended, bool selection_only) } } } + + 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]; + 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()); + } + else { + for (const ModelObject* o : p->model.objects) { + mesh.merge(mesh_to_export(*o, -1)); + } } if (path.EndsWith(".stl")) @@ -6849,16 +6843,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 e254428b56..d6f2d9b79e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -271,8 +271,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); diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp index 619e26042b..1e8bb0c82b 100644 --- a/src/slic3r/GUI/SceneRaycaster.cpp +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -86,7 +86,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) { @@ -98,14 +98,14 @@ 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; 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) { @@ -140,7 +140,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; } @@ -212,6 +212,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 3119e6d1e7..df44b1701c 100644 --- a/src/slic3r/GUI/SceneRaycaster.hpp +++ b/src/slic3r/GUI/SceneRaycaster.hpp @@ -87,10 +87,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 fda11421fd..d74a17cc60 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; } @@ -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); @@ -571,13 +582,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; } @@ -2072,9 +2083,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; @@ -2084,8 +2102,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]; + 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); } @@ -3130,7 +3153,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 a90287689a..6e43afc3cf 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")) @@ -4921,9 +4921,11 @@ void TabSLAPrint::build() optgroup = page->new_optgroup(L("Supports")); optgroup->append_single_option_line("supports_enable"); optgroup->append_single_option_line("support_tree_type"); - + optgroup->append_single_option_line("support_enforcers_only"); + build_sla_support_params({{"", "Default"}, {"branching", "Branching"}}, page); + optgroup = page->new_optgroup(L("Automatic generation")); optgroup->append_single_option_line("support_points_density_relative"); optgroup->append_single_option_line("support_points_minimal_distance"); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index b601cef112..f294abccb8 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)}); }