mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 19:26:04 +08:00
Merge branch 'et_tm_sla_volumes_6-SPE-1285'
This commit is contained in:
commit
e3af59b3ee
17
resources/shaders/110/gouraud_light_clip.fs
Normal file
17
resources/shaders/110/gouraud_light_clip.fs
Normal file
@ -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);
|
||||
}
|
54
resources/shaders/110/gouraud_light_clip.vs
Normal file
54
resources/shaders/110/gouraud_light_clip.vs
Normal file
@ -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);
|
||||
}
|
19
resources/shaders/140/gouraud_light_clip.fs
Normal file
19
resources/shaders/140/gouraud_light_clip.fs
Normal file
@ -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);
|
||||
}
|
54
resources/shaders/140/gouraud_light_clip.vs
Normal file
54
resources/shaders/140/gouraud_light_clip.vs
Normal file
@ -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);
|
||||
}
|
19
resources/shaders/ES/gouraud_light_clip.fs
Normal file
19
resources/shaders/ES/gouraud_light_clip.fs
Normal file
@ -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);
|
||||
}
|
54
resources/shaders/ES/gouraud_light_clip.vs
Normal file
54
resources/shaders/ES/gouraud_light_clip.vs
Normal file
@ -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);
|
||||
}
|
@ -68,9 +68,6 @@ public:
|
||||
|
||||
template<class M> 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;
|
||||
|
@ -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<AABBImpl> m_aabb;
|
||||
VertexFaceIndex m_vfidx; // vertex-face index
|
||||
std::vector<Vec3i> m_fnidx; // face-neighbor index
|
||||
@ -43,7 +42,7 @@ class AABBMesh {
|
||||
template<class M> 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<Vec3f>& vertices() const;
|
||||
const std::vector<Vec3i>& 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.
|
||||
|
130
src/libslic3r/AnyPtr.hpp
Normal file
130
src/libslic3r/AnyPtr.hpp
Normal file
@ -0,0 +1,130 @@
|
||||
#ifndef ANYPTR_HPP
|
||||
#define ANYPTR_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
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 T>
|
||||
class AnyPtr {
|
||||
enum { RawPtr, UPtr, ShPtr, WkPtr };
|
||||
|
||||
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
|
||||
|
||||
template<class Self> static T *get_ptr(Self &&s)
|
||||
{
|
||||
switch (s.ptr.which()) {
|
||||
case RawPtr: return boost::get<T *>(s.ptr);
|
||||
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
|
||||
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
|
||||
return shptr.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(TT *p = nullptr) : ptr{p}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(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<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::weak_ptr<TT> 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<T *>(ptr));
|
||||
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
|
||||
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(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<T> get_shared_cpy() const
|
||||
{
|
||||
std::shared_ptr<T> ret;
|
||||
|
||||
switch (ptr.which()) {
|
||||
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
|
||||
case WkPtr: ret = boost::get<std::weak_ptr<T>>(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<T>{std::move(boost::get<std::unique_ptr<T>>(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
|
@ -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
|
||||
|
86
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
86
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
@ -0,0 +1,86 @@
|
||||
#ifndef CSGMESH_HPP
|
||||
#define CSGMESH_HPP
|
||||
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <admesh/stl.h>
|
||||
|
||||
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<class CSGPartT> CSGType get_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.operation;
|
||||
}
|
||||
|
||||
// Get the stack operation required by the CSG part.
|
||||
template<class CSGPartT> CSGStackOp get_stack_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.stack_operation;
|
||||
}
|
||||
|
||||
// Get the mesh for the part. Can be overriden for any type
|
||||
template<class CSGPartT>
|
||||
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<class CSGPartT>
|
||||
Transform3f get_transform(const CSGPartT &part)
|
||||
{
|
||||
return part.trafo;
|
||||
}
|
||||
|
||||
// Default implementation
|
||||
struct CSGPart {
|
||||
AnyPtr<const indexed_triangle_set> its_ptr;
|
||||
Transform3f trafo;
|
||||
CSGType operation;
|
||||
CSGStackOp stack_operation;
|
||||
|
||||
CSGPart(AnyPtr<const indexed_triangle_set> 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
|
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
@ -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<class It, class OutIt>
|
||||
void copy_csgrange_shallow(const Range<It> &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<decltype(part), const CSGPart&>) {
|
||||
if (auto shptr = part.its_ptr.get_shared_cpy()) {
|
||||
cpy.its_ptr = shptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cpy.its_ptr)
|
||||
cpy.its_ptr = AnyPtr<const indexed_triangle_set>{get_mesh(part)};
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the csg range, allocating new meshes
|
||||
template<class It, class OutIt>
|
||||
void copy_csgrange_deep(const Range<It> &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<const indexed_triangle_set>(*meshptr);
|
||||
}
|
||||
|
||||
cpy.stack_operation = get_stack_operation(part);
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
template<class ItA, class ItB>
|
||||
bool is_same(const Range<ItA> &A, const Range<ItB> &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
|
88
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
88
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
@ -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<class OutIt>
|
||||
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<indexed_triangle_set>(std::move(its)),
|
||||
CSGType::Union,
|
||||
(trafo * vol->get_matrix()).cast<float>()};
|
||||
|
||||
*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<float>()};
|
||||
|
||||
*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<const indexed_triangle_set>(
|
||||
dhole.to_mesh()),
|
||||
CSGType::Difference};
|
||||
|
||||
*out = std::move(part);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // MODELTOCSGMESH_HPP
|
205
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
205
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
@ -0,0 +1,205 @@
|
||||
#ifndef PERFORMCSGMESHBOOLEANS_HPP
|
||||
#define PERFORMCSGMESHBOOLEANS_HPP
|
||||
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
|
||||
#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<class CSGPartT>
|
||||
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<class Ex, class It>
|
||||
std::vector<CGALMeshPtr> get_cgalptrs(Ex policy, const Range<It> &csgrange)
|
||||
{
|
||||
std::vector<CGALMeshPtr> 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<class It>
|
||||
void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMeshPtr &cgalm,
|
||||
const Range<It> &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<Frame>{}};
|
||||
|
||||
opstack.push(Frame{});
|
||||
|
||||
std::vector<CGALMeshPtr> 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<class It, class Visitor>
|
||||
It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
|
||||
{
|
||||
using namespace detail_cgal;
|
||||
|
||||
std::vector<CGALMeshPtr> 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<class It>
|
||||
It check_csgmesh_booleans(const Range<It> &csgrange)
|
||||
{
|
||||
return check_csgmesh_booleans(csgrange, [](auto &) {});
|
||||
}
|
||||
|
||||
template<class It>
|
||||
MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range<It> &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
|
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
@ -0,0 +1,131 @@
|
||||
#ifndef SLICECSGMESH_HPP
|
||||
#define SLICECSGMESH_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include <stack>
|
||||
|
||||
#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<ExPolygons> &target,
|
||||
std::vector<ExPolygons> &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<float> &slicegrid,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
std::vector<size_t> &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<class ItCSG>
|
||||
std::vector<ExPolygons> slice_csgmesh_ex(
|
||||
const Range<ItCSG> &csgrange,
|
||||
const std::vector<float> &slicegrid,
|
||||
const MeshSlicingParamsEx ¶ms,
|
||||
const std::function<void()> &throw_on_cancel = [] {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
struct Frame { CSGType op; std::vector<ExPolygons> slices; };
|
||||
|
||||
std::stack opstack{std::vector<Frame>{}};
|
||||
|
||||
MeshSlicingParamsEx params_cpy = params;
|
||||
auto trafo = params.trafo;
|
||||
auto nonempty_indices = reserve_vector<size_t>(slicegrid.size());
|
||||
|
||||
opstack.push({CSGType::Union, std::vector<ExPolygons>(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<ExPolygons>(slicegrid.size())});
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame *top = &opstack.top();
|
||||
|
||||
if (its) {
|
||||
params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast<double>();
|
||||
std::vector<ExPolygons> 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<ExPolygons> popslices = std::move(top->slices);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
std::vector<ExPolygons> &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<ExPolygons> 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
|
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
@ -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
|
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
@ -0,0 +1,116 @@
|
||||
#ifndef VOXELIZECSGMESH_HPP
|
||||
#define VOXELIZECSGMESH_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
|
||||
#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<class CSGPartT>
|
||||
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<class It>
|
||||
VoxelGridPtr voxelize_csgmesh(const Range<It> &csgrange,
|
||||
const VoxelizeParams ¶ms = {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
VoxelGridPtr ret;
|
||||
|
||||
std::vector<VoxelGridPtr> 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<Frame>{}};
|
||||
|
||||
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
|
@ -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>(mergefn));
|
||||
|
@ -137,7 +137,8 @@ inline Vec3f to_vec3f(const _EpecMesh::Point& v)
|
||||
return { float(iv.x()), float(iv.y()), float(iv.z()) };
|
||||
}
|
||||
|
||||
template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
template<class _Mesh>
|
||||
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<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
its.indices.emplace_back(facet);
|
||||
}
|
||||
|
||||
return TriangleMesh(std::move(its));
|
||||
return its;
|
||||
}
|
||||
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
||||
@ -181,7 +182,12 @@ triangle_mesh_to_cgal(const std::vector<stl_vertex> &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<class Op> 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<class Op> 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
|
||||
|
@ -26,27 +26,35 @@ namespace cgal {
|
||||
|
||||
struct CGALMesh;
|
||||
struct CGALMeshDeleter { void operator()(CGALMesh *ptr); };
|
||||
using CGALMeshPtr = std::unique_ptr<CGALMesh, CGALMeshDeleter>;
|
||||
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
||||
triangle_mesh_to_cgal(const std::vector<stl_vertex> &V,
|
||||
const std::vector<stl_triangle_vertex_indices> &F);
|
||||
CGALMeshPtr clone(const CGALMesh &m);
|
||||
|
||||
inline std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const indexed_triangle_set &M)
|
||||
CGALMeshPtr triangle_mesh_to_cgal(
|
||||
const std::vector<stl_vertex> &V,
|
||||
const std::vector<stl_triangle_vertex_indices> &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<CGALMesh, CGALMeshDeleter> 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);
|
||||
|
@ -108,6 +108,21 @@ template<class IndexT> 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<class Fn>
|
||||
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<class Its, class OutputIt>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -6,6 +6,7 @@
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4146)
|
||||
#endif // _MSC_VER
|
||||
#include <openvdb/openvdb.h>
|
||||
#include <openvdb/tools/MeshToVolume.h>
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
@ -16,14 +17,44 @@
|
||||
#include <openvdb/tools/LevelSetRebuild.h>
|
||||
#include <openvdb/tools/FastSweeping.h>
|
||||
|
||||
//#include "MTUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct VoxelGrid
|
||||
{
|
||||
openvdb::FloatGrid grid;
|
||||
|
||||
mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor;
|
||||
|
||||
template<class...Args>
|
||||
VoxelGrid(Args &&...args): grid{std::forward<Args>(args)...} {}
|
||||
};
|
||||
|
||||
void VoxelGridDeleter::operator()(VoxelGrid *ptr) { delete ptr; }
|
||||
|
||||
// Similarly to std::make_unique()
|
||||
template<class...Args>
|
||||
VoxelGridPtr make_voxelgrid(Args &&...args)
|
||||
{
|
||||
VoxelGrid *ptr = nullptr;
|
||||
try {
|
||||
ptr = new VoxelGrid(std::forward<Args>(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<double>(); }
|
||||
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<double>() * voxel_scale;
|
||||
Slic3r::Vec3d p = trafo * its.vertices[vidx].cast<double>();
|
||||
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<bool(int)> 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<indexed_triangle_set> 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<double>();
|
||||
trafo.prescale(params.voxel_scale());
|
||||
|
||||
Interrupter interrupter{params.statusfn()};
|
||||
|
||||
openvdb::FloatGrid::Ptr grid;
|
||||
for (auto &m : meshparts) {
|
||||
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
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<openvdb::FloatGrid>(
|
||||
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<openvdb::Vec3I> triangles;
|
||||
std::vector<openvdb::Vec4I> quads;
|
||||
|
||||
auto &grid = vgrid.grid;
|
||||
|
||||
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
|
||||
adaptivity, relaxDisorientedTriangles);
|
||||
|
||||
float scale = 1.;
|
||||
try {
|
||||
scale = grid.template metaValue<float>("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<double>();
|
||||
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<float>("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
|
||||
|
@ -3,21 +3,47 @@
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#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 <openvdb/openvdb.h>
|
||||
#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<double>(); }
|
||||
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<VoxelGrid, VoxelGridDeleter>;
|
||||
|
||||
// This is like std::make_unique for a voxelgrid
|
||||
template<class... Args> 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<bool(int)> 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<bool(int)> 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<bool(int)>& 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
|
||||
|
||||
|
100
src/libslic3r/OpenVDBUtilsLegacy.hpp
Normal file
100
src/libslic3r/OpenVDBUtilsLegacy.hpp
Normal file
@ -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 <openvdb/openvdb.h>
|
||||
#include <openvdb/tools/MeshToVolume.h>
|
||||
#include <openvdb/tools/FastSweeping.h>
|
||||
#include <openvdb/tools/Composite.h>
|
||||
#include <openvdb/tools/LevelSetRebuild.h>
|
||||
#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<double>() * 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<indexed_triangle_set> 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<openvdb::FloatGrid>(
|
||||
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<openvdb::FloatGrid>(
|
||||
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
|
@ -516,6 +516,7 @@ static std::vector<std::string> 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",
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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 <cstdint>
|
||||
#include <functional>
|
||||
@ -30,3 +32,5 @@ void its_quadric_edge_collapse(
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_quadric_edge_collapse_hpp_
|
||||
|
||||
#endif // PRUSASLICER_QUADRIC_EDGE_COLLAPSE_HPP
|
||||
|
@ -1,20 +1,25 @@
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <numeric>
|
||||
#include <unordered_set>
|
||||
#include <random>
|
||||
|
||||
#include <libslic3r/OpenVDBUtils.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/TriangleMeshSlicer.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/AABBMesh.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/QuadricEdgeCollapse.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
||||
#include <libslic3r/Execution/ExecutionSeq.hpp>
|
||||
#include <libslic3r/Model.hpp>
|
||||
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <openvdb/tools/FastSweeping.h>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/I18N.hpp>
|
||||
|
||||
@ -27,19 +32,17 @@ namespace sla {
|
||||
|
||||
struct Interior {
|
||||
indexed_triangle_set mesh;
|
||||
openvdb::FloatGrid::Ptr gridptr;
|
||||
mutable std::optional<openvdb::FloatGrid::ConstAccessor> 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 <MIN_SAMPLES_IN_WALL, MAX_OVERSAMPL>. 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<float>::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<ExPolygons> & 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<double>();
|
||||
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<class T>
|
||||
FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior)
|
||||
{
|
||||
return get_distance(Vec3f(p.template cast<float>()), 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<Fn>(visitor));
|
||||
}
|
||||
|
||||
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
|
||||
void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior,
|
||||
const std::vector<bool> &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<bool> 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<float>(), 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<Vec3f, 3> 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<bool> &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<std::string> facehash;
|
||||
|
||||
// Returns the string in reverse, but that is ok for hashing
|
||||
static std::array<char, MAX_NUM_CHARS + 1> to_chars(int64_t val)
|
||||
{
|
||||
std::array<char, MAX_NUM_CHARS + 1> ret;
|
||||
|
||||
static const constexpr char * Conv = "0123456789abcdef";
|
||||
|
||||
auto ptr = ret.begin();
|
||||
auto uval = static_cast<uint64_t>(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<Vec3f> &vertices)
|
||||
{
|
||||
// Scale to integer to avoid floating points
|
||||
std::array<Vec<3, int64_t>, 3> pts = {
|
||||
scaled<int64_t>(vertices[face(0)]),
|
||||
scaled<int64_t>(vertices[face(1)]),
|
||||
scaled<int64_t>(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<bool> &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<bool> create_exclude_mask(const indexed_triangle_set &its,
|
||||
const Interior &interior,
|
||||
const std::vector<DrainHole> &holes)
|
||||
{
|
||||
FaceHash interior_hash{sla::get_mesh(interior)};
|
||||
|
||||
std::vector<bool> 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<double>() / 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<double>();
|
||||
Vec3d dhend = dhpos + dh.normal.cast<double>() * 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<double>();
|
||||
Vec3d nrm = hl.normal.cast<double>();
|
||||
|
||||
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<float>();
|
||||
hl.normal = nrm.cast<float>();
|
||||
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 <MIN_SAMPLES_IN_WALL, MAX_OVERSAMPL>. 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<int> 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<int, 3> 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<void(size_t)> on_hole_fail)
|
||||
{
|
||||
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
hollowed_mesh.vertices,
|
||||
hollowed_mesh.indices
|
||||
);
|
||||
|
||||
std::uniform_real_distribution<float> 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<float, 3> ebb{bb.min.cast<float>(),
|
||||
bb.max.cast<float>()};
|
||||
|
||||
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<int>(HollowMeshResult::Ok);
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) {
|
||||
ret |= static_cast<int>(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<int>(HollowMeshResult::FaultyMesh);
|
||||
}
|
||||
|
||||
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal)
|
||||
&& !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) {
|
||||
ret |= static_cast<int>(HollowMeshResult::FaultyHoles);
|
||||
}
|
||||
|
||||
// Don't even bother
|
||||
if (ret & static_cast<int>(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<bool> 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<int>(HollowMeshResult::DrillingFailed);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
@ -3,10 +3,14 @@
|
||||
|
||||
#include <memory>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/OpenVDBUtils.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
#include <libslic3r/CSGMesh/VoxelizeCSGMesh.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelObject;
|
||||
|
||||
namespace sla {
|
||||
|
||||
struct HollowingConfig
|
||||
@ -28,6 +32,9 @@ using InteriorPtr = std::unique_ptr<Interior, InteriorDeleter>;
|
||||
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<std::pair<float, Vec3d>, 2>& out) const;
|
||||
|
||||
|
||||
indexed_triangle_set to_mesh() const;
|
||||
|
||||
|
||||
template<class Archive> inline void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, normal, radius, height, failed);
|
||||
@ -70,26 +77,116 @@ using DrainHoles = std::vector<DrainHole>;
|
||||
|
||||
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<class Cont> 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<class It>
|
||||
InteriorPtr generate_interior(const Range<It> &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<void(size_t)> on_hole_fail = [](size_t){});
|
||||
|
||||
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
|
||||
const std::vector<bool> &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<bool> &exclude_mask = {});
|
||||
|
||||
template<class T>
|
||||
FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior)
|
||||
{
|
||||
return get_distance(Vec3f(p.template cast<float>()), interior);
|
||||
}
|
||||
sla::DrainHoles transformed_drainhole_points(const ModelObject &mo,
|
||||
const Transform3d &trafo);
|
||||
|
||||
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
const std::vector<float> &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<bool> create_exclude_mask(
|
||||
const indexed_triangle_set &its,
|
||||
const sla::Interior &interior,
|
||||
const std::vector<sla::DrainHole> &holes);
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -3,7 +3,11 @@
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
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<SupportPoint>;
|
||||
|
||||
SupportPoints transformed_support_points(const ModelObject &mo,
|
||||
const Transform3d &trafo);
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SUPPORTPOINT_HPP
|
||||
|
@ -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<float>();
|
||||
for (sla::SupportPoint& suppt : spts) {
|
||||
suppt.pos = tr * suppt.pos;
|
||||
}
|
||||
|
||||
return spts;
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 <unordered_set>
|
||||
@ -17,7 +13,7 @@
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
// #define SLAPRINT_DO_BENCHMARK
|
||||
#define SLAPRINT_DO_BENCHMARK
|
||||
|
||||
#ifdef SLAPRINT_DO_BENCHMARK
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
@ -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<SLAPrintObjectStep> 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::vector<t_conf
|
||||
steps.emplace_back(slaposObjectSlice);
|
||||
} else if (
|
||||
opt_key == "support_points_density_relative"
|
||||
|| opt_key == "support_enforcers_only"
|
||||
|| opt_key == "support_points_minimal_distance") {
|
||||
steps.emplace_back(slaposSupportPoints);
|
||||
} else if (
|
||||
@ -937,8 +933,10 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
|
||||
{
|
||||
bool invalidated = Inherited::invalidate_step(step);
|
||||
// propagate to dependent steps
|
||||
if (step == slaposHollowing) {
|
||||
if (step == slaposAssembly) {
|
||||
invalidated |= this->invalidate_all_steps();
|
||||
} else if (step == slaposHollowing) {
|
||||
invalidated |= invalidated |= this->invalidate_steps({ slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
|
||||
} else if (step == slaposDrillHoles) {
|
||||
invalidated |= this->invalidate_steps({ slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
@ -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<const indexed_triangle_set> &
|
||||
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<csg::CSGPart> SLAPrintObject::get_parts_to_slice() const
|
||||
{
|
||||
return get_parts_to_slice(slaposCount);
|
||||
}
|
||||
|
||||
return m_transformed_rmesh.get();
|
||||
std::vector<csg::CSGPart>
|
||||
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<csg::CSGPart> 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<float>();
|
||||
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<float>();
|
||||
const Vec3f sc = trans.get_scaling_factor().cast<float>();
|
||||
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<const csg::CSGPart&>(part));
|
||||
}
|
||||
|
||||
return part.cgalcache? clone(*part.cgalcache) : nullptr;
|
||||
}
|
||||
|
||||
} // namespace csg
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -3,16 +3,18 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#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 <boost/functional/hash.hpp>
|
||||
|
||||
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<CSGPartForStep>;
|
||||
|
||||
public:
|
||||
|
||||
@ -72,31 +112,20 @@ public:
|
||||
};
|
||||
const std::vector<Instance>& 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<const indexed_triangle_set>& 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<csg::CSGPart> get_parts_to_slice() const;
|
||||
|
||||
// This will return the transformed mesh which is cached
|
||||
const TriangleMesh& transformed_mesh() const;
|
||||
std::vector<csg::CSGPart> 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<class InstVec> inline void set_instances(InstVec&& instances) { m_instances = std::forward<InstVec>(instances); }
|
||||
@ -306,9 +336,6 @@ private:
|
||||
|
||||
std::vector<float> m_model_height_levels;
|
||||
|
||||
// Caching the transformed (m_trafo) raw mesh of the object
|
||||
mutable CachedObject<TriangleMesh> 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<SupportData> m_supportdata;
|
||||
|
||||
|
||||
std::unique_ptr<SupportData> 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<std::shared_ptr<const indexed_triangle_set>, 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<HollowingData> 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; });
|
||||
|
@ -14,8 +14,17 @@
|
||||
|
||||
#include <libslic3r/ElephantFootCompensation.hpp>
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/MeshSplitImpl.hpp>
|
||||
#include <libslic3r/SlicesToTriangleMesh.hpp>
|
||||
#include <libslic3r/CSGMesh/ModelToCSGMesh.hpp>
|
||||
#include <libslic3r/CSGMesh/SliceCSGMesh.hpp>
|
||||
#include <libslic3r/CSGMesh/VoxelizeCSGMesh.hpp>
|
||||
#include <libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp>
|
||||
#include <libslic3r/OpenVDBUtils.hpp>
|
||||
#include <libslic3r/QuadricEdgeCollapse.hpp>
|
||||
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
//#include <libslic3r/ShortEdgeCollapse.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@ -32,18 +41,20 @@ namespace Slic3r {
|
||||
namespace {
|
||||
|
||||
const std::array<unsigned, slaposCount> 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<class Cont> 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<class Cont>
|
||||
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<int>(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<int>(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<int>(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<const indexed_triangle_set>(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<CSGPartForStep> &s, SLAPrintObjectStep step)
|
||||
{
|
||||
auto r = s.equal_range(step);
|
||||
s.erase(r.first, r.second);
|
||||
}
|
||||
|
||||
struct csg_inserter {
|
||||
std::multiset<CSGPartForStep> &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<float>::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<std::string> facehash;
|
||||
|
||||
// Returns the string in reverse, but that is ok for hashing
|
||||
static std::array<char, MAX_NUM_CHARS + 1> to_chars(int64_t val)
|
||||
{
|
||||
std::array<char, MAX_NUM_CHARS + 1> ret;
|
||||
|
||||
static const constexpr char * Conv = "0123456789abcdef";
|
||||
|
||||
auto ptr = ret.begin();
|
||||
auto uval = static_cast<uint64_t>(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<Vec3f> &vertices)
|
||||
{
|
||||
// Scale to integer to avoid floating points
|
||||
std::array<Vec<3, int64_t>, 3> pts = {
|
||||
scaled<int64_t>(vertices[face(0)]),
|
||||
scaled<int64_t>(vertices[face(1)]),
|
||||
scaled<int64_t>(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<bool> &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<bool> create_exclude_mask(
|
||||
const indexed_triangle_set &its,
|
||||
const sla::Interior &interior,
|
||||
const std::vector<sla::DrainHole> &holes)
|
||||
{
|
||||
FaceHash interior_hash{sla::get_mesh(interior)};
|
||||
|
||||
std::vector<bool> 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<indexed_triangle_set>(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<double>() / 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<double>();
|
||||
Vec3d dhend = dhpos + dh.normal.cast<double>() * 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<int> 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<int, 3> 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<float> 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<float, 3> ebb{bb.min.cast<float>(),
|
||||
bb.max.cast<float>()};
|
||||
|
||||
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<class Pred>
|
||||
static std::vector<ExPolygons> slice_volumes(
|
||||
const ModelVolumePtrs &volumes,
|
||||
const std::vector<float> &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<ExPolygons> 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<class Cont> 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<bool> 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<ExPolygons> 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<ExPolygons> &blockers;
|
||||
const std::vector<ExPolygons> &enforcers;
|
||||
bool enforcers_only = false;
|
||||
};
|
||||
|
||||
static void filter_support_points_by_modifiers(sla::SupportPoints &pts,
|
||||
const SuppPtMask &mask,
|
||||
const std::vector<float> &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<sla::SupportPoint>(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<SLAPrintObject::SupportData>(*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<float>& 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<sla::SupportPoint>& points = auto_supports.output();
|
||||
std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||
throw_if_canceled();
|
||||
|
||||
MeshSlicingParamsEx params;
|
||||
params.closing_radius = float(po.config().slice_closing_radius.value);
|
||||
std::vector<ExPolygons> 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<ExPolygons> 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<SLAPrintObject::SupportData>(*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;
|
||||
|
@ -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<class...Args> void report_status(Args&&...args)
|
||||
{
|
||||
m_print->m_report_status(*m_print, std::forward<Args>(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
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_reduce.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "SupportMaterial.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "OpenVDBUtils.hpp"
|
||||
#include "OpenVDBUtilsLegacy.hpp"
|
||||
#include <openvdb/tools/VolumeToSpheres.h>
|
||||
|
||||
#include <cassert>
|
||||
@ -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<openvdb::FloatGrid>::create(*grid);
|
||||
std::vector<openvdb::Vec3R> pts, prev, projections;
|
||||
std::vector<float> distances;
|
||||
|
@ -269,12 +269,18 @@ inline int its_triangle_edge_index(const stl_triangle_vertex_indices &triangle_i
|
||||
|
||||
using its_triangle = std::array<stl_vertex, 3>;
|
||||
|
||||
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<double>(), bmax.cast<double>()};
|
||||
}
|
||||
|
||||
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<double>(), bmax.cast<double>()};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Serialization through the Cereal library
|
||||
|
@ -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<class Cont> auto range(Cont &&cont)
|
||||
{
|
||||
return Range{std::begin(cont), std::end(cont)};
|
||||
}
|
||||
|
||||
template<class T, class = FloatingOnly<T>>
|
||||
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 <instance_idx, print_instance_idx>
|
||||
const std::vector<std::pair<size_t, size_t>>& 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<size_t, size_t>& 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<GUI::MeshRaycaster>(std::make_shared<const TriangleMesh>(mesh));
|
||||
#endif // ENABLE_SMOOTH_NORMALS
|
||||
v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first);
|
||||
v.geometry_id = std::pair<size_t, size_t>(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,
|
||||
|
@ -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 <instance_idx, print_instance_idx>
|
||||
const std::vector<std::pair<size_t, size_t>>& 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);
|
||||
|
@ -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);
|
||||
|
@ -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<SLAPrintObjectStep, 3> SLASteps;
|
||||
SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad };
|
||||
struct SLASupportState {
|
||||
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
|
||||
};
|
||||
// State of the sla_steps for all SLAPrintObjects.
|
||||
std::vector<SLASupportState> sla_support_state;
|
||||
|
||||
std::vector<size_t> instance_ids_selected;
|
||||
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
|
||||
std::vector<GLVolumeState> 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<double> 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 <instance_idx, print_instance_idx>
|
||||
std::vector<std::pair<size_t, size_t>> instances[std::tuple_size<SLASteps>::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<GUI::MeshRaycaster>(std::make_shared<TriangleMesh>(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<GUI::MeshRaycaster>(std::make_shared<TriangleMesh>(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<size_t, size_t>(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<const indexed_triangle_set> 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
|
||||
|
@ -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
|
||||
|
||||
|
@ -65,6 +65,8 @@ std::pair<bool, std::string> 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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<std::pair<std::string, std::string>> 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<std::pair<const char *, const char *>, 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<std::pair<std::string, std::string>> MenuFactory::TEXT_VOLUME_ICONS {
|
||||
static const constexpr std::array<std::pair<const char *, const char *>, 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()
|
||||
|
@ -33,8 +33,6 @@ struct SettingsFactory
|
||||
class MenuFactory
|
||||
{
|
||||
public:
|
||||
static const std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS;
|
||||
static const std::vector<std::pair<std::string, std::string>> TEXT_VOLUME_ICONS;
|
||||
static std::vector<wxBitmapBundle*> get_volume_bitmaps();
|
||||
static std::vector<wxBitmapBundle*> 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);
|
||||
};
|
||||
|
||||
}}
|
||||
|
@ -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<ModelVolumeType> 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<int>& inst_idxs)
|
||||
|
@ -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),
|
||||
|
@ -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()) {
|
||||
|
@ -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 <GL/glew.h>
|
||||
@ -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<const indexed_triangle_set> 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<double>());
|
||||
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<double>()) * 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<Vec3f, Vec3f>& 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<std::shared_ptr<SceneRaycasterItem>>* 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<SceneRaycasterItem> 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<double>());
|
||||
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;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef slic3r_GLGizmoHollow_hpp_
|
||||
#define slic3r_GLGizmoHollow_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "GLGizmoSlaBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
@ -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<Vec3f, Vec3f>& 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<std::shared_ptr<SceneRaycasterItem>> m_raycasters;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> 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;
|
||||
|
180
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp
Normal file
180
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp
Normal file
@ -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<const indexed_triangle_set> 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<GUI::MeshRaycaster>(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<GUI::MeshRaycaster>(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<Vec3f, Vec3f>& 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
|
57
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp
Normal file
57
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp
Normal file
@ -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 <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
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<Vec3f, Vec3f>& 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<std::shared_ptr<SceneRaycasterItem>> m_volume_raycasters;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoSlaBase_hpp_
|
@ -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 <wx/settings.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
#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<MeshRaycaster>(std::make_shared<const TriangleMesh>(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<double>());
|
||||
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<double>()) * 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<double>());
|
||||
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<double>()))
|
||||
continue;
|
||||
|
||||
const Transform3d hole_matrix = Geometry::translation_transform(drain_hole.pos.cast<double>()) * 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<double>());
|
||||
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<Vec3f, Vec3f>& 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<float>() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast<float>());
|
||||
|
||||
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<void()> on_yes, std::function<void()> 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<sla::SupportPoint>& points = po->get_support_points();
|
||||
auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast<float>();
|
||||
auto mat = po->trafo().inverse().cast<float>();
|
||||
for (unsigned int i=0; i<points.size();++i)
|
||||
m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
|
||||
|
||||
@ -1209,7 +1110,7 @@ void GLGizmoSlaSupports::auto_generate()
|
||||
|
||||
if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points"));
|
||||
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
|
||||
wxGetApp().CallAfter([this]() { reslice_until_step(slaposPad); });
|
||||
mo->sla_points_status = sla::PointsStatus::Generating;
|
||||
}
|
||||
}
|
||||
@ -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<std::shared_ptr<SceneRaycasterItem>>* 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<SceneRaycasterItem> 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<double>()) * 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<double>());
|
||||
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<double>()) * 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<double>());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Vec3f, Vec3f>& 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::pair<std::shared_ptr<SceneRaycasterItem>, std::shared_ptr<SceneRaycasterItem>>> m_raycasters;
|
||||
GLModel m_cylinder;
|
||||
std::vector<std::pair<std::shared_ptr<SceneRaycasterItem>, std::shared_ptr<SceneRaycasterItem>>> 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;
|
||||
};
|
||||
|
@ -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<HollowedMesh*>(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<Raycaster*>(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<TriangleMesh>(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<const TriangleMesh*> 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<const indexed_triangle_set> 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<ModelVolume*>& 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<const TriangleMesh>(*mesh)));
|
||||
@ -362,8 +304,9 @@ std::vector<const MeshRaycaster*> 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<const TriangleMesh*> 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<Geometry::Transformation> 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<MeshClipper> 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<MeshClipper>();
|
||||
mc->set_mesh(range(partstoslice));
|
||||
mc_tr = Geometry::Transformation{po->trafo().inverse().cast<double>()};
|
||||
}
|
||||
}
|
||||
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<MeshClipper>& 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<MeshClipper>& 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<Vec3d> 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;
|
||||
|
||||
|
@ -5,11 +5,12 @@
|
||||
#include <map>
|
||||
|
||||
#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<const TriangleMesh*> m_old_meshes;
|
||||
std::vector<std::unique_ptr<MeshClipper>> 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<TriangleMesh> m_hollowed_mesh_transformed;
|
||||
std::unique_ptr<TriangleMesh> 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<std::unique_ptr<MeshRaycaster>> m_raycasters;
|
||||
std::vector<const TriangleMesh*> 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<const TriangleMesh*> m_old_meshes;
|
||||
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
|
||||
// Used to store the sla mesh coming from the backend
|
||||
TriangleMesh m_sla_mesh_cache;
|
||||
std::vector<std::pair<std::unique_ptr<MeshClipper>, Geometry::Transformation>> m_clippers;
|
||||
std::unique_ptr<ClippingPlane> m_clp;
|
||||
double m_clp_ratio = 0.;
|
||||
double m_active_inst_bb_radius = 0.;
|
||||
|
@ -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<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->reslice_SLA_supports(true);
|
||||
dynamic_cast<GLGizmoSlaSupports*>(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
|
||||
|
@ -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;
|
||||
|
@ -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 <GL/glew.h>
|
||||
|
||||
#include <igl/unproject.h>
|
||||
@ -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<const indexed_triangle_set> &&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<const indexed_triangle_set> &&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<double, Eigen::DontAlign>::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);
|
||||
|
@ -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 <memory>
|
||||
|
||||
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<const indexed_triangle_set> &&ptr);
|
||||
|
||||
void set_negative_mesh(const TriangleMesh &mesh);
|
||||
void set_negative_mesh(const indexed_triangle_set &mesh);
|
||||
void set_negative_mesh(AnyPtr<const indexed_triangle_set> &&ptr);
|
||||
|
||||
template<class It>
|
||||
void set_mesh(const Range<It> &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<const indexed_triangle_set> m_mesh;
|
||||
AnyPtr<const indexed_triangle_set> m_negative_mesh;
|
||||
std::vector<csg::CSGPart> m_csgmesh;
|
||||
|
||||
ClippingPlane m_plane;
|
||||
ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing();
|
||||
|
||||
@ -138,13 +157,17 @@ private:
|
||||
class MeshRaycaster {
|
||||
public:
|
||||
explicit MeshRaycaster(std::shared_ptr<const TriangleMesh> 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<TriangleMesh>(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);
|
||||
|
@ -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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<float>());
|
||||
}
|
||||
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<SLAPrintObject::Instance>& 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<const indexed_triangle_set> 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<TriangleMesh(const ModelObject& mo, int instance_id)>
|
||||
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<float>());
|
||||
}
|
||||
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.
|
||||
|
@ -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);
|
||||
|
@ -86,7 +86,7 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr<SceneRaycasterItem> 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<double>::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<std::optional<HitResult>*>(&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<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters(type);
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters(type);
|
||||
const Vec3f camera_forward = camera.get_dir_forward().cast<float>();
|
||||
HitResult current_hit = { type };
|
||||
for (std::shared_ptr<SceneRaycasterItem> 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<std::optional<HitResult>*>(&m_last_hit) = ret;
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
return ret;
|
||||
}
|
||||
@ -212,6 +212,20 @@ std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_raycasters
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_raycasters(EType type) const
|
||||
{
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>>* 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)
|
||||
|
@ -87,10 +87,11 @@ public:
|
||||
void remove_raycaster(std::shared_ptr<SceneRaycasterItem> item);
|
||||
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>>* get_raycasters(EType type);
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>>* 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);
|
||||
|
@ -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<size_t> &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<unsigned int> Selection::get_volume_idxs_from_object(unsigned int ob
|
||||
{
|
||||
std::vector<unsigned int> 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<unsigned int> Selection::get_volume_idxs_from_instance(unsigned int
|
||||
{
|
||||
std::vector<unsigned int> 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()
|
||||
|
@ -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");
|
||||
|
@ -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)});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user