Merge branch 'et_tm_sla_volumes_6-SPE-1285'

This commit is contained in:
tamasmeszaros 2023-01-18 16:56:54 +01:00
commit e3af59b3ee
76 changed files with 3797 additions and 1762 deletions

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

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

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

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

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

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

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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

View 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 &params,
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

View 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 &part;
}
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

View 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 &params = {})
{
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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