mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-25 07:34:03 -06:00
Merge branch 'master' into tm_rotcalipers
This commit is contained in:
commit
4a71c42f9b
90 changed files with 3806 additions and 3809 deletions
|
@ -130,13 +130,10 @@ add_library(libslic3r STATIC
|
|||
Print.hpp
|
||||
PrintBase.cpp
|
||||
PrintBase.hpp
|
||||
PrintExport.hpp
|
||||
PrintConfig.cpp
|
||||
PrintConfig.hpp
|
||||
PrintObject.cpp
|
||||
PrintRegion.cpp
|
||||
Rasterizer/Rasterizer.hpp
|
||||
Rasterizer/Rasterizer.cpp
|
||||
SLAPrint.cpp
|
||||
SLAPrint.hpp
|
||||
SLA/SLAAutoSupports.hpp
|
||||
|
@ -177,6 +174,10 @@ add_library(libslic3r STATIC
|
|||
SLA/SLARotfinder.cpp
|
||||
SLA/SLABoostAdapter.hpp
|
||||
SLA/SLASpatIndex.hpp
|
||||
SLA/SLARaster.hpp
|
||||
SLA/SLARaster.cpp
|
||||
SLA/SLARasterWriter.hpp
|
||||
SLA/SLARasterWriter.cpp
|
||||
)
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
|
@ -184,13 +185,12 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
|||
endif ()
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB)
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_link_libraries(libslic3r
|
||||
libnest2d
|
||||
admesh
|
||||
miniz
|
||||
${Boost_LIBRARIES}
|
||||
boost_libs
|
||||
clipper
|
||||
nowide
|
||||
${EXPAT_LIBRARIES}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
#include "FillRectilinear3.hpp"
|
||||
|
||||
#define SLIC3R_DEBUG
|
||||
// #define SLIC3R_DEBUG
|
||||
|
||||
// Make assert active if SLIC3R_DEBUG
|
||||
#ifdef SLIC3R_DEBUG
|
||||
|
|
|
@ -1489,10 +1489,10 @@ namespace Slic3r {
|
|||
}
|
||||
|
||||
// splits volume out of imported geometry
|
||||
unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
|
||||
ModelVolume* volume = object.add_volume(TriangleMesh());
|
||||
stl_file& stl = volume->mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
TriangleMesh triangle_mesh;
|
||||
stl_file &stl = triangle_mesh.stl;
|
||||
unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = (uint32_t)triangles_count;
|
||||
stl.stats.original_num_facets = (int)stl.stats.number_of_facets;
|
||||
stl_allocate(&stl);
|
||||
|
@ -1509,9 +1509,11 @@ namespace Slic3r {
|
|||
}
|
||||
}
|
||||
|
||||
stl_get_size(&stl);
|
||||
volume->mesh.repair();
|
||||
volume->center_geometry();
|
||||
stl_get_size(&stl);
|
||||
triangle_mesh.repair();
|
||||
|
||||
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
|
||||
volume->center_geometry_after_creation();
|
||||
volume->calculate_convex_hull();
|
||||
|
||||
// apply volume's name and config data
|
||||
|
@ -1879,29 +1881,28 @@ namespace Slic3r {
|
|||
if (volume == nullptr)
|
||||
continue;
|
||||
|
||||
if (!volume->mesh().repaired)
|
||||
throw std::runtime_error("store_3mf() requires repair()");
|
||||
if (!volume->mesh().has_shared_vertices())
|
||||
throw std::runtime_error("store_3mf() requires shared vertices");
|
||||
|
||||
volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first;
|
||||
|
||||
if (!volume->mesh.repaired)
|
||||
volume->mesh.repair();
|
||||
|
||||
stl_file& stl = volume->mesh.stl;
|
||||
if (stl.v_shared == nullptr)
|
||||
stl_generate_shared_vertices(&stl);
|
||||
|
||||
if (stl.stats.shared_vertices == 0)
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
if (its.vertices.empty())
|
||||
{
|
||||
add_error("Found invalid mesh");
|
||||
return false;
|
||||
}
|
||||
|
||||
vertices_count += stl.stats.shared_vertices;
|
||||
vertices_count += its.vertices.size();
|
||||
|
||||
const Transform3d& matrix = volume->get_matrix();
|
||||
|
||||
for (int i = 0; i < stl.stats.shared_vertices; ++i)
|
||||
for (size_t i = 0; i < its.vertices.size(); ++i)
|
||||
{
|
||||
stream << " <" << VERTEX_TAG << " ";
|
||||
Vec3f v = (matrix * stl.v_shared[i].cast<double>()).cast<float>();
|
||||
Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>();
|
||||
stream << "x=\"" << v(0) << "\" ";
|
||||
stream << "y=\"" << v(1) << "\" ";
|
||||
stream << "z=\"" << v(2) << "\" />\n";
|
||||
|
@ -1920,19 +1921,19 @@ namespace Slic3r {
|
|||
VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume);
|
||||
assert(volume_it != volumes_offsets.end());
|
||||
|
||||
stl_file& stl = volume->mesh.stl;
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
|
||||
// updates triangle offsets
|
||||
volume_it->second.first_triangle_id = triangles_count;
|
||||
triangles_count += stl.stats.number_of_facets;
|
||||
triangles_count += its.indices.size();
|
||||
volume_it->second.last_triangle_id = triangles_count - 1;
|
||||
|
||||
for (uint32_t i = 0; i < stl.stats.number_of_facets; ++i)
|
||||
for (size_t i = 0; i < its.indices.size(); ++ i)
|
||||
{
|
||||
stream << " <" << TRIANGLE_TAG << " ";
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
stream << "v" << j + 1 << "=\"" << stl.v_indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" ";
|
||||
stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" ";
|
||||
}
|
||||
stream << "/>\n";
|
||||
}
|
||||
|
|
|
@ -522,7 +522,8 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
case NODE_TYPE_VOLUME:
|
||||
{
|
||||
assert(m_object && m_volume);
|
||||
stl_file &stl = m_volume->mesh.stl;
|
||||
TriangleMesh mesh;
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
|
||||
stl.stats.original_num_facets = stl.stats.number_of_facets;
|
||||
|
@ -533,8 +534,9 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
|
||||
}
|
||||
stl_get_size(&stl);
|
||||
m_volume->mesh.repair();
|
||||
m_volume->center_geometry();
|
||||
mesh.repair();
|
||||
m_volume->set_mesh(std::move(mesh));
|
||||
m_volume->center_geometry_after_creation();
|
||||
m_volume->calculate_convex_hull();
|
||||
m_volume_facets.clear();
|
||||
m_volume = nullptr;
|
||||
|
@ -923,23 +925,23 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
|||
int num_vertices = 0;
|
||||
for (ModelVolume *volume : object->volumes) {
|
||||
vertices_offsets.push_back(num_vertices);
|
||||
if (! volume->mesh.repaired)
|
||||
if (! volume->mesh().repaired)
|
||||
throw std::runtime_error("store_amf() requires repair()");
|
||||
auto &stl = volume->mesh.stl;
|
||||
if (stl.v_shared == nullptr)
|
||||
stl_generate_shared_vertices(&stl);
|
||||
if (! volume->mesh().has_shared_vertices())
|
||||
throw std::runtime_error("store_amf() requires shared vertices");
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
const Transform3d& matrix = volume->get_matrix();
|
||||
for (size_t i = 0; i < stl.stats.shared_vertices; ++i) {
|
||||
for (size_t i = 0; i < its.vertices.size(); ++i) {
|
||||
stream << " <vertex>\n";
|
||||
stream << " <coordinates>\n";
|
||||
Vec3f v = (matrix * stl.v_shared[i].cast<double>()).cast<float>();
|
||||
Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>();
|
||||
stream << " <x>" << v(0) << "</x>\n";
|
||||
stream << " <y>" << v(1) << "</y>\n";
|
||||
stream << " <z>" << v(2) << "</z>\n";
|
||||
stream << " </coordinates>\n";
|
||||
stream << " </vertex>\n";
|
||||
}
|
||||
num_vertices += stl.stats.shared_vertices;
|
||||
num_vertices += its.vertices.size();
|
||||
}
|
||||
stream << " </vertices>\n";
|
||||
for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) {
|
||||
|
@ -956,10 +958,11 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
|||
if (volume->is_modifier())
|
||||
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
|
||||
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
|
||||
for (int i = 0; i < (int)volume->mesh.stl.stats.number_of_facets; ++i) {
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
for (size_t i = 0; i < (int)its.indices.size(); ++i) {
|
||||
stream << " <triangle>\n";
|
||||
for (int j = 0; j < 3; ++j)
|
||||
stream << " <v" << j + 1 << ">" << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "</v" << j + 1 << ">\n";
|
||||
stream << " <v" << j + 1 << ">" << its.indices[i][j] + vertices_offset << "</v" << j + 1 << ">\n";
|
||||
stream << " </triangle>\n";
|
||||
}
|
||||
stream << " </volume>\n";
|
||||
|
|
|
@ -161,16 +161,15 @@ static void extract_model_from_archive(
|
|||
else {
|
||||
// Header has been extracted. Now read the faces.
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.error = 0;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = header.nTriangles;
|
||||
stl.stats.original_num_facets = header.nTriangles;
|
||||
stl_allocate(&stl);
|
||||
if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) {
|
||||
memcpy((char*)stl.facet_start, data.data() + sizeof(StlHeader), 50 * header.nTriangles);
|
||||
memcpy((char*)stl.facet_start.data(), data.data() + sizeof(StlHeader), 50 * header.nTriangles);
|
||||
if (sizeof(stl_facet) > SIZEOF_STL_FACET) {
|
||||
// The stl.facet_start is not packed tightly. Unpack the array of stl_facets.
|
||||
unsigned char *data = (unsigned char*)stl.facet_start;
|
||||
unsigned char *data = (unsigned char*)stl.facet_start.data();
|
||||
for (size_t i = header.nTriangles - 1; i > 0; -- i)
|
||||
memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET);
|
||||
}
|
||||
|
@ -257,7 +256,7 @@ static void extract_model_from_archive(
|
|||
stl.stats.number_of_facets = (uint32_t)facets.size();
|
||||
stl.stats.original_num_facets = (int)facets.size();
|
||||
stl_allocate(&stl);
|
||||
memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50);
|
||||
memcpy((void*)stl.facet_start.data(), facets.data(), facets.size() * 50);
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
// Add a mesh to a model.
|
||||
|
|
|
@ -17,8 +17,7 @@ namespace Slic3r {
|
|||
bool load_stl(const char *path, Model *model, const char *object_name_in)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
mesh.ReadSTLFile(path);
|
||||
if (mesh.stl.error) {
|
||||
if (! mesh.ReadSTLFile(path)) {
|
||||
// die "Failed to open $file\n" if !-e $path;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <mutex> // for std::lock_guard
|
||||
#include <functional> // for std::function
|
||||
#include <utility> // for std::forward
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -182,6 +183,14 @@ public:
|
|||
inline bool empty() const { return size() == 0; }
|
||||
};
|
||||
|
||||
template<class C> bool all_of(const C &container) {
|
||||
return std::all_of(container.begin(),
|
||||
container.end(),
|
||||
[](const typename C::value_type &v) {
|
||||
return static_cast<bool>(v);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // MTUTILS_HPP
|
||||
|
|
|
@ -160,12 +160,6 @@ Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig
|
|||
return model;
|
||||
}
|
||||
|
||||
void Model::repair()
|
||||
{
|
||||
for (ModelObject *o : this->objects)
|
||||
o->repair();
|
||||
}
|
||||
|
||||
ModelObject* Model::add_object()
|
||||
{
|
||||
this->objects.emplace_back(new ModelObject(this));
|
||||
|
@ -472,7 +466,7 @@ bool Model::looks_like_multipart_object() const
|
|||
if (obj->volumes.size() > 1 || obj->config.keys().size() > 1)
|
||||
return false;
|
||||
for (const ModelVolume *vol : obj->volumes) {
|
||||
double zmin_this = vol->mesh.bounding_box().min(2);
|
||||
double zmin_this = vol->mesh().bounding_box().min(2);
|
||||
if (zmin == std::numeric_limits<double>::max())
|
||||
zmin = zmin_this;
|
||||
else if (std::abs(zmin - zmin_this) > EPSILON)
|
||||
|
@ -679,7 +673,7 @@ ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh)
|
|||
{
|
||||
ModelVolume* v = new ModelVolume(this, mesh);
|
||||
this->volumes.push_back(v);
|
||||
v->center_geometry();
|
||||
v->center_geometry_after_creation();
|
||||
this->invalidate_bounding_box();
|
||||
return v;
|
||||
}
|
||||
|
@ -688,7 +682,7 @@ ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh)
|
|||
{
|
||||
ModelVolume* v = new ModelVolume(this, std::move(mesh));
|
||||
this->volumes.push_back(v);
|
||||
v->center_geometry();
|
||||
v->center_geometry_after_creation();
|
||||
this->invalidate_bounding_box();
|
||||
return v;
|
||||
}
|
||||
|
@ -697,8 +691,9 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other)
|
|||
{
|
||||
ModelVolume* v = new ModelVolume(this, other);
|
||||
this->volumes.push_back(v);
|
||||
v->center_geometry();
|
||||
this->invalidate_bounding_box();
|
||||
// The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull.
|
||||
// v->center_geometry_after_creation();
|
||||
// this->invalidate_bounding_box();
|
||||
return v;
|
||||
}
|
||||
|
||||
|
@ -706,7 +701,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&me
|
|||
{
|
||||
ModelVolume* v = new ModelVolume(this, other, std::move(mesh));
|
||||
this->volumes.push_back(v);
|
||||
v->center_geometry();
|
||||
v->center_geometry_after_creation();
|
||||
this->invalidate_bounding_box();
|
||||
return v;
|
||||
}
|
||||
|
@ -827,7 +822,7 @@ TriangleMesh ModelObject::raw_mesh() const
|
|||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part())
|
||||
{
|
||||
TriangleMesh vol_mesh(v->mesh);
|
||||
TriangleMesh vol_mesh(v->mesh());
|
||||
vol_mesh.transform(v->get_matrix());
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
|
@ -840,7 +835,7 @@ TriangleMesh ModelObject::full_raw_mesh() const
|
|||
TriangleMesh mesh;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
{
|
||||
TriangleMesh vol_mesh(v->mesh);
|
||||
TriangleMesh vol_mesh(v->mesh());
|
||||
vol_mesh.transform(v->get_matrix());
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
|
@ -854,7 +849,7 @@ const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const
|
|||
m_raw_mesh_bounding_box.reset();
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part())
|
||||
m_raw_mesh_bounding_box.merge(v->mesh.transformed_bounding_box(v->get_matrix()));
|
||||
m_raw_mesh_bounding_box.merge(v->mesh().transformed_bounding_box(v->get_matrix()));
|
||||
}
|
||||
return m_raw_mesh_bounding_box;
|
||||
}
|
||||
|
@ -863,7 +858,7 @@ BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const
|
|||
{
|
||||
BoundingBoxf3 bb;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
bb.merge(v->mesh.transformed_bounding_box(v->get_matrix()));
|
||||
bb.merge(v->mesh().transformed_bounding_box(v->get_matrix()));
|
||||
return bb;
|
||||
}
|
||||
|
||||
|
@ -881,7 +876,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
|
|||
for (const ModelVolume *v : this->volumes)
|
||||
{
|
||||
if (v->is_model_part())
|
||||
m_raw_bounding_box.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix()));
|
||||
m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
|
||||
}
|
||||
}
|
||||
return m_raw_bounding_box;
|
||||
|
@ -895,7 +890,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
|
|||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
if (v->is_model_part())
|
||||
bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix()));
|
||||
bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
|
||||
}
|
||||
return bb;
|
||||
}
|
||||
|
@ -908,21 +903,20 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const
|
|||
Points pts;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part()) {
|
||||
const stl_file &stl = v->mesh.stl;
|
||||
Transform3d trafo = trafo_instance * v->get_matrix();
|
||||
if (stl.v_shared == nullptr) {
|
||||
const indexed_triangle_set &its = v->mesh().its;
|
||||
if (its.vertices.empty()) {
|
||||
// Using the STL faces.
|
||||
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++ i) {
|
||||
const stl_facet &facet = stl.facet_start[i];
|
||||
const stl_file& stl = v->mesh().stl;
|
||||
for (const stl_facet &facet : stl.facet_start)
|
||||
for (size_t j = 0; j < 3; ++ j) {
|
||||
Vec3d p = trafo * facet.vertex[j].cast<double>();
|
||||
pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y())));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Using the shared vertices should be a bit quicker than using the STL faces.
|
||||
for (int i = 0; i < stl.stats.shared_vertices; ++ i) {
|
||||
Vec3d p = trafo * stl.v_shared[i].cast<double>();
|
||||
for (size_t i = 0; i < its.vertices.size(); ++ i) {
|
||||
Vec3d p = trafo * its.vertices[i].cast<double>();
|
||||
pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y())));
|
||||
}
|
||||
}
|
||||
|
@ -1039,6 +1033,7 @@ void ModelObject::mirror(Axis axis)
|
|||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void ModelObject::scale_mesh(const Vec3d &versor)
|
||||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
|
@ -1062,14 +1057,14 @@ size_t ModelObject::facets_count() const
|
|||
size_t num = 0;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part())
|
||||
num += v->mesh.stl.stats.number_of_facets;
|
||||
num += v->mesh().stl.stats.number_of_facets;
|
||||
return num;
|
||||
}
|
||||
|
||||
bool ModelObject::needed_repair() const
|
||||
{
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part() && v->mesh.needed_repair())
|
||||
if (v->is_model_part() && v->mesh().needed_repair())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
@ -1135,11 +1130,12 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
volume->mesh.transform(instance_matrix * volume_matrix, true);
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(instance_matrix * volume_matrix, true);
|
||||
volume->reset_mesh();
|
||||
|
||||
// Perform cut
|
||||
volume->mesh.require_shared_vertices(); // TriangleMeshSlicer needs this
|
||||
TriangleMeshSlicer tms(&volume->mesh);
|
||||
TriangleMeshSlicer tms(&mesh);
|
||||
tms.cut(float(z), &upper_mesh, &lower_mesh);
|
||||
|
||||
// Reset volume transformation except for offset
|
||||
|
@ -1158,14 +1154,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
|
||||
if (keep_upper && upper_mesh.facets_count() > 0) {
|
||||
ModelVolume* vol = upper->add_volume(upper_mesh);
|
||||
vol->name = volume->name;
|
||||
vol->config = volume->config;
|
||||
vol->name = volume->name;
|
||||
vol->config = volume->config;
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
}
|
||||
if (keep_lower && lower_mesh.facets_count() > 0) {
|
||||
ModelVolume* vol = lower->add_volume(lower_mesh);
|
||||
vol->name = volume->name;
|
||||
vol->config = volume->config;
|
||||
vol->name = volume->name;
|
||||
vol->config = volume->config;
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
|
||||
// Compute the lower part instances' bounding boxes to figure out where to place
|
||||
|
@ -1233,7 +1229,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
|||
}
|
||||
|
||||
ModelVolume* volume = this->volumes.front();
|
||||
TriangleMeshPtrs meshptrs = volume->mesh.split();
|
||||
TriangleMeshPtrs meshptrs = volume->mesh().split();
|
||||
for (TriangleMesh *mesh : meshptrs) {
|
||||
mesh->repair();
|
||||
|
||||
|
@ -1260,12 +1256,6 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
|||
return;
|
||||
}
|
||||
|
||||
void ModelObject::repair()
|
||||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
v->mesh.repair();
|
||||
}
|
||||
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||
// This situation is solved by baking in the instance transformation into the mesh vertices.
|
||||
|
@ -1295,8 +1285,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
|
|||
|
||||
// Adjust the meshes.
|
||||
// Transformation to be applied to the meshes.
|
||||
Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0);
|
||||
Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
|
||||
Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0);
|
||||
Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
|
||||
for (ModelVolume *model_volume : this->volumes) {
|
||||
const Geometry::Transformation volume_trafo = model_volume->get_transformation();
|
||||
bool volume_left_handed = volume_trafo.is_left_handed();
|
||||
|
@ -1306,7 +1296,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
|
|||
double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.;
|
||||
// Transform the mesh.
|
||||
Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0);
|
||||
model_volume->transform_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed);
|
||||
// Following method creates a new shared_ptr<TriangleMesh>
|
||||
model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed);
|
||||
// Reset the rotation, scaling and mirroring.
|
||||
model_volume->set_rotation(Vec3d(0., 0., 0.));
|
||||
model_volume->set_scaling_factor(Vec3d(volume_new_scaling_factor, volume_new_scaling_factor, volume_new_scaling_factor));
|
||||
|
@ -1347,13 +1338,9 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const
|
|||
|
||||
Transform3d mv = mi * v->get_matrix();
|
||||
const TriangleMesh& hull = v->get_convex_hull();
|
||||
for (uint32_t f = 0; f < hull.stl.stats.number_of_facets; ++f)
|
||||
{
|
||||
const stl_facet* facet = hull.stl.facet_start + f;
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[0].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[1].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[2].cast<double>()));
|
||||
}
|
||||
for (const stl_facet &facet : hull.stl.facet_start)
|
||||
for (int i = 0; i < 3; ++ i)
|
||||
min_z = std::min(min_z, (mv * facet.vertex[i].cast<double>()).z());
|
||||
}
|
||||
|
||||
return min_z + inst->get_offset(Z);
|
||||
|
@ -1452,7 +1439,7 @@ std::string ModelObject::get_export_filename() const
|
|||
stl_stats ModelObject::get_object_stl_stats() const
|
||||
{
|
||||
if (this->volumes.size() == 1)
|
||||
return this->volumes[0]->mesh.stl.stats;
|
||||
return this->volumes[0]->mesh().stl.stats;
|
||||
|
||||
stl_stats full_stats;
|
||||
memset(&full_stats, 0, sizeof(stl_stats));
|
||||
|
@ -1463,7 +1450,7 @@ stl_stats ModelObject::get_object_stl_stats() const
|
|||
if (volume->id() == this->volumes[0]->id())
|
||||
continue;
|
||||
|
||||
const stl_stats& stats = volume->mesh.stl.stats;
|
||||
const stl_stats& stats = volume->mesh().stl.stats;
|
||||
|
||||
// initialize full_stats (for repaired errors)
|
||||
full_stats.degenerate_facets += stats.degenerate_facets;
|
||||
|
@ -1531,30 +1518,30 @@ bool ModelVolume::is_splittable() const
|
|||
{
|
||||
// the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
|
||||
if (m_is_splittable == -1)
|
||||
m_is_splittable = (int)mesh.is_splittable();
|
||||
m_is_splittable = (int)this->mesh().is_splittable();
|
||||
|
||||
return m_is_splittable == 1;
|
||||
}
|
||||
|
||||
void ModelVolume::center_geometry()
|
||||
void ModelVolume::center_geometry_after_creation()
|
||||
{
|
||||
Vec3d shift = mesh.bounding_box().center();
|
||||
Vec3d shift = this->mesh().bounding_box().center();
|
||||
if (!shift.isApprox(Vec3d::Zero()))
|
||||
{
|
||||
mesh.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
m_convex_hull.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
translate(shift);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelVolume::calculate_convex_hull()
|
||||
{
|
||||
m_convex_hull = mesh.convex_hull_3d();
|
||||
m_convex_hull = std::make_shared<TriangleMesh>(this->mesh().convex_hull_3d());
|
||||
}
|
||||
|
||||
int ModelVolume::get_mesh_errors_count() const
|
||||
{
|
||||
const stl_stats& stats = this->mesh.stl.stats;
|
||||
const stl_stats& stats = this->mesh().stl.stats;
|
||||
|
||||
return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
|
||||
stats.facets_added + stats.facets_reversed + stats.backwards_edges;
|
||||
|
@ -1562,7 +1549,7 @@ int ModelVolume::get_mesh_errors_count() const
|
|||
|
||||
const TriangleMesh& ModelVolume::get_convex_hull() const
|
||||
{
|
||||
return m_convex_hull;
|
||||
return *m_convex_hull.get();
|
||||
}
|
||||
|
||||
ModelVolumeType ModelVolume::type_from_string(const std::string &s)
|
||||
|
@ -1602,7 +1589,7 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t)
|
|||
// This is useful to assign different materials to different volumes of an object.
|
||||
size_t ModelVolume::split(unsigned int max_extruders)
|
||||
{
|
||||
TriangleMeshPtrs meshptrs = this->mesh.split();
|
||||
TriangleMeshPtrs meshptrs = this->mesh().split();
|
||||
if (meshptrs.size() <= 1) {
|
||||
delete meshptrs.front();
|
||||
return 1;
|
||||
|
@ -1619,7 +1606,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
|||
mesh->repair();
|
||||
if (idx == 0)
|
||||
{
|
||||
this->mesh = std::move(*mesh);
|
||||
this->set_mesh(std::move(*mesh));
|
||||
this->calculate_convex_hull();
|
||||
// Assign a new unique ID, so that a new GLVolume will be generated.
|
||||
this->set_new_unique_id();
|
||||
|
@ -1628,7 +1615,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
|||
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh)));
|
||||
|
||||
this->object->volumes[ivolume]->set_offset(Vec3d::Zero());
|
||||
this->object->volumes[ivolume]->center_geometry();
|
||||
this->object->volumes[ivolume]->center_geometry_after_creation();
|
||||
this->object->volumes[ivolume]->translate(offset);
|
||||
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
|
||||
this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders));
|
||||
|
@ -1694,24 +1681,33 @@ void ModelVolume::mirror(Axis axis)
|
|||
set_mirror(mirror);
|
||||
}
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void ModelVolume::scale_geometry(const Vec3d& versor)
|
||||
{
|
||||
mesh.scale(versor);
|
||||
m_convex_hull.scale(versor);
|
||||
m_mesh->scale(versor);
|
||||
m_convex_hull->scale(versor);
|
||||
}
|
||||
|
||||
void ModelVolume::transform_mesh(const Transform3d &mesh_trafo, bool fix_left_handed)
|
||||
void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed)
|
||||
{
|
||||
this->mesh.transform(mesh_trafo, fix_left_handed);
|
||||
this->m_convex_hull.transform(mesh_trafo, fix_left_handed);
|
||||
TriangleMesh mesh = this->mesh();
|
||||
mesh.transform(mesh_trafo, fix_left_handed);
|
||||
this->set_mesh(std::move(mesh));
|
||||
TriangleMesh convex_hull = this->get_convex_hull();
|
||||
convex_hull.transform(mesh_trafo, fix_left_handed);
|
||||
this->m_convex_hull = std::make_shared<TriangleMesh>(std::move(convex_hull));
|
||||
// Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
|
||||
this->set_new_unique_id();
|
||||
}
|
||||
|
||||
void ModelVolume::transform_mesh(const Matrix3d &matrix, bool fix_left_handed)
|
||||
void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_handed)
|
||||
{
|
||||
this->mesh.transform(matrix, fix_left_handed);
|
||||
this->m_convex_hull.transform(matrix, fix_left_handed);
|
||||
TriangleMesh mesh = this->mesh();
|
||||
mesh.transform(matrix, fix_left_handed);
|
||||
this->set_mesh(std::move(mesh));
|
||||
TriangleMesh convex_hull = this->get_convex_hull();
|
||||
convex_hull.transform(matrix, fix_left_handed);
|
||||
this->m_convex_hull = std::make_shared<TriangleMesh>(std::move(convex_hull));
|
||||
// Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
|
||||
this->set_new_unique_id();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
#include "Point.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "Slicing.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -261,6 +263,7 @@ public:
|
|||
void rotate(double angle, const Vec3d& axis);
|
||||
void mirror(Axis axis);
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void scale_mesh(const Vec3d& versor);
|
||||
|
||||
size_t materials_count() const;
|
||||
|
@ -268,7 +271,6 @@ public:
|
|||
bool needed_repair() const;
|
||||
ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates
|
||||
void split(ModelObjectPtrs* new_objects);
|
||||
void repair();
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||
// This situation is solved by baking in the instance transformation into the mesh vertices.
|
||||
|
@ -340,7 +342,12 @@ class ModelVolume : public ModelBase
|
|||
public:
|
||||
std::string name;
|
||||
// The triangular model.
|
||||
TriangleMesh mesh;
|
||||
const TriangleMesh& mesh() const { return *m_mesh.get(); }
|
||||
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<TriangleMesh>(mesh); }
|
||||
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<TriangleMesh>(std::move(mesh)); }
|
||||
void set_mesh(std::shared_ptr<TriangleMesh> &mesh) { m_mesh = mesh; }
|
||||
void set_mesh(std::unique_ptr<TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
|
||||
void reset_mesh() { m_mesh = std::make_shared<TriangleMesh>(); }
|
||||
// Configuration parameters specific to an object model geometry or a modifier volume,
|
||||
// overriding the global Slic3r settings and the ModelObject settings.
|
||||
DynamicPrintConfig config;
|
||||
|
@ -377,13 +384,16 @@ public:
|
|||
void rotate(double angle, const Vec3d& axis);
|
||||
void mirror(Axis axis);
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void scale_geometry(const Vec3d& versor);
|
||||
|
||||
// translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box
|
||||
void center_geometry();
|
||||
// Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
|
||||
// Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
|
||||
void center_geometry_after_creation();
|
||||
|
||||
void calculate_convex_hull();
|
||||
const TriangleMesh& get_convex_hull() const;
|
||||
std::shared_ptr<const TriangleMesh> get_convex_hull_shared_ptr() const { return m_convex_hull; }
|
||||
// Get count of errors in the mesh
|
||||
int get_mesh_errors_count() const;
|
||||
|
||||
|
@ -430,18 +440,20 @@ protected:
|
|||
|
||||
explicit ModelVolume(const ModelVolume &rhs) = default;
|
||||
void set_model_object(ModelObject *model_object) { object = model_object; }
|
||||
void transform_mesh(const Transform3d& t, bool fix_left_handed);
|
||||
void transform_mesh(const Matrix3d& m, bool fix_left_handed);
|
||||
void transform_this_mesh(const Transform3d& t, bool fix_left_handed);
|
||||
void transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
|
||||
|
||||
private:
|
||||
// Parent object owning this ModelVolume.
|
||||
ModelObject* object;
|
||||
ModelObject* object;
|
||||
// The triangular model.
|
||||
std::shared_ptr<TriangleMesh> m_mesh;
|
||||
// Is it an object to be printed, or a modifier volume?
|
||||
ModelVolumeType m_type;
|
||||
t_model_material_id m_material_id;
|
||||
ModelVolumeType m_type;
|
||||
t_model_material_id m_material_id;
|
||||
// The convex hull of this model's mesh.
|
||||
TriangleMesh m_convex_hull;
|
||||
Geometry::Transformation m_transformation;
|
||||
std::shared_ptr<TriangleMesh> m_convex_hull;
|
||||
Geometry::Transformation m_transformation;
|
||||
|
||||
// flag to optimize the checking if the volume is splittable
|
||||
// -1 -> is unknown value (before first cheking)
|
||||
|
@ -449,24 +461,24 @@ private:
|
|||
// 1 -> is splittable
|
||||
mutable int m_is_splittable{ -1 };
|
||||
|
||||
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object)
|
||||
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object)
|
||||
{
|
||||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
calculate_convex_hull();
|
||||
}
|
||||
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
|
||||
mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(ModelVolumeType::MODEL_PART), object(object) {}
|
||||
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {}
|
||||
|
||||
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other) :
|
||||
ModelBase(other), // copy the ID
|
||||
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
{
|
||||
this->set_material_id(other.material_id());
|
||||
}
|
||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
|
||||
name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
{
|
||||
this->set_material_id(other.material_id());
|
||||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
|
@ -597,10 +609,6 @@ public:
|
|||
static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true);
|
||||
static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true);
|
||||
|
||||
/// Repair the ModelObjects of the current Model.
|
||||
/// This function calls repair function on each TriangleMesh of each model object volume
|
||||
void repair();
|
||||
|
||||
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
|
||||
ModelObject* add_object();
|
||||
ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);
|
||||
|
|
|
@ -2258,6 +2258,20 @@ void PrintConfigDef::init_sla_params()
|
|||
def->min = 100;
|
||||
def->set_default_value(new ConfigOptionInt(1440));
|
||||
|
||||
def = this->add("display_mirror_x", coBool);
|
||||
def->full_label = L("Display horizontal mirroring");
|
||||
def->label = L("Mirror horizontally");
|
||||
def->tooltip = L("Enable horizontal mirroring of output images");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("display_mirror_y", coBool);
|
||||
def->full_label = L("Display vertical mirroring");
|
||||
def->label = L("Mirror vertically");
|
||||
def->tooltip = L("Enable vertical mirroring of output images");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("display_orientation", coEnum);
|
||||
def->label = L("Display orientation");
|
||||
def->tooltip = L("Set the actual LCD display orientation inside the SLA printer."
|
||||
|
|
|
@ -1083,6 +1083,8 @@ public:
|
|||
ConfigOptionInt display_pixels_x;
|
||||
ConfigOptionInt display_pixels_y;
|
||||
ConfigOptionEnum<SLADisplayOrientation> display_orientation;
|
||||
ConfigOptionBool display_mirror_x;
|
||||
ConfigOptionBool display_mirror_y;
|
||||
ConfigOptionFloats relative_correction;
|
||||
ConfigOptionFloat absolute_correction;
|
||||
ConfigOptionFloat gamma_correction;
|
||||
|
@ -1099,6 +1101,8 @@ protected:
|
|||
OPT_PTR(display_height);
|
||||
OPT_PTR(display_pixels_x);
|
||||
OPT_PTR(display_pixels_y);
|
||||
OPT_PTR(display_mirror_x);
|
||||
OPT_PTR(display_mirror_y);
|
||||
OPT_PTR(display_orientation);
|
||||
OPT_PTR(relative_correction);
|
||||
OPT_PTR(absolute_correction);
|
||||
|
|
|
@ -1,327 +0,0 @@
|
|||
#ifndef PRINTEXPORT_HPP
|
||||
#define PRINTEXPORT_HPP
|
||||
|
||||
// For png export of the sliced model
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include "Rasterizer/Rasterizer.hpp"
|
||||
//#include <tbb/parallel_for.h>
|
||||
//#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Used for addressing parameters of FilePrinter::set_statistics()
|
||||
enum ePrintStatistics
|
||||
{
|
||||
psUsedMaterial = 0,
|
||||
psNumFade,
|
||||
psNumSlow,
|
||||
psNumFast,
|
||||
|
||||
psCnt
|
||||
};
|
||||
|
||||
enum class FilePrinterFormat {
|
||||
SLA_PNGZIP,
|
||||
SVG
|
||||
};
|
||||
|
||||
/*
|
||||
* Interface for a file printer of the slices. Implementation can be an SVG
|
||||
* or PNG printer or any other format.
|
||||
*
|
||||
* The format argument specifies the output format of the printer and it enables
|
||||
* different implementations of this class template for each supported format.
|
||||
*
|
||||
*/
|
||||
template<FilePrinterFormat format>
|
||||
class FilePrinter {
|
||||
public:
|
||||
|
||||
// Draw a polygon which is a polygon inside a slice on the specified layer.
|
||||
void draw_polygon(const ExPolygon& p, unsigned lyr);
|
||||
void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr);
|
||||
|
||||
// Tell the printer how many layers should it consider.
|
||||
void layers(unsigned layernum);
|
||||
|
||||
// Get the number of layers in the print.
|
||||
unsigned layers() const;
|
||||
|
||||
/* Switch to a particular layer. If there where less layers then the
|
||||
* specified layer number than an appropriate number of layers will be
|
||||
* allocated in the printer.
|
||||
*/
|
||||
void begin_layer(unsigned layer);
|
||||
|
||||
// Allocate a new layer on top of the last and switch to it.
|
||||
void begin_layer();
|
||||
|
||||
/*
|
||||
* Finish the selected layer. It means that no drawing is allowed on that
|
||||
* layer anymore. This fact can be used to prepare the file system output
|
||||
* data like png comprimation and so on.
|
||||
*/
|
||||
void finish_layer(unsigned layer);
|
||||
|
||||
// Finish the top layer.
|
||||
void finish_layer();
|
||||
|
||||
// Save all the layers into the file (or dir) specified in the path argument
|
||||
// An optional project name can be added to be used for the layer file names
|
||||
void save(const std::string& path, const std::string& projectname = "");
|
||||
|
||||
// Save only the selected layer to the file specified in path argument.
|
||||
void save_layer(unsigned lyr, const std::string& path);
|
||||
};
|
||||
|
||||
// Provokes static_assert in the right way.
|
||||
template<class T = void> struct VeryFalse { static const bool value = false; };
|
||||
|
||||
// This can be explicitly implemented in the gui layer or the default Zipper
|
||||
// API in libslic3r with minz.
|
||||
template<class Fmt> class LayerWriter {
|
||||
public:
|
||||
|
||||
LayerWriter(const std::string& /*zipfile_path*/)
|
||||
{
|
||||
static_assert(VeryFalse<Fmt>::value,
|
||||
"No layer writer implementation provided!");
|
||||
}
|
||||
|
||||
// Should create a new file within the zip with the given filename. It
|
||||
// should also finish any previous entry.
|
||||
void next_entry(const std::string& /*fname*/) {}
|
||||
|
||||
// Should create a new file within the archive and write the provided data.
|
||||
void binary_entry(const std::string& /*fname*/,
|
||||
const std::uint8_t* buf, size_t len);
|
||||
|
||||
// Test whether the object can still be used for writing.
|
||||
bool is_ok() { return false; }
|
||||
|
||||
// Write some data (text) into the current file (entry) within the archive.
|
||||
template<class T> LayerWriter& operator<<(T&& /*arg*/) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Flush the current entry into the archive.
|
||||
void finalize() {}
|
||||
};
|
||||
|
||||
// Implementation for PNG raster output
|
||||
// Be aware that if a large number of layers are allocated, it can very well
|
||||
// exhaust the available memory especially on 32 bit platform.
|
||||
template<> class FilePrinter<FilePrinterFormat::SLA_PNGZIP>
|
||||
{
|
||||
struct Layer {
|
||||
Raster raster;
|
||||
RawBytes rawbytes;
|
||||
|
||||
Layer() {}
|
||||
|
||||
Layer(const Layer&) = delete;
|
||||
Layer(Layer&& m):
|
||||
raster(std::move(m.raster)) {}
|
||||
};
|
||||
|
||||
// We will save the compressed PNG data into stringstreams which can be done
|
||||
// in parallel. Later we can write every layer to the disk sequentially.
|
||||
std::vector<Layer> m_layers_rst;
|
||||
Raster::Resolution m_res;
|
||||
Raster::PixelDim m_pxdim;
|
||||
double m_exp_time_s = .0, m_exp_time_first_s = .0;
|
||||
double m_layer_height = .0;
|
||||
Raster::Origin m_o = Raster::Origin::TOP_LEFT;
|
||||
double m_gamma;
|
||||
|
||||
double m_used_material = 0.0;
|
||||
int m_cnt_fade_layers = 0;
|
||||
int m_cnt_slow_layers = 0;
|
||||
int m_cnt_fast_layers = 0;
|
||||
|
||||
std::string createIniContent(const std::string& projectname) {
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
|
||||
auto expt_str = to_string(m_exp_time_s);
|
||||
auto expt_first_str = to_string(m_exp_time_first_s);
|
||||
auto layerh_str = to_string(m_layer_height);
|
||||
|
||||
const std::string cnt_fade_layers = to_string(m_cnt_fade_layers);
|
||||
const std::string cnt_slow_layers = to_string(m_cnt_slow_layers);
|
||||
const std::string cnt_fast_layers = to_string(m_cnt_fast_layers);
|
||||
const std::string used_material = to_string(m_used_material);
|
||||
|
||||
return string(
|
||||
"action = print\n"
|
||||
"jobDir = ") + projectname + "\n" +
|
||||
"expTime = " + expt_str + "\n"
|
||||
"expTimeFirst = " + expt_first_str + "\n"
|
||||
"numFade = " + cnt_fade_layers + "\n"
|
||||
"layerHeight = " + layerh_str + "\n"
|
||||
"usedMaterial = " + used_material + "\n"
|
||||
"numSlow = " + cnt_slow_layers + "\n"
|
||||
"numFast = " + cnt_fast_layers + "\n";
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
enum RasterOrientation {
|
||||
RO_LANDSCAPE,
|
||||
RO_PORTRAIT
|
||||
};
|
||||
|
||||
// We will play with the raster's coordinate origin parameter. When the
|
||||
// printer should print in landscape mode it should have the Y axis flipped
|
||||
// because the layers should be displayed upside down. PNG has its
|
||||
// coordinate origin in the top-left corner so normally the Raster objects
|
||||
// should be instantiated with the TOP_LEFT flag. However, in landscape mode
|
||||
// we do want the pictures to be upside down so we will make BOTTOM_LEFT
|
||||
// type rasters and the PNG format will do the flipping automatically.
|
||||
|
||||
// In case of portrait images, we have to rotate the image by a 90 degrees
|
||||
// and flip the y axis. To get the correct upside-down orientation of the
|
||||
// slice images, we can flip the x and y coordinates of the input polygons
|
||||
// and do the Y flipping of the image. This will generate the correct
|
||||
// orientation in portrait mode.
|
||||
|
||||
inline FilePrinter(double width_mm, double height_mm,
|
||||
unsigned width_px, unsigned height_px,
|
||||
double layer_height,
|
||||
double exp_time, double exp_time_first,
|
||||
RasterOrientation ro = RO_PORTRAIT,
|
||||
double gamma = 1.0):
|
||||
m_res(width_px, height_px),
|
||||
m_pxdim(width_mm/width_px, height_mm/height_px),
|
||||
m_exp_time_s(exp_time),
|
||||
m_exp_time_first_s(exp_time_first),
|
||||
m_layer_height(layer_height),
|
||||
|
||||
// Here is the trick with the orientation.
|
||||
m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT :
|
||||
Raster::Origin::TOP_LEFT ),
|
||||
m_gamma(gamma)
|
||||
{
|
||||
}
|
||||
|
||||
FilePrinter(const FilePrinter& ) = delete;
|
||||
FilePrinter(FilePrinter&& m):
|
||||
m_layers_rst(std::move(m.m_layers_rst)),
|
||||
m_res(m.m_res),
|
||||
m_pxdim(m.m_pxdim) {}
|
||||
|
||||
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
||||
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
||||
|
||||
inline void draw_polygon(const ExPolygon& p, unsigned lyr) {
|
||||
assert(lyr < m_layers_rst.size());
|
||||
m_layers_rst[lyr].raster.draw(p);
|
||||
}
|
||||
|
||||
inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) {
|
||||
assert(lyr < m_layers_rst.size());
|
||||
m_layers_rst[lyr].raster.draw(p);
|
||||
}
|
||||
|
||||
inline void begin_layer(unsigned lyr) {
|
||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
||||
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o, m_gamma);
|
||||
}
|
||||
|
||||
inline void begin_layer() {
|
||||
m_layers_rst.emplace_back();
|
||||
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_o, m_gamma);
|
||||
}
|
||||
|
||||
inline void finish_layer(unsigned lyr_id) {
|
||||
assert(lyr_id < m_layers_rst.size());
|
||||
m_layers_rst[lyr_id].rawbytes =
|
||||
m_layers_rst[lyr_id].raster.save(Raster::Compression::PNG);
|
||||
m_layers_rst[lyr_id].raster.reset();
|
||||
}
|
||||
|
||||
inline void finish_layer() {
|
||||
if(!m_layers_rst.empty()) {
|
||||
m_layers_rst.back().rawbytes =
|
||||
m_layers_rst.back().raster.save(Raster::Compression::PNG);
|
||||
m_layers_rst.back().raster.reset();
|
||||
}
|
||||
}
|
||||
|
||||
template<class LyrFmt>
|
||||
inline void save(const std::string& fpath, const std::string& prjname = "")
|
||||
{
|
||||
try {
|
||||
LayerWriter<LyrFmt> writer(fpath);
|
||||
if(!writer.is_ok()) return;
|
||||
|
||||
std::string project = prjname.empty()?
|
||||
boost::filesystem::path(fpath).stem().string() : prjname;
|
||||
|
||||
writer.next_entry("config.ini");
|
||||
if(!writer.is_ok()) return;
|
||||
|
||||
writer << createIniContent(project);
|
||||
|
||||
for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++)
|
||||
{
|
||||
if(m_layers_rst[i].rawbytes.size() > 0) {
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", i);
|
||||
auto zfilename = project + lyrnum + ".png";
|
||||
if(!writer.is_ok()) break;
|
||||
|
||||
writer.binary_entry(zfilename,
|
||||
m_layers_rst[i].rawbytes.data(),
|
||||
m_layers_rst[i].rawbytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
writer.finalize();
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void save_layer(unsigned lyr, const std::string& path) {
|
||||
unsigned i = lyr;
|
||||
assert(i < m_layers_rst.size());
|
||||
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", lyr);
|
||||
std::string loc = path + "layer" + lyrnum + ".png";
|
||||
|
||||
std::fstream out(loc, std::fstream::out | std::fstream::binary);
|
||||
if(out.good()) {
|
||||
m_layers_rst[i].raster.save(out, Raster::Compression::PNG);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Can't create file for layer";
|
||||
}
|
||||
|
||||
out.close();
|
||||
m_layers_rst[i].raster.reset();
|
||||
}
|
||||
|
||||
void set_statistics(const std::vector<double> statistics)
|
||||
{
|
||||
if (statistics.size() != psCnt)
|
||||
return;
|
||||
|
||||
m_used_material = statistics[psUsedMaterial];
|
||||
m_cnt_fade_layers = int(statistics[psNumFade]);
|
||||
m_cnt_slow_layers = int(statistics[psNumSlow]);
|
||||
m_cnt_fast_layers = int(statistics[psNumFast]);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PRINTEXPORT_HPP
|
|
@ -1797,7 +1797,7 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
|
|||
if (! volumes.empty()) {
|
||||
// Compose mesh.
|
||||
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
|
||||
TriangleMesh mesh(volumes.front()->mesh);
|
||||
TriangleMesh mesh(volumes.front()->mesh());
|
||||
mesh.transform(volumes.front()->get_matrix(), true);
|
||||
assert(mesh.repaired);
|
||||
if (volumes.size() == 1 && mesh.repaired) {
|
||||
|
@ -1806,7 +1806,7 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
|
|||
}
|
||||
for (size_t idx_volume = 1; idx_volume < volumes.size(); ++ idx_volume) {
|
||||
const ModelVolume &model_volume = *volumes[idx_volume];
|
||||
TriangleMesh vol_mesh(model_volume.mesh);
|
||||
TriangleMesh vol_mesh(model_volume.mesh());
|
||||
vol_mesh.transform(model_volume.get_matrix(), true);
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
|
@ -1815,10 +1815,11 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
|
|||
// apply XY shift
|
||||
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
|
||||
// perform actual slicing
|
||||
TriangleMeshSlicer mslicer;
|
||||
const Print *print = this->print();
|
||||
auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();});
|
||||
mesh.require_shared_vertices(); // TriangleMeshSlicer needs this
|
||||
// TriangleMeshSlicer needs shared vertices, also this calls the repair() function.
|
||||
mesh.require_shared_vertices();
|
||||
TriangleMeshSlicer mslicer;
|
||||
mslicer.init(&mesh, callback);
|
||||
mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
|
||||
m_print->throw_if_canceled();
|
||||
|
@ -1832,7 +1833,7 @@ std::vector<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &z,
|
|||
std::vector<ExPolygons> layers;
|
||||
// Compose mesh.
|
||||
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
|
||||
TriangleMesh mesh(volume.mesh);
|
||||
TriangleMesh mesh(volume.mesh());
|
||||
mesh.transform(volume.get_matrix(), true);
|
||||
if (mesh.repaired) {
|
||||
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
|
||||
|
@ -1846,7 +1847,8 @@ std::vector<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &z,
|
|||
TriangleMeshSlicer mslicer;
|
||||
const Print *print = this->print();
|
||||
auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();});
|
||||
mesh.require_shared_vertices(); // TriangleMeshSlicer needs this
|
||||
// TriangleMeshSlicer needs the shared vertices.
|
||||
mesh.require_shared_vertices();
|
||||
mslicer.init(&mesh, callback);
|
||||
mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
|
||||
m_print->throw_if_canceled();
|
||||
|
|
|
@ -53,7 +53,7 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||
|
||||
// Shorthand for the vertex arrays
|
||||
auto& upoints = upper.points, &lpoints = lower.points;
|
||||
auto& rpts = ret.points; auto& rfaces = ret.indices;
|
||||
auto& rpts = ret.points; auto& ind = ret.indices;
|
||||
|
||||
// If the Z levels are flipped, or the offset difference is negative, we
|
||||
// will interpret that as the triangles normals should be inverted.
|
||||
|
@ -61,10 +61,11 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||
|
||||
// Copy the points into the mesh, convert them from 2D to 3D
|
||||
rpts.reserve(upoints.size() + lpoints.size());
|
||||
rfaces.reserve(2*upoints.size() + 2*lpoints.size());
|
||||
const double sf = SCALING_FACTOR;
|
||||
for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm);
|
||||
for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm);
|
||||
ind.reserve(2 * upoints.size() + 2 * lpoints.size());
|
||||
for (auto &p : upoints)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm);
|
||||
for (auto &p : lpoints)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm);
|
||||
|
||||
// Create pointing indices into vertex arrays. u-upper, l-lower
|
||||
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
|
||||
|
@ -121,9 +122,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||
case Proceed::UPPER:
|
||||
if(!ustarted || uidx != uendidx) { // there are vertices remaining
|
||||
// Get the 3D vertices in order
|
||||
const Vec3d& p_up1 = rpts[size_t(uidx)];
|
||||
const Vec3d& p_low = rpts[size_t(lidx)];
|
||||
const Vec3d& p_up2 = rpts[size_t(unextidx)];
|
||||
const Vec3d& p_up1 = rpts[uidx];
|
||||
const Vec3d& p_low = rpts[lidx];
|
||||
const Vec3d& p_up2 = rpts[unextidx];
|
||||
|
||||
// Calculate fitness: the average of the two connecting edges
|
||||
double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2);
|
||||
|
@ -133,8 +134,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||
if(current_fit > prev_fit) { // fit is worse than previously
|
||||
proceed = Proceed::LOWER;
|
||||
} else { // good to go, create the triangle
|
||||
inverted? rfaces.emplace_back(unextidx, lidx, uidx) :
|
||||
rfaces.emplace_back(uidx, lidx, unextidx) ;
|
||||
inverted
|
||||
? ind.emplace_back(int(unextidx), int(lidx), int(uidx))
|
||||
: ind.emplace_back(int(uidx), int(lidx), int(unextidx));
|
||||
|
||||
// Increment the iterators, rotate if necessary
|
||||
++uidx; ++unextidx;
|
||||
|
@ -150,9 +152,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||
case Proceed::LOWER:
|
||||
// Mode with lower segment, upper vertex. Same structure:
|
||||
if(!lstarted || lidx != lendidx) {
|
||||
const Vec3d& p_low1 = rpts[size_t(lidx)];
|
||||
const Vec3d& p_low2 = rpts[size_t(lnextidx)];
|
||||
const Vec3d& p_up = rpts[size_t(uidx)];
|
||||
const Vec3d& p_low1 = rpts[lidx];
|
||||
const Vec3d& p_low2 = rpts[lnextidx];
|
||||
const Vec3d& p_up = rpts[uidx];
|
||||
|
||||
double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2);
|
||||
double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2);
|
||||
|
@ -161,8 +163,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||
if(current_fit > prev_fit) {
|
||||
proceed = Proceed::UPPER;
|
||||
} else {
|
||||
inverted? rfaces.emplace_back(uidx, lnextidx, lidx) :
|
||||
rfaces.emplace_back(lidx, lnextidx, uidx);
|
||||
inverted
|
||||
? ind.emplace_back(int(uidx), int(lnextidx), int(lidx))
|
||||
: ind.emplace_back(int(lidx), int(lnextidx), int(uidx));
|
||||
|
||||
++lidx; ++lnextidx;
|
||||
if(lnextidx == rpts.size()) lnextidx = offs;
|
||||
|
@ -200,7 +203,7 @@ void offset(ExPolygon& sh, coord_t distance) {
|
|||
}
|
||||
|
||||
ClipperOffset offs;
|
||||
offs.ArcTolerance = 0.01*mm(1);
|
||||
offs.ArcTolerance = 0.01*scaled(1.0);
|
||||
Paths result;
|
||||
offs.AddPath(ctour, jtRound, etClosedPolygon);
|
||||
offs.AddPaths(holes, jtRound, etClosedPolygon);
|
||||
|
@ -303,16 +306,6 @@ ExPolygons unify(const ExPolygons& shapes) {
|
|||
return retv;
|
||||
}
|
||||
|
||||
/// Only a debug function to generate top and bottom plates from a 2D shape.
|
||||
/// It is not used in the algorithm directly.
|
||||
inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
|
||||
auto lower = triangulate_expolygon_3d(poly);
|
||||
auto upper = triangulate_expolygon_3d(poly, z_distance*SCALING_FACTOR, true);
|
||||
Contour3D ret;
|
||||
ret.merge(lower); ret.merge(upper);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// This method will create a rounded edge around a flat polygon in 3d space.
|
||||
/// 'base_plate' parameter is the target plate.
|
||||
/// 'radius' is the radius of the edges.
|
||||
|
@ -358,7 +351,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
|||
double x2 = xx*xx;
|
||||
double stepy = std::sqrt(r2 - x2);
|
||||
|
||||
offset(ob, s*mm(xx));
|
||||
offset(ob, s*scaled(xx));
|
||||
wh = ceilheight_mm - radius_mm + stepy;
|
||||
|
||||
Contour3D pwalls;
|
||||
|
@ -382,7 +375,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
|||
double xx = radius_mm - i*stepx;
|
||||
double x2 = xx*xx;
|
||||
double stepy = std::sqrt(r2 - x2);
|
||||
offset(ob, s*mm(xx));
|
||||
offset(ob, s*scaled(xx));
|
||||
wh = ceilheight_mm - radius_mm - stepy;
|
||||
|
||||
Contour3D pwalls;
|
||||
|
@ -402,41 +395,6 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
|||
return curvedwalls;
|
||||
}
|
||||
|
||||
/// Generating the concave part of the 3D pool with the bottom plate and the
|
||||
/// side walls.
|
||||
Contour3D inner_bed(const ExPolygon& poly,
|
||||
double depth_mm,
|
||||
double begin_h_mm = 0)
|
||||
{
|
||||
Contour3D bottom;
|
||||
Pointf3s triangles = triangulate_expolygon_3d(poly, -depth_mm + begin_h_mm);
|
||||
bottom.merge(triangles);
|
||||
|
||||
coord_t depth = mm(depth_mm);
|
||||
coord_t begin_h = mm(begin_h_mm);
|
||||
|
||||
auto lines = poly.lines();
|
||||
|
||||
// Generate outer walls
|
||||
auto fp = [](const Point& p, Point::coord_type z) {
|
||||
return unscale(x(p), y(p), z);
|
||||
};
|
||||
|
||||
for(auto& l : lines) {
|
||||
auto s = coord_t(bottom.points.size());
|
||||
|
||||
bottom.points.emplace_back(fp(l.a, -depth + begin_h));
|
||||
bottom.points.emplace_back(fp(l.b, -depth + begin_h));
|
||||
bottom.points.emplace_back(fp(l.a, begin_h));
|
||||
bottom.points.emplace_back(fp(l.b, begin_h));
|
||||
|
||||
bottom.indices.emplace_back(s + 3, s + 1, s);
|
||||
bottom.indices.emplace_back(s + 2, s + 3, s);
|
||||
}
|
||||
|
||||
return bottom;
|
||||
}
|
||||
|
||||
inline Point centroid(Points& pp) {
|
||||
Point c;
|
||||
switch(pp.size()) {
|
||||
|
@ -518,7 +476,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
|||
double dx = x(c) - x(cc), dy = y(c) - y(cc);
|
||||
double l = std::sqrt(dx * dx + dy * dy);
|
||||
double nx = dx / l, ny = dy / l;
|
||||
double max_dist = mm(max_dist_mm);
|
||||
double max_dist = scaled(max_dist_mm);
|
||||
|
||||
ExPolygon& expo = punion[idx++];
|
||||
BoundingBox querybb(expo);
|
||||
|
@ -534,10 +492,10 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
|||
ctour.reserve(3);
|
||||
ctour.emplace_back(cc);
|
||||
|
||||
Point d(coord_t(mm(1)*nx), coord_t(mm(1)*ny));
|
||||
Point d(coord_t(scaled(1.)*nx), coord_t(scaled(1.)*ny));
|
||||
ctour.emplace_back(c + Point( -y(d), x(d) ));
|
||||
ctour.emplace_back(c + Point( y(d), -x(d) ));
|
||||
offset(r, mm(1));
|
||||
offset(r, scaled(1.));
|
||||
|
||||
return r;
|
||||
});
|
||||
|
@ -569,15 +527,16 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
|
|||
// Now we have to unify all slice layers which can be an expensive operation
|
||||
// so we will try to simplify the polygons
|
||||
ExPolygons tmp; tmp.reserve(count);
|
||||
for(ExPolygons& o : out) for(ExPolygon& e : o) {
|
||||
auto&& exss = e.simplify(0.1/SCALING_FACTOR);
|
||||
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
||||
}
|
||||
for(ExPolygons& o : out)
|
||||
for(ExPolygon& e : o) {
|
||||
auto&& exss = e.simplify(scaled(0.1));
|
||||
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
||||
}
|
||||
|
||||
ExPolygons utmp = unify(tmp);
|
||||
|
||||
for(auto& o : utmp) {
|
||||
auto&& smp = o.simplify(0.1/SCALING_FACTOR);
|
||||
auto&& smp = o.simplify(scaled(0.1));
|
||||
output.insert(output.end(), smp.begin(), smp.end());
|
||||
}
|
||||
}
|
||||
|
@ -607,11 +566,11 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||
const double bottom_offs = (thickness + wingheight) / std::tan(slope);
|
||||
|
||||
// scaled values
|
||||
const coord_t s_thickness = mm(thickness);
|
||||
const coord_t s_eradius = mm(cfg.edge_radius_mm);
|
||||
const coord_t s_thickness = scaled(thickness);
|
||||
const coord_t s_eradius = scaled(cfg.edge_radius_mm);
|
||||
const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness);
|
||||
const coord_t s_wingdist = mm(wingdist);
|
||||
const coord_t s_bottom_offs = mm(bottom_offs);
|
||||
const coord_t s_wingdist = scaled(wingdist);
|
||||
const coord_t s_bottom_offs = scaled(bottom_offs);
|
||||
|
||||
auto& thrcl = cfg.throw_on_cancel;
|
||||
|
||||
|
|
|
@ -11,11 +11,6 @@
|
|||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
using coord_t = Point::coord_type;
|
||||
|
||||
/// get the scaled clipper units for a millimeter value
|
||||
inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); }
|
||||
|
||||
/// Get x and y coordinates (because we are eigenizing...)
|
||||
inline coord_t x(const Point& p) { return p(0); }
|
||||
inline coord_t y(const Point& p) { return p(1); }
|
||||
|
@ -36,12 +31,10 @@ inline coord_t x(const Vec3crd& p) { return p(0); }
|
|||
inline coord_t y(const Vec3crd& p) { return p(1); }
|
||||
inline coord_t z(const Vec3crd& p) { return p(2); }
|
||||
|
||||
using Indices = std::vector<Vec3crd>;
|
||||
|
||||
/// Intermediate struct for a 3D mesh
|
||||
struct Contour3D {
|
||||
Pointf3s points;
|
||||
Indices indices;
|
||||
std::vector<Vec3i> indices;
|
||||
|
||||
void merge(const Contour3D& ctr) {
|
||||
auto s3 = coord_t(points.size());
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
#include "Rasterizer.hpp"
|
||||
#include <ExPolygon.hpp>
|
||||
#ifndef SLARASTER_CPP
|
||||
#define SLARASTER_CPP
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
// For rasterizing
|
||||
|
@ -19,11 +24,13 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
const Polygon& contour(const ExPolygon& p) { return p.contour; }
|
||||
const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; }
|
||||
inline const Polygon& contour(const ExPolygon& p) { return p.contour; }
|
||||
inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; }
|
||||
|
||||
const Polygons& holes(const ExPolygon& p) { return p.holes; }
|
||||
const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; }
|
||||
inline const Polygons& holes(const ExPolygon& p) { return p.holes; }
|
||||
inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; }
|
||||
|
||||
namespace sla {
|
||||
|
||||
class Raster::Impl {
|
||||
public:
|
||||
|
@ -39,7 +46,7 @@ public:
|
|||
static const TPixel ColorWhite;
|
||||
static const TPixel ColorBlack;
|
||||
|
||||
using Origin = Raster::Origin;
|
||||
using Format = Raster::Format;
|
||||
|
||||
private:
|
||||
Raster::Resolution m_resolution;
|
||||
|
@ -52,16 +59,21 @@ private:
|
|||
TRendererAA m_renderer;
|
||||
|
||||
std::function<double(double)> m_gammafn;
|
||||
Origin m_o;
|
||||
std::array<bool, 2> m_mirror;
|
||||
Format m_fmt = Format::PNG;
|
||||
|
||||
inline void flipy(agg::path_storage& path) const {
|
||||
path.flip_y(0, m_resolution.height_px);
|
||||
}
|
||||
|
||||
inline void flipx(agg::path_storage& path) const {
|
||||
path.flip_x(0, m_resolution.width_px);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
|
||||
Origin o, double gamma = 1.0):
|
||||
const std::array<bool, 2>& mirror, double gamma = 1.0):
|
||||
m_resolution(res),
|
||||
// m_pxdim(pd),
|
||||
m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm),
|
||||
|
@ -72,7 +84,7 @@ public:
|
|||
m_pixfmt(m_rbuf),
|
||||
m_raw_renderer(m_pixfmt),
|
||||
m_renderer(m_raw_renderer),
|
||||
m_o(o)
|
||||
m_mirror(mirror)
|
||||
{
|
||||
m_renderer.color(ColorWhite);
|
||||
|
||||
|
@ -81,6 +93,19 @@ public:
|
|||
|
||||
clear();
|
||||
}
|
||||
|
||||
inline Impl(const Raster::Resolution& res,
|
||||
const Raster::PixelDim &pd,
|
||||
Format fmt,
|
||||
double gamma = 1.0):
|
||||
Impl(res, pd, {false, false}, gamma)
|
||||
{
|
||||
switch (fmt) {
|
||||
case Format::PNG: m_mirror = {false, true}; break;
|
||||
case Format::RAW: m_mirror = {false, false}; break;
|
||||
}
|
||||
m_fmt = fmt;
|
||||
}
|
||||
|
||||
template<class P> void draw(const P &poly) {
|
||||
agg::rasterizer_scanline_aa<> ras;
|
||||
|
@ -89,14 +114,16 @@ public:
|
|||
ras.gamma(m_gammafn);
|
||||
|
||||
auto&& path = to_path(contour(poly));
|
||||
|
||||
if(m_o == Origin::TOP_LEFT) flipy(path);
|
||||
|
||||
if(m_mirror[X]) flipx(path);
|
||||
if(m_mirror[Y]) flipy(path);
|
||||
|
||||
ras.add_path(path);
|
||||
|
||||
for(auto& h : holes(poly)) {
|
||||
auto&& holepath = to_path(h);
|
||||
if(m_o == Origin::TOP_LEFT) flipy(holepath);
|
||||
if(m_mirror[X]) flipx(holepath);
|
||||
if(m_mirror[Y]) flipy(holepath);
|
||||
ras.add_path(holepath);
|
||||
}
|
||||
|
||||
|
@ -108,11 +135,11 @@ public:
|
|||
}
|
||||
|
||||
inline TBuffer& buffer() { return m_buf; }
|
||||
|
||||
inline Format format() const { return m_fmt; }
|
||||
|
||||
inline const Raster::Resolution resolution() { return m_resolution; }
|
||||
|
||||
inline Origin origin() const /*noexcept*/ { return m_o; }
|
||||
|
||||
|
||||
private:
|
||||
inline double getPx(const Point& p) {
|
||||
return p(0) * m_pxdim_scaled.w_mm;
|
||||
|
@ -154,30 +181,30 @@ private:
|
|||
const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
|
||||
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
|
||||
|
||||
Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o, double g):
|
||||
m_impl(new Impl(r, pd, o, g)) {}
|
||||
template<> Raster::Raster() { reset(); };
|
||||
Raster::~Raster() = default;
|
||||
|
||||
Raster::Raster() {}
|
||||
// Raster::Raster(Raster &&m) = default;
|
||||
// Raster& Raster::operator=(Raster&&) = default;
|
||||
|
||||
Raster::~Raster() {}
|
||||
|
||||
Raster::Raster(Raster &&m):
|
||||
m_impl(std::move(m.m_impl)) {}
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||
double g)
|
||||
{
|
||||
// Free up the unnecessary memory and make sure it stays clear after
|
||||
// an exception
|
||||
auto o = m_impl? m_impl->origin() : Origin::TOP_LEFT;
|
||||
reset(r, pd, o, g);
|
||||
// FIXME: remove after migrating to higher version of windows compiler
|
||||
Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {}
|
||||
Raster& Raster::operator=(Raster &&m) {
|
||||
m_impl = std::move(m.m_impl); return *this;
|
||||
}
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||
Raster::Origin o, double gamma)
|
||||
Format fmt, double gamma)
|
||||
{
|
||||
m_impl.reset();
|
||||
m_impl.reset(new Impl(r, pd, o, gamma));
|
||||
m_impl.reset(new Impl(r, pd, fmt, gamma));
|
||||
}
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||
const std::array<bool, 2>& mirror, double gamma)
|
||||
{
|
||||
m_impl.reset();
|
||||
m_impl.reset(new Impl(r, pd, mirror, gamma));
|
||||
}
|
||||
|
||||
void Raster::reset()
|
||||
|
@ -208,13 +235,13 @@ void Raster::draw(const ClipperLib::Polygon &poly)
|
|||
m_impl->draw(poly);
|
||||
}
|
||||
|
||||
void Raster::save(std::ostream& stream, Compression comp)
|
||||
void Raster::save(std::ostream& stream, Format fmt)
|
||||
{
|
||||
assert(m_impl);
|
||||
if(!stream.good()) return;
|
||||
|
||||
switch(comp) {
|
||||
case Compression::PNG: {
|
||||
switch(fmt) {
|
||||
case Format::PNG: {
|
||||
auto& b = m_impl->buffer();
|
||||
size_t out_len = 0;
|
||||
void * rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
|
@ -231,7 +258,7 @@ void Raster::save(std::ostream& stream, Compression comp)
|
|||
|
||||
break;
|
||||
}
|
||||
case Compression::RAW: {
|
||||
case Format::RAW: {
|
||||
stream << "P5 "
|
||||
<< m_impl->resolution().width_px << " "
|
||||
<< m_impl->resolution().height_px << " "
|
||||
|
@ -244,14 +271,19 @@ void Raster::save(std::ostream& stream, Compression comp)
|
|||
}
|
||||
}
|
||||
|
||||
RawBytes Raster::save(Raster::Compression comp)
|
||||
void Raster::save(std::ostream &stream)
|
||||
{
|
||||
save(stream, m_impl->format());
|
||||
}
|
||||
|
||||
RawBytes Raster::save(Format fmt)
|
||||
{
|
||||
assert(m_impl);
|
||||
|
||||
std::vector<std::uint8_t> data; size_t s = 0;
|
||||
|
||||
switch(comp) {
|
||||
case Compression::PNG: {
|
||||
switch(fmt) {
|
||||
case Format::PNG: {
|
||||
void *rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
m_impl->buffer().data(),
|
||||
int(resolution().width_px),
|
||||
|
@ -265,7 +297,7 @@ RawBytes Raster::save(Raster::Compression comp)
|
|||
MZ_FREE(rawdata);
|
||||
break;
|
||||
}
|
||||
case Compression::RAW: {
|
||||
case Format::RAW: {
|
||||
auto header = std::string("P5 ") +
|
||||
std::to_string(m_impl->resolution().width_px) + " " +
|
||||
std::to_string(m_impl->resolution().height_px) + " " + "255 ";
|
||||
|
@ -286,4 +318,12 @@ RawBytes Raster::save(Raster::Compression comp)
|
|||
return {std::move(data)};
|
||||
}
|
||||
|
||||
RawBytes Raster::save()
|
||||
{
|
||||
return save(m_impl->format());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SLARASTER_CPP
|
|
@ -1,17 +1,21 @@
|
|||
#ifndef RASTERIZER_HPP
|
||||
#define RASTERIZER_HPP
|
||||
#ifndef SLARASTER_HPP
|
||||
#define SLARASTER_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ClipperLib { struct Polygon; }
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
|
||||
namespace sla {
|
||||
|
||||
// Raw byte buffer paired with its size. Suitable for compressed PNG data.
|
||||
class RawBytes {
|
||||
|
||||
|
@ -23,15 +27,18 @@ public:
|
|||
|
||||
size_t size() const { return m_buffer.size(); }
|
||||
const uint8_t * data() { return m_buffer.data(); }
|
||||
|
||||
RawBytes(const RawBytes&) = delete;
|
||||
RawBytes& operator=(const RawBytes&) = delete;
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// FIXME: the following is needed for MSVC2013 compatibility
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
RawBytes(const RawBytes&) = delete;
|
||||
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
|
||||
// RawBytes(RawBytes&&) = default;
|
||||
// RawBytes& operator=(RawBytes&&) = default;
|
||||
|
||||
RawBytes& operator=(const RawBytes&) = delete;
|
||||
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
|
||||
RawBytes& operator=(RawBytes&& mv) {
|
||||
m_buffer = std::move(mv.m_buffer);
|
||||
return *this;
|
||||
|
@ -54,28 +61,19 @@ class Raster {
|
|||
public:
|
||||
|
||||
/// Supported compression types
|
||||
enum class Compression {
|
||||
enum class Format {
|
||||
RAW, //!> Uncompressed pixel data
|
||||
PNG //!> PNG compression
|
||||
};
|
||||
|
||||
/// The Rasterizer expects the input polygons to have their coordinate
|
||||
/// system origin in the bottom left corner. If the raster is then
|
||||
/// configured with the TOP_LEFT origin parameter (in the constructor) than
|
||||
/// it will flip the Y axis in output to maintain the correct orientation.
|
||||
/// This is the default case with PNG images. They have the origin in the
|
||||
/// top left corner. Without the flipping, the image would be upside down
|
||||
/// with the scaled (clipper) coordinate system of the input polygons.
|
||||
enum class Origin {
|
||||
TOP_LEFT,
|
||||
BOTTOM_LEFT
|
||||
};
|
||||
|
||||
/// Type that represents a resolution in pixels.
|
||||
struct Resolution {
|
||||
unsigned width_px;
|
||||
unsigned height_px;
|
||||
inline Resolution(unsigned w, unsigned h): width_px(w), height_px(h) {}
|
||||
|
||||
inline Resolution(unsigned w = 0, unsigned h = 0):
|
||||
width_px(w), height_px(h) {}
|
||||
|
||||
inline unsigned pixels() const /*noexcept*/ {
|
||||
return width_px * height_px;
|
||||
}
|
||||
|
@ -85,24 +83,34 @@ public:
|
|||
struct PixelDim {
|
||||
double w_mm;
|
||||
double h_mm;
|
||||
inline PixelDim(double px_width_mm, double px_height_mm ):
|
||||
inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0):
|
||||
w_mm(px_width_mm), h_mm(px_height_mm) {}
|
||||
};
|
||||
|
||||
/// Constructor taking the resolution and the pixel dimension.
|
||||
Raster(const Resolution& r, const PixelDim& pd,
|
||||
Origin o = Origin::BOTTOM_LEFT, double gamma = 1.0);
|
||||
template <class...Args> Raster(Args...args) {
|
||||
reset(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
Raster();
|
||||
Raster(const Raster& cpy) = delete;
|
||||
Raster& operator=(const Raster& cpy) = delete;
|
||||
Raster(Raster&& m);
|
||||
Raster& operator=(Raster&&);
|
||||
~Raster();
|
||||
|
||||
/// Reallocated everything for the given resolution and pixel dimension.
|
||||
void reset(const Resolution& r, const PixelDim& pd, double gamma = 1.0);
|
||||
void reset(const Resolution& r, const PixelDim& pd, Origin o, double gamma);
|
||||
|
||||
/// The third parameter is either the X, Y mirroring or a supported format
|
||||
/// for which the correct mirroring will be configured.
|
||||
void reset(const Resolution&,
|
||||
const PixelDim&,
|
||||
const std::array<bool, 2>& mirror,
|
||||
double gamma = 1.0);
|
||||
|
||||
void reset(const Resolution& r,
|
||||
const PixelDim& pd,
|
||||
Format o,
|
||||
double gamma = 1.0);
|
||||
|
||||
/**
|
||||
* Release the allocated resources. Drawing in this state ends in
|
||||
* unspecified behavior.
|
||||
|
@ -119,11 +127,24 @@ public:
|
|||
void draw(const ExPolygon& poly);
|
||||
void draw(const ClipperLib::Polygon& poly);
|
||||
|
||||
// Saving the raster:
|
||||
// It is possible to override the format given in the constructor but
|
||||
// be aware that the mirroring will not be modified.
|
||||
|
||||
/// Save the raster on the specified stream.
|
||||
void save(std::ostream& stream, Compression comp = Compression::RAW);
|
||||
void save(std::ostream& stream, Format);
|
||||
void save(std::ostream& stream);
|
||||
|
||||
RawBytes save(Compression comp = Compression::RAW);
|
||||
/// Save into a continuous byte stream which is returned.
|
||||
RawBytes save(Format fmt);
|
||||
RawBytes save();
|
||||
};
|
||||
|
||||
}
|
||||
#endif // RASTERIZER_HPP
|
||||
// This prevents the duplicate default constructor warning on MSVC2013
|
||||
template<> Raster::Raster();
|
||||
|
||||
|
||||
} // sla
|
||||
} // Slic3r
|
||||
|
||||
#endif // SLARASTER_HPP
|
136
src/libslic3r/SLA/SLARasterWriter.cpp
Normal file
136
src/libslic3r/SLA/SLARasterWriter.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "SLARasterWriter.hpp"
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
std::string SLARasterWriter::createIniContent(const std::string& projectname) const
|
||||
{
|
||||
auto expt_str = std::to_string(m_exp_time_s);
|
||||
auto expt_first_str = std::to_string(m_exp_time_first_s);
|
||||
auto layerh_str = std::to_string(m_layer_height);
|
||||
|
||||
const std::string cnt_fade_layers = std::to_string(m_cnt_fade_layers);
|
||||
const std::string cnt_slow_layers = std::to_string(m_cnt_slow_layers);
|
||||
const std::string cnt_fast_layers = std::to_string(m_cnt_fast_layers);
|
||||
const std::string used_material = std::to_string(m_used_material);
|
||||
|
||||
return std::string(
|
||||
"action = print\n"
|
||||
"jobDir = ") + projectname + "\n" +
|
||||
"expTime = " + expt_str + "\n"
|
||||
"expTimeFirst = " + expt_first_str + "\n"
|
||||
"numFade = " + cnt_fade_layers + "\n"
|
||||
"layerHeight = " + layerh_str + "\n"
|
||||
"usedMaterial = " + used_material + "\n"
|
||||
"numSlow = " + cnt_slow_layers + "\n"
|
||||
"numFast = " + cnt_fast_layers + "\n";
|
||||
}
|
||||
|
||||
void SLARasterWriter::flpXY(ClipperLib::Polygon &poly)
|
||||
{
|
||||
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
||||
std::reverse(poly.Contour.begin(), poly.Contour.end());
|
||||
|
||||
for(auto& h : poly.Holes) {
|
||||
for(auto& p : h) std::swap(p.X, p.Y);
|
||||
std::reverse(h.begin(), h.end());
|
||||
}
|
||||
}
|
||||
|
||||
void SLARasterWriter::flpXY(ExPolygon &poly)
|
||||
{
|
||||
for(auto& p : poly.contour.points) p = Point(p.y(), p.x());
|
||||
std::reverse(poly.contour.points.begin(), poly.contour.points.end());
|
||||
|
||||
for(auto& h : poly.holes) {
|
||||
for(auto& p : h.points) p = Point(p.y(), p.x());
|
||||
std::reverse(h.points.begin(), h.points.end());
|
||||
}
|
||||
}
|
||||
|
||||
SLARasterWriter::SLARasterWriter(const SLAPrinterConfig &cfg,
|
||||
const SLAMaterialConfig &mcfg,
|
||||
double layer_height)
|
||||
{
|
||||
double w = cfg.display_width.getFloat();
|
||||
double h = cfg.display_height.getFloat();
|
||||
auto pw = unsigned(cfg.display_pixels_x.getInt());
|
||||
auto ph = unsigned(cfg.display_pixels_y.getInt());
|
||||
|
||||
m_mirror[X] = cfg.display_mirror_x.getBool();
|
||||
|
||||
// PNG raster will implicitly do an Y mirror
|
||||
m_mirror[Y] = ! cfg.display_mirror_y.getBool();
|
||||
|
||||
auto ro = cfg.display_orientation.getInt();
|
||||
|
||||
if(ro == roPortrait) {
|
||||
std::swap(w, h);
|
||||
std::swap(pw, ph);
|
||||
m_o = roPortrait;
|
||||
|
||||
// XY flipping implicitly does an X mirror
|
||||
m_mirror[X] = ! m_mirror[X];
|
||||
} else m_o = roLandscape;
|
||||
|
||||
m_res = Raster::Resolution(pw, ph);
|
||||
m_pxdim = Raster::PixelDim(w/pw, h/ph);
|
||||
m_exp_time_s = mcfg.exposure_time.getFloat();
|
||||
m_exp_time_first_s = mcfg.initial_exposure_time.getFloat();
|
||||
m_layer_height = layer_height;
|
||||
|
||||
m_gamma = cfg.gamma_correction.getFloat();
|
||||
}
|
||||
|
||||
void SLARasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||
{
|
||||
try {
|
||||
Zipper zipper(fpath); // zipper with no compression
|
||||
|
||||
std::string project = prjname.empty()?
|
||||
boost::filesystem::path(fpath).stem().string() : prjname;
|
||||
|
||||
zipper.add_entry("config.ini");
|
||||
|
||||
zipper << createIniContent(project);
|
||||
|
||||
for(unsigned i = 0; i < m_layers_rst.size(); i++)
|
||||
{
|
||||
if(m_layers_rst[i].rawbytes.size() > 0) {
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", i);
|
||||
auto zfilename = project + lyrnum + ".png";
|
||||
|
||||
// Add binary entry to the zipper
|
||||
zipper.add_entry(zfilename,
|
||||
m_layers_rst[i].rawbytes.data(),
|
||||
m_layers_rst[i].rawbytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
zipper.finalize();
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SLARasterWriter::set_statistics(const std::vector<double> statistics)
|
||||
{
|
||||
if (statistics.size() != psCnt)
|
||||
return;
|
||||
|
||||
m_used_material = statistics[psUsedMaterial];
|
||||
m_cnt_fade_layers = int(statistics[psNumFade]);
|
||||
m_cnt_slow_layers = int(statistics[psNumSlow]);
|
||||
m_cnt_fast_layers = int(statistics[psNumFast]);
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
167
src/libslic3r/SLA/SLARasterWriter.hpp
Normal file
167
src/libslic3r/SLA/SLARasterWriter.hpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
#ifndef SLARASTERWRITER_HPP
|
||||
#define SLARASTERWRITER_HPP
|
||||
|
||||
// For png export of the sliced model
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
// Implementation for PNG raster output
|
||||
// Be aware that if a large number of layers are allocated, it can very well
|
||||
// exhaust the available memory especially on 32 bit platform.
|
||||
// This class is designed to be used in parallel mode. Layers have an ID and
|
||||
// each layer can be written and compressed independently (in parallel).
|
||||
// At the end when all layers where written, the save method can be used to
|
||||
// write out the result into a zipped archive.
|
||||
class SLARasterWriter
|
||||
{
|
||||
public:
|
||||
enum RasterOrientation {
|
||||
roLandscape,
|
||||
roPortrait
|
||||
};
|
||||
|
||||
// Used for addressing parameters of set_statistics()
|
||||
enum ePrintStatistics
|
||||
{
|
||||
psUsedMaterial = 0,
|
||||
psNumFade,
|
||||
psNumSlow,
|
||||
psNumFast,
|
||||
|
||||
psCnt
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
// A struct to bind the raster image data and its compressed bytes together.
|
||||
struct Layer {
|
||||
Raster raster;
|
||||
RawBytes rawbytes;
|
||||
|
||||
Layer() = default;
|
||||
Layer(const Layer&) = delete; // The image is big, do not copy by accident
|
||||
Layer& operator=(const Layer&) = delete;
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////
|
||||
// FIXME: the following is needed for MSVC2013 compatibility
|
||||
// /////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Layer(Layer&& m) = default;
|
||||
// Layer& operator=(Layer&&) = default;
|
||||
Layer(Layer &&m):
|
||||
raster(std::move(m.raster)), rawbytes(std::move(m.rawbytes)) {}
|
||||
Layer& operator=(Layer &&m) {
|
||||
raster = std::move(m.raster); rawbytes = std::move(m.rawbytes);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// We will save the compressed PNG data into RawBytes type buffers in
|
||||
// parallel. Later we can write every layer to the disk sequentially.
|
||||
std::vector<Layer> m_layers_rst;
|
||||
Raster::Resolution m_res;
|
||||
Raster::PixelDim m_pxdim;
|
||||
double m_exp_time_s = .0, m_exp_time_first_s = .0;
|
||||
double m_layer_height = .0;
|
||||
RasterOrientation m_o = roPortrait;
|
||||
std::array<bool, 2> m_mirror;
|
||||
|
||||
double m_gamma;
|
||||
|
||||
double m_used_material = 0.0;
|
||||
int m_cnt_fade_layers = 0;
|
||||
int m_cnt_slow_layers = 0;
|
||||
int m_cnt_fast_layers = 0;
|
||||
|
||||
std::string createIniContent(const std::string& projectname) const;
|
||||
|
||||
static void flpXY(ClipperLib::Polygon& poly);
|
||||
static void flpXY(ExPolygon& poly);
|
||||
|
||||
public:
|
||||
|
||||
SLARasterWriter(const SLAPrinterConfig& cfg,
|
||||
const SLAMaterialConfig& mcfg,
|
||||
double layer_height);
|
||||
|
||||
SLARasterWriter(const SLARasterWriter& ) = delete;
|
||||
SLARasterWriter& operator=(const SLARasterWriter&) = delete;
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// FIXME: the following is needed for MSVC2013 compatibility
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// SLARasterWriter(SLARasterWriter&& m) = default;
|
||||
// SLARasterWriter& operator=(SLARasterWriter&&) = default;
|
||||
SLARasterWriter(SLARasterWriter&& m):
|
||||
m_layers_rst(std::move(m.m_layers_rst)),
|
||||
m_res(m.m_res),
|
||||
m_pxdim(m.m_pxdim),
|
||||
m_exp_time_s(m.m_exp_time_s),
|
||||
m_exp_time_first_s(m.m_exp_time_first_s),
|
||||
m_layer_height(m.m_layer_height),
|
||||
m_o(m.m_o),
|
||||
m_mirror(std::move(m.m_mirror)),
|
||||
m_gamma(m.m_gamma),
|
||||
m_used_material(m.m_used_material),
|
||||
m_cnt_fade_layers(m.m_cnt_fade_layers),
|
||||
m_cnt_slow_layers(m.m_cnt_slow_layers),
|
||||
m_cnt_fast_layers(m.m_cnt_fast_layers)
|
||||
{}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
||||
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
||||
|
||||
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr) {
|
||||
assert(lyr < m_layers_rst.size());
|
||||
if(m_o == roPortrait) {
|
||||
Poly poly(p); flpXY(poly);
|
||||
m_layers_rst[lyr].raster.draw(poly);
|
||||
}
|
||||
else m_layers_rst[lyr].raster.draw(p);
|
||||
}
|
||||
|
||||
inline void begin_layer(unsigned lyr) {
|
||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
||||
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
|
||||
}
|
||||
|
||||
inline void begin_layer() {
|
||||
m_layers_rst.emplace_back();
|
||||
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
|
||||
}
|
||||
|
||||
inline void finish_layer(unsigned lyr_id) {
|
||||
assert(lyr_id < m_layers_rst.size());
|
||||
m_layers_rst[lyr_id].rawbytes =
|
||||
m_layers_rst[lyr_id].raster.save(Raster::Format::PNG);
|
||||
m_layers_rst[lyr_id].raster.reset();
|
||||
}
|
||||
|
||||
inline void finish_layer() {
|
||||
if(!m_layers_rst.empty()) {
|
||||
m_layers_rst.back().rawbytes =
|
||||
m_layers_rst.back().raster.save(Raster::Format::PNG);
|
||||
m_layers_rst.back().raster.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void save(const std::string& fpath, const std::string& prjname = "");
|
||||
|
||||
void set_statistics(const std::vector<double> statistics);
|
||||
};
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // SLARASTERWRITER_HPP
|
|
@ -44,7 +44,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
|||
// call the status callback in each iteration but the actual value may be
|
||||
// the same for subsequent iterations (status goes from 0 to 100 but
|
||||
// iterations can be many more)
|
||||
auto objfunc = [&emesh, &status, &statuscb, max_tries]
|
||||
auto objfunc = [&emesh, &status, &statuscb, &stopcond, max_tries]
|
||||
(double rx, double ry, double rz)
|
||||
{
|
||||
EigenMesh3D& m = emesh;
|
||||
|
@ -91,7 +91,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
|||
}
|
||||
|
||||
// report status
|
||||
statuscb( unsigned(++status * 100.0/max_tries) );
|
||||
if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) );
|
||||
|
||||
return score;
|
||||
};
|
||||
|
|
|
@ -236,13 +236,13 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0})
|
|||
// According to the slicing algorithms, we need to aid them with generating
|
||||
// a watertight body. So we create a triangle fan for the upper and lower
|
||||
// ending of the cylinder to close the geometry.
|
||||
points.emplace_back(jp); size_t ci = points.size() - 1;
|
||||
points.emplace_back(jp); int ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(i + offs + 1, i + offs, ci);
|
||||
|
||||
indices.emplace_back(offs, steps + offs - 1, ci);
|
||||
|
||||
points.emplace_back(endp); ci = points.size() - 1;
|
||||
points.emplace_back(endp); ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(ci, i, i + 1);
|
||||
|
||||
|
|
|
@ -121,19 +121,10 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
|||
V.resize(3*stl.stats.number_of_facets, 3);
|
||||
F.resize(stl.stats.number_of_facets, 3);
|
||||
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
||||
const stl_facet* facet = stl.facet_start+i;
|
||||
V(3*i+0, 0) = double(facet->vertex[0](0));
|
||||
V(3*i+0, 1) = double(facet->vertex[0](1));
|
||||
V(3*i+0, 2) = double(facet->vertex[0](2));
|
||||
|
||||
V(3*i+1, 0) = double(facet->vertex[1](0));
|
||||
V(3*i+1, 1) = double(facet->vertex[1](1));
|
||||
V(3*i+1, 2) = double(facet->vertex[1](2));
|
||||
|
||||
V(3*i+2, 0) = double(facet->vertex[2](0));
|
||||
V(3*i+2, 1) = double(facet->vertex[2](1));
|
||||
V(3*i+2, 2) = double(facet->vertex[2](2));
|
||||
|
||||
const stl_facet &facet = stl.facet_start[i];
|
||||
V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast<double>();
|
||||
V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast<double>();
|
||||
V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast<double>();
|
||||
F(i, 0) = int(3*i+0);
|
||||
F(i, 1) = int(3*i+1);
|
||||
F(i, 2) = int(3*i+2);
|
||||
|
|
|
@ -28,14 +28,16 @@ namespace Slic3r {
|
|||
|
||||
using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
|
||||
|
||||
class SLAPrintObject::SupportData {
|
||||
class SLAPrintObject::SupportData
|
||||
{
|
||||
public:
|
||||
sla::EigenMesh3D emesh; // index-triangle representation
|
||||
std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto)
|
||||
SupportTreePtr support_tree_ptr; // the supports
|
||||
SlicedSupports support_slices; // sliced supports
|
||||
sla::EigenMesh3D emesh; // index-triangle representation
|
||||
std::vector<sla::SupportPoint>
|
||||
support_points; // all the support points (manual/auto)
|
||||
SupportTreePtr support_tree_ptr; // the supports
|
||||
SlicedSupports support_slices; // sliced supports
|
||||
|
||||
inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {}
|
||||
inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
@ -666,11 +668,11 @@ void SLAPrint::process()
|
|||
double ilhd = m_material_config.initial_layer_height.getFloat();
|
||||
auto ilh = float(ilhd);
|
||||
|
||||
auto ilhs = coord_t(ilhd / SCALING_FACTOR);
|
||||
auto ilhs = scaled(ilhd);
|
||||
const size_t objcount = m_objects.size();
|
||||
|
||||
const unsigned min_objstatus = 0; // where the per object operations start
|
||||
const unsigned max_objstatus = 50; // where the per object operations end
|
||||
static const unsigned min_objstatus = 0; // where the per object operations start
|
||||
static const unsigned max_objstatus = 50; // where the per object operations end
|
||||
|
||||
// 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
|
||||
|
@ -687,31 +689,32 @@ void SLAPrint::process()
|
|||
|
||||
// Slicing the model object. This method is oversimplified and needs to
|
||||
// be compared with the fff slicing algorithm for verification
|
||||
auto slice_model = [this, ilhs, ilh, ilhd](SLAPrintObject& po) {
|
||||
auto slice_model = [this, ilhs, ilh](SLAPrintObject& po) {
|
||||
const TriangleMesh& mesh = po.transformed_mesh();
|
||||
|
||||
// We need to prepare the slice index...
|
||||
|
||||
double lhd = m_objects.front()->m_config.layer_height.getFloat();
|
||||
float lh = float(lhd);
|
||||
auto lhs = coord_t(lhd / SCALING_FACTOR);
|
||||
auto lhs = scaled(lhd);
|
||||
|
||||
auto&& bb3d = mesh.bounding_box();
|
||||
double minZ = bb3d.min(Z) - po.get_elevation();
|
||||
double maxZ = bb3d.max(Z);
|
||||
auto &&bb3d = mesh.bounding_box();
|
||||
double minZ = bb3d.min(Z) - po.get_elevation();
|
||||
double maxZ = bb3d.max(Z);
|
||||
auto minZf = float(minZ);
|
||||
|
||||
auto minZs = coord_t(minZ / SCALING_FACTOR);
|
||||
auto maxZs = coord_t(maxZ / SCALING_FACTOR);
|
||||
auto minZs = scaled(minZ);
|
||||
auto maxZs = scaled(maxZ);
|
||||
|
||||
po.m_slice_index.clear();
|
||||
|
||||
size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
|
||||
po.m_slice_index.reserve(cap);
|
||||
|
||||
po.m_slice_index.emplace_back(minZs + ilhs, minZ + ilhd / 2.0, ilh);
|
||||
po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
|
||||
|
||||
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
|
||||
po.m_slice_index.emplace_back(h, h*SCALING_FACTOR - lhd / 2.0, lh);
|
||||
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
|
||||
po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh);
|
||||
|
||||
// Just get the first record that is form the model:
|
||||
auto slindex_it =
|
||||
|
@ -737,7 +740,7 @@ void SLAPrint::process()
|
|||
|
||||
auto mit = slindex_it;
|
||||
double doffs = m_printer_config.absolute_correction.getFloat();
|
||||
coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR);
|
||||
coord_t clpr_offs = scaled(doffs);
|
||||
for(size_t id = 0;
|
||||
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
|
||||
id++)
|
||||
|
@ -745,7 +748,7 @@ void SLAPrint::process()
|
|||
// We apply the printer correction offset here.
|
||||
if(clpr_offs != 0)
|
||||
po.m_model_slices[id] =
|
||||
offset_ex(po.m_model_slices[id], clpr_offs);
|
||||
offset_ex(po.m_model_slices[id], float(clpr_offs));
|
||||
|
||||
mit->set_model_slice_idx(po, id); ++mit;
|
||||
}
|
||||
|
@ -949,15 +952,15 @@ void SLAPrint::process()
|
|||
}
|
||||
|
||||
double doffs = m_printer_config.absolute_correction.getFloat();
|
||||
coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR);
|
||||
coord_t clpr_offs = scaled(doffs);
|
||||
for(size_t i = 0;
|
||||
i < sd->support_slices.size() && i < po.m_slice_index.size();
|
||||
++i)
|
||||
{
|
||||
// We apply the printer correction offset here.
|
||||
if(clpr_offs != 0)
|
||||
sd->support_slices[i] =
|
||||
offset_ex(sd->support_slices[i], clpr_offs);
|
||||
sd->support_slices[i] =
|
||||
offset_ex(sd->support_slices[i], float(clpr_offs));
|
||||
|
||||
po.m_slice_index[i].set_support_slice_idx(po, i);
|
||||
}
|
||||
|
@ -1011,7 +1014,7 @@ void SLAPrint::process()
|
|||
namespace sl = libnest2d::shapelike; // For algorithms
|
||||
|
||||
// If the raster has vertical orientation, we will flip the coordinates
|
||||
bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait;
|
||||
// bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait;
|
||||
|
||||
// Set up custom union and diff functions for clipper polygons
|
||||
auto polyunion = [] (const ClipperPolygons& subjects)
|
||||
|
@ -1063,15 +1066,15 @@ void SLAPrint::process()
|
|||
|
||||
const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20]
|
||||
|
||||
const double width = m_printer_config.display_width.getFloat() / SCALING_FACTOR;
|
||||
const double height = m_printer_config.display_height.getFloat() / SCALING_FACTOR;
|
||||
const double width = scaled(m_printer_config.display_width.getFloat());
|
||||
const double height = scaled(m_printer_config.display_height.getFloat());
|
||||
const double display_area = width*height;
|
||||
|
||||
// get polygons for all instances in the object
|
||||
auto get_all_polygons =
|
||||
[flpXY](const ExPolygons& input_polygons,
|
||||
const std::vector<SLAPrintObject::Instance>& instances,
|
||||
bool is_lefthanded)
|
||||
[](const ExPolygons& input_polygons,
|
||||
const std::vector<SLAPrintObject::Instance>& instances,
|
||||
bool is_lefthanded)
|
||||
{
|
||||
ClipperPolygons polygons;
|
||||
polygons.reserve(input_polygons.size() * instances.size());
|
||||
|
@ -1085,7 +1088,7 @@ void SLAPrint::process()
|
|||
|
||||
// We need to reverse if flpXY OR is_lefthanded is true but
|
||||
// not if both are true which is a logical inequality (XOR)
|
||||
bool needreverse = flpXY != is_lefthanded;
|
||||
bool needreverse = /*flpXY !=*/ is_lefthanded;
|
||||
|
||||
// should be a move
|
||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||
|
@ -1120,10 +1123,10 @@ void SLAPrint::process()
|
|||
sl::translate(poly, ClipperPoint{instances[i].shift(X),
|
||||
instances[i].shift(Y)});
|
||||
|
||||
if (flpXY) {
|
||||
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
||||
for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y);
|
||||
}
|
||||
// if (flpXY) {
|
||||
// for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
||||
// for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y);
|
||||
// }
|
||||
|
||||
polygons.emplace_back(std::move(poly));
|
||||
}
|
||||
|
@ -1170,13 +1173,20 @@ void SLAPrint::process()
|
|||
ClipperPolygons model_polygons;
|
||||
ClipperPolygons supports_polygons;
|
||||
|
||||
size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
size_t c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel)
|
||||
.size();
|
||||
});
|
||||
|
||||
model_polygons.reserve(c);
|
||||
|
||||
c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) {
|
||||
c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
|
@ -1264,8 +1274,9 @@ void SLAPrint::process()
|
|||
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
|
||||
tbb::parallel_for<size_t, decltype(printlayerfn)>(0, m_printer_input.size(), printlayerfn);
|
||||
|
||||
m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR;
|
||||
m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR;
|
||||
auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
|
||||
m_print_statistics.support_used_material = supports_volume * SCALING2;
|
||||
m_print_statistics.objects_used_material = models_volume * SCALING2;
|
||||
|
||||
// Estimated printing time
|
||||
// A layers count o the highest object
|
||||
|
@ -1281,38 +1292,14 @@ void SLAPrint::process()
|
|||
};
|
||||
|
||||
// Rasterizing the model objects, and their supports
|
||||
auto rasterize = [this, max_objstatus]() {
|
||||
auto rasterize = [this]() {
|
||||
if(canceled()) return;
|
||||
|
||||
// collect all the keys
|
||||
|
||||
// If the raster has vertical orientation, we will flip the coordinates
|
||||
bool flpXY = m_printer_config.display_orientation.getInt() ==
|
||||
SLADisplayOrientation::sladoPortrait;
|
||||
|
||||
{ // create a raster printer for the current print parameters
|
||||
// I don't know any better
|
||||
auto& ocfg = m_objects.front()->m_config;
|
||||
auto& matcfg = m_material_config;
|
||||
auto& printcfg = m_printer_config;
|
||||
|
||||
double w = printcfg.display_width.getFloat();
|
||||
double h = printcfg.display_height.getFloat();
|
||||
auto pw = unsigned(printcfg.display_pixels_x.getInt());
|
||||
auto ph = unsigned(printcfg.display_pixels_y.getInt());
|
||||
double lh = ocfg.layer_height.getFloat();
|
||||
double exp_t = matcfg.exposure_time.getFloat();
|
||||
double iexp_t = matcfg.initial_exposure_time.getFloat();
|
||||
|
||||
double gamma = m_printer_config.gamma_correction.getFloat();
|
||||
|
||||
if(flpXY) { std::swap(w, h); std::swap(pw, ph); }
|
||||
|
||||
m_printer.reset(
|
||||
new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t,
|
||||
flpXY? SLAPrinter::RO_PORTRAIT :
|
||||
SLAPrinter::RO_LANDSCAPE,
|
||||
gamma));
|
||||
double layerh = m_default_object_config.layer_height.getFloat();
|
||||
m_printer.reset(new SLAPrinter(m_printer_config,
|
||||
m_material_config,
|
||||
layerh));
|
||||
}
|
||||
|
||||
// Allocate space for all the layers
|
||||
|
@ -1376,11 +1363,12 @@ void SLAPrint::process()
|
|||
tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn);
|
||||
|
||||
// Set statistics values to the printer
|
||||
m_printer->set_statistics({(m_print_statistics.objects_used_material + m_print_statistics.support_used_material)/1000,
|
||||
double(m_default_object_config.faded_layers.getInt()),
|
||||
double(m_print_statistics.slow_layers_count),
|
||||
double(m_print_statistics.fast_layers_count)
|
||||
});
|
||||
m_printer->set_statistics(
|
||||
{(m_print_statistics.objects_used_material
|
||||
+ m_print_statistics.support_used_material) / 1000,
|
||||
double(m_default_object_config.faded_layers.getInt()),
|
||||
double(m_print_statistics.slow_layers_count),
|
||||
double(m_print_statistics.fast_layers_count)});
|
||||
};
|
||||
|
||||
using slaposFn = std::function<void(SLAPrintObject&)>;
|
||||
|
@ -1408,25 +1396,36 @@ void SLAPrint::process()
|
|||
|
||||
// TODO: this loop could run in parallel but should not exhaust all the CPU
|
||||
// power available
|
||||
// Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects.
|
||||
std::vector<SLAPrintObjectStep> step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount };
|
||||
for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) {
|
||||
for(SLAPrintObject * po : m_objects) {
|
||||
// Calculate the support structures first before slicing the supports,
|
||||
// so that the preview will get displayed ASAP for all objects.
|
||||
std::vector<SLAPrintObjectStep> step_ranges = {slaposObjectSlice,
|
||||
slaposSliceSupports,
|
||||
slaposCount};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name;
|
||||
for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++idx_range) {
|
||||
for (SLAPrintObject *po : m_objects) {
|
||||
|
||||
for (int s = int(step_ranges[idx_range]); s < int(step_ranges[idx_range + 1]); ++s) {
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< "Slicing object " << po->model_object()->name;
|
||||
|
||||
for (int s = int(step_ranges[idx_range]);
|
||||
s < int(step_ranges[idx_range + 1]);
|
||||
++s) {
|
||||
auto currentstep = static_cast<SLAPrintObjectStep>(s);
|
||||
|
||||
// Cancellation checking. Each step will check for cancellation
|
||||
// on its own and return earlier gracefully. Just after it returns
|
||||
// execution gets to this point and throws the canceled signal.
|
||||
// Cancellation checking. Each step will check for
|
||||
// cancellation on its own and return earlier gracefully.
|
||||
// Just after it returns execution gets to this point and
|
||||
// throws the canceled signal.
|
||||
throw_if_canceled();
|
||||
|
||||
st += incr * ostepd;
|
||||
|
||||
if(po->m_stepmask[currentstep] && po->set_started(currentstep)) {
|
||||
m_report_status(*this, st, OBJ_STEP_LABELS(currentstep));
|
||||
if (po->m_stepmask[currentstep]
|
||||
&& po->set_started(currentstep)) {
|
||||
m_report_status(*this,
|
||||
st,
|
||||
OBJ_STEP_LABELS(currentstep));
|
||||
pobj_program[currentstep](*po);
|
||||
throw_if_canceled();
|
||||
po->set_done(currentstep);
|
||||
|
@ -1488,6 +1487,8 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
|||
"display_height",
|
||||
"display_pixels_x",
|
||||
"display_pixels_y",
|
||||
"display_mirror_x",
|
||||
"display_mirror_y",
|
||||
"display_orientation"
|
||||
};
|
||||
|
||||
|
@ -1786,8 +1787,8 @@ std::vector<sla::SupportPoint> SLAPrintObject::transformed_support_points() cons
|
|||
ret.reserve(spts.size());
|
||||
|
||||
for(sla::SupportPoint& sp : spts) {
|
||||
Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2));
|
||||
ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island);
|
||||
Vec3f transformed_pos = trafo().cast<float>() * sp.pos;
|
||||
ret.emplace_back(transformed_pos, sp.head_front_radius, sp.is_new_island);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
#include <mutex>
|
||||
#include "PrintBase.hpp"
|
||||
#include "PrintExport.hpp"
|
||||
//#include "PrintExport.hpp"
|
||||
#include "SLA/SLARasterWriter.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
#include "Zipper.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -54,15 +54,15 @@ public:
|
|||
bool is_left_handed() const { return m_left_handed; }
|
||||
|
||||
struct Instance {
|
||||
Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {}
|
||||
bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; }
|
||||
// ID of the corresponding ModelInstance.
|
||||
ModelID instance_id;
|
||||
// Slic3r::Point objects in scaled G-code coordinates
|
||||
Point shift;
|
||||
// Rotation along the Z axis, in radians.
|
||||
float rotation;
|
||||
};
|
||||
Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {}
|
||||
bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; }
|
||||
// ID of the corresponding ModelInstance.
|
||||
ModelID instance_id;
|
||||
// Slic3r::Point objects in scaled G-code coordinates
|
||||
Point shift;
|
||||
// Rotation along the Z axis, in radians.
|
||||
float rotation;
|
||||
};
|
||||
const std::vector<Instance>& instances() const { return m_instances; }
|
||||
|
||||
bool has_mesh(SLAPrintObjectStep step) const;
|
||||
|
@ -142,15 +142,19 @@ public:
|
|||
};
|
||||
|
||||
private:
|
||||
|
||||
template <class T> inline static T level(const SliceRecord& sr) {
|
||||
template<class T> inline static T level(const SliceRecord &sr)
|
||||
{
|
||||
static_assert(std::is_arithmetic<T>::value, "Arithmetic only!");
|
||||
return std::is_integral<T>::value ? T(sr.print_level()) : T(sr.slice_level());
|
||||
return std::is_integral<T>::value ? T(sr.print_level())
|
||||
: T(sr.slice_level());
|
||||
}
|
||||
|
||||
template <class T> inline static SliceRecord create_slice_record(T val) {
|
||||
template<class T> inline static SliceRecord create_slice_record(T val)
|
||||
{
|
||||
static_assert(std::is_arithmetic<T>::value, "Arithmetic only!");
|
||||
return std::is_integral<T>::value ? SliceRecord{ coord_t(val), 0.f, 0.f } : SliceRecord{ 0, float(val), 0.f };
|
||||
return std::is_integral<T>::value
|
||||
? SliceRecord{coord_t(val), 0.f, 0.f}
|
||||
: SliceRecord{0, float(val), 0.f};
|
||||
}
|
||||
|
||||
// This is a template method for searching the slice index either by
|
||||
|
@ -241,11 +245,11 @@ protected:
|
|||
~SLAPrintObject();
|
||||
|
||||
void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); }
|
||||
void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false)
|
||||
{ this->m_config.apply_only(other, keys, ignore_nonexistent); }
|
||||
void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false)
|
||||
{ this->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_transformed_rmesh.invalidate([this, &trafo, 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); }
|
||||
|
@ -322,37 +326,6 @@ struct SLAPrintStatistics
|
|||
}
|
||||
};
|
||||
|
||||
// The implementation of creating zipped archives with wxWidgets
|
||||
template<> class LayerWriter<Zipper> {
|
||||
Zipper m_zip;
|
||||
public:
|
||||
|
||||
LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {}
|
||||
|
||||
void next_entry(const std::string& fname) { m_zip.add_entry(fname); }
|
||||
|
||||
void binary_entry(const std::string& fname,
|
||||
const std::uint8_t* buf,
|
||||
size_t l)
|
||||
{
|
||||
m_zip.add_entry(fname, buf, l);
|
||||
}
|
||||
|
||||
template<class T> inline LayerWriter& operator<<(T&& arg) {
|
||||
m_zip << std::forward<T>(arg); return *this;
|
||||
}
|
||||
|
||||
bool is_ok() const {
|
||||
return true; // m_zip blows up if something goes wrong...
|
||||
}
|
||||
|
||||
// After finalize, no writing to the archive will have an effect. The only
|
||||
// valid operation is to dispose the object calling the destructor which
|
||||
// should close the file. This method can throw and signal potential errors
|
||||
// when flushing the archive. This is why its present.
|
||||
void finalize() { m_zip.finalize(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class is the high level FSM for the SLA printing process.
|
||||
*
|
||||
|
@ -380,16 +353,15 @@ public:
|
|||
void set_task(const TaskParams ¶ms) override;
|
||||
void process() override;
|
||||
void finalize() override;
|
||||
// Returns true if an object step is done on all objects and there's at least one object.
|
||||
// Returns true if an object step is done on all objects and there's at least one object.
|
||||
bool is_step_done(SLAPrintObjectStep step) const;
|
||||
// Returns true if the last step was finished with success.
|
||||
bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); }
|
||||
|
||||
template<class Fmt = Zipper>
|
||||
inline void export_raster(const std::string& fpath,
|
||||
const std::string& projectname = "")
|
||||
const std::string& projectname = "")
|
||||
{
|
||||
if(m_printer) m_printer->save<Fmt>(fpath, projectname);
|
||||
if(m_printer) m_printer->save(fpath, projectname);
|
||||
}
|
||||
|
||||
const PrintObjects& objects() const { return m_objects; }
|
||||
|
@ -450,7 +422,7 @@ public:
|
|||
const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
|
||||
|
||||
private:
|
||||
using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>;
|
||||
using SLAPrinter = sla::SLARasterWriter;
|
||||
using SLAPrinterPtr = std::unique_ptr<SLAPrinter>;
|
||||
|
||||
// Implement same logic as in SLAPrintObject
|
||||
|
|
|
@ -227,7 +227,7 @@ std::vector<coordf_t> layer_height_profile_adaptive(
|
|||
as.set_slicing_parameters(slicing_params);
|
||||
for (const ModelVolume *volume : volumes)
|
||||
if (volume->is_model_part())
|
||||
as.add_mesh(&volume->mesh);
|
||||
as.add_mesh(&volume->mesh());
|
||||
as.prepare();
|
||||
|
||||
// 2) Generate layers using the algorithm of @platsch
|
||||
|
|
|
@ -27,8 +27,8 @@ void SlicingAdaptive::prepare()
|
|||
nfaces_total += (*it_mesh)->stl.stats.number_of_facets;
|
||||
m_faces.reserve(nfaces_total);
|
||||
for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
|
||||
for (int i = 0; i < (*it_mesh)->stl.stats.number_of_facets; ++ i)
|
||||
m_faces.push_back((*it_mesh)->stl.facet_start + i);
|
||||
for (const stl_facet &face : (*it_mesh)->stl.facet_start)
|
||||
m_faces.emplace_back(&face);
|
||||
|
||||
// 2) Sort faces lexicographically by their Z span.
|
||||
std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) {
|
||||
|
|
|
@ -42,20 +42,17 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets)
|
||||
: repaired(false)
|
||||
TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets) : repaired(false)
|
||||
{
|
||||
stl_initialize(&this->stl);
|
||||
stl_file &stl = this->stl;
|
||||
stl.error = 0;
|
||||
stl.stats.type = inmemory;
|
||||
|
||||
// count facets and allocate memory
|
||||
stl.stats.number_of_facets = facets.size();
|
||||
stl.stats.number_of_facets = (uint32_t)facets.size();
|
||||
stl.stats.original_num_facets = stl.stats.number_of_facets;
|
||||
stl_allocate(&stl);
|
||||
|
||||
for (uint32_t i = 0; i < stl.stats.number_of_facets; i++) {
|
||||
for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) {
|
||||
stl_facet facet;
|
||||
facet.vertex[0] = points[facets[i](0)].cast<float>();
|
||||
facet.vertex[1] = points[facets[i](1)].cast<float>();
|
||||
|
@ -73,57 +70,37 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& f
|
|||
stl_get_size(&stl);
|
||||
}
|
||||
|
||||
TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other)
|
||||
{
|
||||
stl_close(&this->stl);
|
||||
this->stl = other.stl;
|
||||
this->repaired = other.repaired;
|
||||
this->stl.heads = nullptr;
|
||||
this->stl.tail = nullptr;
|
||||
this->stl.error = other.stl.error;
|
||||
if (other.stl.facet_start != nullptr) {
|
||||
this->stl.facet_start = (stl_facet*)calloc(other.stl.stats.number_of_facets, sizeof(stl_facet));
|
||||
std::copy(other.stl.facet_start, other.stl.facet_start + other.stl.stats.number_of_facets, this->stl.facet_start);
|
||||
}
|
||||
if (other.stl.neighbors_start != nullptr) {
|
||||
this->stl.neighbors_start = (stl_neighbors*)calloc(other.stl.stats.number_of_facets, sizeof(stl_neighbors));
|
||||
std::copy(other.stl.neighbors_start, other.stl.neighbors_start + other.stl.stats.number_of_facets, this->stl.neighbors_start);
|
||||
}
|
||||
if (other.stl.v_indices != nullptr) {
|
||||
this->stl.v_indices = (v_indices_struct*)calloc(other.stl.stats.number_of_facets, sizeof(v_indices_struct));
|
||||
std::copy(other.stl.v_indices, other.stl.v_indices + other.stl.stats.number_of_facets, this->stl.v_indices);
|
||||
}
|
||||
if (other.stl.v_shared != nullptr) {
|
||||
this->stl.v_shared = (stl_vertex*)calloc(other.stl.stats.shared_vertices, sizeof(stl_vertex));
|
||||
std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// #define SLIC3R_TRACE_REPAIR
|
||||
|
||||
void TriangleMesh::repair()
|
||||
void TriangleMesh::repair(bool update_shared_vertices)
|
||||
{
|
||||
if (this->repaired) return;
|
||||
|
||||
if (this->repaired) {
|
||||
if (update_shared_vertices)
|
||||
this->require_shared_vertices();
|
||||
return;
|
||||
}
|
||||
|
||||
// admesh fails when repairing empty meshes
|
||||
if (this->stl.stats.number_of_facets == 0) return;
|
||||
if (this->stl.stats.number_of_facets == 0)
|
||||
return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started";
|
||||
|
||||
|
||||
// checking exact
|
||||
#ifdef SLIC3R_TRACE_REPAIR
|
||||
BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact";
|
||||
#endif /* SLIC3R_TRACE_REPAIR */
|
||||
assert(stl_validate(&this->stl));
|
||||
stl_check_facets_exact(&stl);
|
||||
assert(stl_validate(&this->stl));
|
||||
stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge);
|
||||
stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge);
|
||||
stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge);
|
||||
|
||||
// checking nearby
|
||||
//int last_edges_fixed = 0;
|
||||
float tolerance = stl.stats.shortest_edge;
|
||||
float increment = stl.stats.bounding_diameter / 10000.0;
|
||||
float tolerance = (float)stl.stats.shortest_edge;
|
||||
float increment = (float)stl.stats.bounding_diameter / 10000.0f;
|
||||
int iterations = 2;
|
||||
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
|
@ -141,6 +118,7 @@ void TriangleMesh::repair()
|
|||
}
|
||||
}
|
||||
}
|
||||
assert(stl_validate(&this->stl));
|
||||
|
||||
// remove_unconnected
|
||||
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
||||
|
@ -148,6 +126,7 @@ void TriangleMesh::repair()
|
|||
BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets";
|
||||
#endif /* SLIC3R_TRACE_REPAIR */
|
||||
stl_remove_unconnected_facets(&stl);
|
||||
assert(stl_validate(&this->stl));
|
||||
}
|
||||
|
||||
// fill_holes
|
||||
|
@ -168,28 +147,38 @@ void TriangleMesh::repair()
|
|||
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions";
|
||||
#endif /* SLIC3R_TRACE_REPAIR */
|
||||
stl_fix_normal_directions(&stl);
|
||||
assert(stl_validate(&this->stl));
|
||||
|
||||
// normal_values
|
||||
#ifdef SLIC3R_TRACE_REPAIR
|
||||
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values";
|
||||
#endif /* SLIC3R_TRACE_REPAIR */
|
||||
stl_fix_normal_values(&stl);
|
||||
assert(stl_validate(&this->stl));
|
||||
|
||||
// always calculate the volume and reverse all normals if volume is negative
|
||||
#ifdef SLIC3R_TRACE_REPAIR
|
||||
BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume";
|
||||
#endif /* SLIC3R_TRACE_REPAIR */
|
||||
stl_calculate_volume(&stl);
|
||||
assert(stl_validate(&this->stl));
|
||||
|
||||
// neighbors
|
||||
#ifdef SLIC3R_TRACE_REPAIR
|
||||
BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors";
|
||||
#endif /* SLIC3R_TRACE_REPAIR */
|
||||
stl_verify_neighbors(&stl);
|
||||
assert(stl_validate(&this->stl));
|
||||
|
||||
this->repaired = true;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
|
||||
|
||||
// This call should be quite cheap, a lot of code requires the indexed_triangle_set data structure,
|
||||
// and it is risky to generate such a structure once the meshes are shared. Do it now.
|
||||
this->its.clear();
|
||||
if (update_shared_vertices)
|
||||
this->require_shared_vertices();
|
||||
}
|
||||
|
||||
float TriangleMesh::volume()
|
||||
|
@ -249,20 +238,24 @@ bool TriangleMesh::needed_repair() const
|
|||
|
||||
void TriangleMesh::WriteOBJFile(const char* output_file)
|
||||
{
|
||||
stl_generate_shared_vertices(&stl);
|
||||
stl_write_obj(&stl, output_file);
|
||||
its_write_obj(this->its, output_file);
|
||||
}
|
||||
|
||||
void TriangleMesh::scale(float factor)
|
||||
{
|
||||
stl_scale(&(this->stl), factor);
|
||||
stl_invalidate_shared_vertices(&this->stl);
|
||||
for (stl_vertex& v : this->its.vertices)
|
||||
v *= factor;
|
||||
}
|
||||
|
||||
void TriangleMesh::scale(const Vec3d &versor)
|
||||
{
|
||||
stl_scale_versor(&this->stl, versor.cast<float>());
|
||||
stl_invalidate_shared_vertices(&this->stl);
|
||||
for (stl_vertex& v : this->its.vertices) {
|
||||
v.x() *= versor.x();
|
||||
v.y() *= versor.y();
|
||||
v.z() *= versor.z();
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleMesh::translate(float x, float y, float z)
|
||||
|
@ -270,7 +263,9 @@ void TriangleMesh::translate(float x, float y, float z)
|
|||
if (x == 0.f && y == 0.f && z == 0.f)
|
||||
return;
|
||||
stl_translate_relative(&(this->stl), x, y, z);
|
||||
stl_invalidate_shared_vertices(&this->stl);
|
||||
stl_vertex shift(x, y, z);
|
||||
for (stl_vertex& v : this->its.vertices)
|
||||
v += shift;
|
||||
}
|
||||
|
||||
void TriangleMesh::translate(const Vec3f &displacement)
|
||||
|
@ -287,13 +282,15 @@ void TriangleMesh::rotate(float angle, const Axis &axis)
|
|||
angle = Slic3r::Geometry::rad2deg(angle);
|
||||
|
||||
if (axis == X) {
|
||||
stl_rotate_x(&(this->stl), angle);
|
||||
stl_rotate_x(&this->stl, angle);
|
||||
its_rotate_x(this->its, angle);
|
||||
} else if (axis == Y) {
|
||||
stl_rotate_y(&(this->stl), angle);
|
||||
stl_rotate_y(&this->stl, angle);
|
||||
its_rotate_y(this->its, angle);
|
||||
} else if (axis == Z) {
|
||||
stl_rotate_z(&(this->stl), angle);
|
||||
stl_rotate_z(&this->stl, angle);
|
||||
its_rotate_z(this->its, angle);
|
||||
}
|
||||
stl_invalidate_shared_vertices(&this->stl);
|
||||
}
|
||||
|
||||
void TriangleMesh::rotate(float angle, const Vec3d& axis)
|
||||
|
@ -305,39 +302,49 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis)
|
|||
Transform3d m = Transform3d::Identity();
|
||||
m.rotate(Eigen::AngleAxisd(angle, axis_norm));
|
||||
stl_transform(&stl, m);
|
||||
its_transform(its, m);
|
||||
}
|
||||
|
||||
void TriangleMesh::mirror(const Axis &axis)
|
||||
{
|
||||
if (axis == X) {
|
||||
stl_mirror_yz(&this->stl);
|
||||
for (stl_vertex &v : this->its.vertices)
|
||||
v(0) *= -1.0;
|
||||
} else if (axis == Y) {
|
||||
stl_mirror_xz(&this->stl);
|
||||
for (stl_vertex &v : this->its.vertices)
|
||||
v(1) *= -1.0;
|
||||
} else if (axis == Z) {
|
||||
stl_mirror_xy(&this->stl);
|
||||
for (stl_vertex &v : this->its.vertices)
|
||||
v(2) *= -1.0;
|
||||
}
|
||||
stl_invalidate_shared_vertices(&this->stl);
|
||||
}
|
||||
|
||||
void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed)
|
||||
{
|
||||
stl_transform(&stl, t);
|
||||
stl_invalidate_shared_vertices(&stl);
|
||||
its_transform(its, t);
|
||||
if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) {
|
||||
// Left handed transformation is being applied. It is a good idea to flip the faces and their normals.
|
||||
this->repair();
|
||||
this->repair(false);
|
||||
stl_reverse_all_facets(&stl);
|
||||
this->its.clear();
|
||||
this->require_shared_vertices();
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed)
|
||||
{
|
||||
stl_transform(&stl, m);
|
||||
stl_invalidate_shared_vertices(&stl);
|
||||
its_transform(its, m);
|
||||
if (fix_left_handed && m.determinant() < 0.) {
|
||||
// Left handed transformation is being applied. It is a good idea to flip the faces and their normals.
|
||||
this->repair();
|
||||
this->repair(false);
|
||||
stl_reverse_all_facets(&stl);
|
||||
this->its.clear();
|
||||
this->require_shared_vertices();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,7 +362,8 @@ void TriangleMesh::rotate(double angle, Point* center)
|
|||
return;
|
||||
Vec2f c = center->cast<float>();
|
||||
this->translate(-c(0), -c(1), 0);
|
||||
stl_rotate_z(&(this->stl), (float)angle);
|
||||
stl_rotate_z(&this->stl, (float)angle);
|
||||
its_rotate_z(this->its, (float)angle);
|
||||
this->translate(c(0), c(1), 0);
|
||||
}
|
||||
|
||||
|
@ -435,9 +443,8 @@ TriangleMeshPtrs TriangleMesh::split() const
|
|||
TriangleMesh* mesh = new TriangleMesh;
|
||||
meshes.emplace_back(mesh);
|
||||
mesh->stl.stats.type = inmemory;
|
||||
mesh->stl.stats.number_of_facets = facets.size();
|
||||
mesh->stl.stats.number_of_facets = (uint32_t)facets.size();
|
||||
mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets;
|
||||
stl_clear_error(&mesh->stl);
|
||||
stl_allocate(&mesh->stl);
|
||||
|
||||
// Assign the facets to the new mesh.
|
||||
|
@ -455,7 +462,7 @@ void TriangleMesh::merge(const TriangleMesh &mesh)
|
|||
{
|
||||
// reset stats and metadata
|
||||
int number_of_facets = this->stl.stats.number_of_facets;
|
||||
stl_invalidate_shared_vertices(&this->stl);
|
||||
this->its.clear();
|
||||
this->repaired = false;
|
||||
|
||||
// update facet count and allocate more memory
|
||||
|
@ -477,13 +484,12 @@ ExPolygons TriangleMesh::horizontal_projection() const
|
|||
{
|
||||
Polygons pp;
|
||||
pp.reserve(this->stl.stats.number_of_facets);
|
||||
for (uint32_t i = 0; i < this->stl.stats.number_of_facets; ++ i) {
|
||||
stl_facet* facet = &this->stl.facet_start[i];
|
||||
for (const stl_facet &facet : this->stl.facet_start) {
|
||||
Polygon p;
|
||||
p.points.resize(3);
|
||||
p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](1));
|
||||
p.points[1] = Point::new_scale(facet->vertex[1](0), facet->vertex[1](1));
|
||||
p.points[2] = Point::new_scale(facet->vertex[2](0), facet->vertex[2](1));
|
||||
p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1));
|
||||
p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1));
|
||||
p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1));
|
||||
p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that
|
||||
pp.emplace_back(p);
|
||||
}
|
||||
|
@ -495,11 +501,10 @@ ExPolygons TriangleMesh::horizontal_projection() const
|
|||
// 2D convex hull of a 3D mesh projected into the Z=0 plane.
|
||||
Polygon TriangleMesh::convex_hull()
|
||||
{
|
||||
this->require_shared_vertices();
|
||||
Points pp;
|
||||
pp.reserve(this->stl.stats.shared_vertices);
|
||||
for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) {
|
||||
const stl_vertex &v = this->stl.v_shared[i];
|
||||
pp.reserve(this->its.vertices.size());
|
||||
for (size_t i = 0; i < this->its.vertices.size(); ++ i) {
|
||||
const stl_vertex &v = this->its.vertices[i];
|
||||
pp.emplace_back(Point::new_scale(v(0), v(1)));
|
||||
}
|
||||
return Slic3r::Geometry::convex_hull(pp);
|
||||
|
@ -517,49 +522,47 @@ BoundingBoxf3 TriangleMesh::bounding_box() const
|
|||
BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const
|
||||
{
|
||||
BoundingBoxf3 bbox;
|
||||
if (stl.v_shared == nullptr) {
|
||||
if (this->its.vertices.empty()) {
|
||||
// Using the STL faces.
|
||||
for (size_t i = 0; i < this->facets_count(); ++ i) {
|
||||
const stl_facet &facet = this->stl.facet_start[i];
|
||||
for (const stl_facet &facet : this->stl.facet_start)
|
||||
for (size_t j = 0; j < 3; ++ j)
|
||||
bbox.merge(trafo * facet.vertex[j].cast<double>());
|
||||
}
|
||||
} else {
|
||||
// Using the shared vertices should be a bit quicker than using the STL faces.
|
||||
for (int i = 0; i < stl.stats.shared_vertices; ++ i)
|
||||
bbox.merge(trafo * this->stl.v_shared[i].cast<double>());
|
||||
for (const stl_vertex &v : this->its.vertices)
|
||||
bbox.merge(trafo * v.cast<double>());
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
TriangleMesh TriangleMesh::convex_hull_3d() const
|
||||
{
|
||||
// Helper struct for qhull:
|
||||
struct PointForQHull{
|
||||
PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {}
|
||||
realT x, y, z;
|
||||
};
|
||||
std::vector<PointForQHull> src_vertices;
|
||||
|
||||
// We will now fill the vector with input points for computation:
|
||||
stl_facet* facet_ptr = stl.facet_start;
|
||||
while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
|
||||
{
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
const stl_vertex& v = facet_ptr->vertex[i];
|
||||
src_vertices.emplace_back(v(0), v(1), v(2));
|
||||
}
|
||||
|
||||
facet_ptr += 1;
|
||||
}
|
||||
|
||||
// The qhull call:
|
||||
orgQhull::Qhull qhull;
|
||||
qhull.disableOutputStream(); // we want qhull to be quiet
|
||||
try
|
||||
std::vector<realT> src_vertices;
|
||||
try
|
||||
{
|
||||
qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt");
|
||||
if (this->has_shared_vertices()) {
|
||||
#if REALfloat
|
||||
qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt");
|
||||
#else
|
||||
src_vertices.reserve(this->its.vertices() * 3);
|
||||
// We will now fill the vector with input points for computation:
|
||||
for (const stl_vertex &v : ths->its.vertices.size())
|
||||
for (int i = 0; i < 3; ++ i)
|
||||
src_vertices.emplace_back(v(i));
|
||||
qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt");
|
||||
#endif
|
||||
} else {
|
||||
src_vertices.reserve(this->stl.facet_start.size() * 9);
|
||||
// We will now fill the vector with input points for computation:
|
||||
for (const stl_facet &f : this->stl.facet_start)
|
||||
for (int i = 0; i < 3; ++ i)
|
||||
for (int j = 0; j < 3; ++ j)
|
||||
src_vertices.emplace_back(f.vertex[i](j));
|
||||
qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
@ -587,34 +590,20 @@ TriangleMesh TriangleMesh::convex_hull_3d() const
|
|||
|
||||
TriangleMesh output_mesh(dst_vertices, facets);
|
||||
output_mesh.repair();
|
||||
output_mesh.require_shared_vertices();
|
||||
return output_mesh;
|
||||
}
|
||||
|
||||
void TriangleMesh::require_shared_vertices()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start";
|
||||
if (!this->repaired)
|
||||
assert(stl_validate(&this->stl));
|
||||
if (! this->repaired)
|
||||
this->repair();
|
||||
if (this->stl.v_shared == NULL) {
|
||||
if (this->its.vertices.empty()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices";
|
||||
stl_generate_shared_vertices(&(this->stl));
|
||||
stl_generate_shared_vertices(&this->stl, this->its);
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// Verify validity of neighborship data.
|
||||
for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) {
|
||||
const stl_neighbors &nbr = stl.neighbors_start[facet_idx];
|
||||
const int *vertices = stl.v_indices[facet_idx].vertex;
|
||||
for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) {
|
||||
int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx];
|
||||
if (nbr_face != -1) {
|
||||
assert(
|
||||
(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]) ||
|
||||
(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[nbr_idx]));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* _DEBUG */
|
||||
assert(stl_validate(&this->stl, this->its));
|
||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
|
||||
}
|
||||
|
||||
|
@ -626,10 +615,9 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac
|
|||
|
||||
throw_on_cancel();
|
||||
facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1);
|
||||
v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices);
|
||||
// Scale the copied vertices.
|
||||
for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i)
|
||||
this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR);
|
||||
v_scaled_shared.assign(_mesh->its.vertices.size(), stl_vertex());
|
||||
for (size_t i = 0; i < v_scaled_shared.size(); ++ i)
|
||||
this->v_scaled_shared[i] = _mesh->its.vertices[i] / float(SCALING_FACTOR);
|
||||
|
||||
// Create a mapping from triangle edge into face.
|
||||
struct EdgeToFace {
|
||||
|
@ -649,8 +637,8 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac
|
|||
for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx)
|
||||
for (int i = 0; i < 3; ++ i) {
|
||||
EdgeToFace &e2f = edges_map[facet_idx*3+i];
|
||||
e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i];
|
||||
e2f.vertex_high = this->mesh->stl.v_indices[facet_idx].vertex[(i + 1) % 3];
|
||||
e2f.vertex_low = this->mesh->its.indices[facet_idx][i];
|
||||
e2f.vertex_high = this->mesh->its.indices[facet_idx][(i + 1) % 3];
|
||||
e2f.face = facet_idx;
|
||||
// 1 based indexing, to be always strictly positive.
|
||||
e2f.face_edge = i + 1;
|
||||
|
@ -818,7 +806,7 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
|
|||
void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex,
|
||||
const std::vector<float> &z) const
|
||||
{
|
||||
const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx];
|
||||
const stl_facet &facet = m_use_quaternion ? (this->mesh->stl.facet_start.data() + facet_idx)->rotated(m_quaternion) : *(this->mesh->stl.facet_start.data() + facet_idx);
|
||||
|
||||
// find facet extents
|
||||
const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
|
||||
|
@ -887,7 +875,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
|
|||
// Reorder vertices so that the first one is the one with lowest Z.
|
||||
// This is needed to get all intersection lines in a consistent order
|
||||
// (external on the right of the line)
|
||||
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
|
||||
const stl_triangle_vertex_indices &vertices = this->mesh->its.indices[facet_idx];
|
||||
int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0);
|
||||
|
||||
// These are used only if the cut plane is tilted:
|
||||
|
@ -1714,7 +1702,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
|||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object";
|
||||
float scaled_z = scale_(z);
|
||||
for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) {
|
||||
stl_facet* facet = &this->mesh->stl.facet_start[facet_idx];
|
||||
const stl_facet* facet = &this->mesh->stl.facet_start[facet_idx];
|
||||
|
||||
// find facet extents
|
||||
float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2)));
|
||||
|
@ -1736,10 +1724,12 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
|||
|
||||
if (min_z > z || (min_z == z && max_z > z)) {
|
||||
// facet is above the cut plane and does not belong to it
|
||||
if (upper != NULL) stl_add_facet(&upper->stl, facet);
|
||||
if (upper != nullptr)
|
||||
stl_add_facet(&upper->stl, facet);
|
||||
} else if (max_z < z || (max_z == z && min_z < z)) {
|
||||
// facet is below the cut plane and does not belong to it
|
||||
if (lower != NULL) stl_add_facet(&lower->stl, facet);
|
||||
if (lower != nullptr)
|
||||
stl_add_facet(&lower->stl, facet);
|
||||
} else if (min_z < z && max_z > z) {
|
||||
// Facet is cut by the slicing plane.
|
||||
|
||||
|
@ -1786,22 +1776,24 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
|||
quadrilateral[1].vertex[2] = v0v1;
|
||||
|
||||
if (v0(2) > z) {
|
||||
if (upper != NULL) stl_add_facet(&upper->stl, &triangle);
|
||||
if (lower != NULL) {
|
||||
if (upper != nullptr)
|
||||
stl_add_facet(&upper->stl, &triangle);
|
||||
if (lower != nullptr) {
|
||||
stl_add_facet(&lower->stl, &quadrilateral[0]);
|
||||
stl_add_facet(&lower->stl, &quadrilateral[1]);
|
||||
}
|
||||
} else {
|
||||
if (upper != NULL) {
|
||||
if (upper != nullptr) {
|
||||
stl_add_facet(&upper->stl, &quadrilateral[0]);
|
||||
stl_add_facet(&upper->stl, &quadrilateral[1]);
|
||||
}
|
||||
if (lower != NULL) stl_add_facet(&lower->stl, &triangle);
|
||||
if (lower != nullptr)
|
||||
stl_add_facet(&lower->stl, &triangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (upper != NULL) {
|
||||
if (upper != nullptr) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part";
|
||||
ExPolygons section;
|
||||
this->make_expolygons_simple(upper_lines, §ion);
|
||||
|
@ -1815,7 +1807,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
|||
}
|
||||
}
|
||||
|
||||
if (lower != NULL) {
|
||||
if (lower != nullptr) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part";
|
||||
ExPolygons section;
|
||||
this->make_expolygons_simple(lower_lines, §ion);
|
||||
|
@ -1905,10 +1897,10 @@ TriangleMesh make_cylinder(double r, double h, double fa)
|
|||
//FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html
|
||||
TriangleMesh make_sphere(double radius, double fa)
|
||||
{
|
||||
int sectorCount = ceil(2. * M_PI / fa);
|
||||
int stackCount = ceil(M_PI / fa);
|
||||
float sectorStep = 2. * M_PI / sectorCount;
|
||||
float stackStep = M_PI / stackCount;
|
||||
int sectorCount = int(ceil(2. * M_PI / fa));
|
||||
int stackCount = int(ceil(M_PI / fa));
|
||||
float sectorStep = float(2. * M_PI / sectorCount);
|
||||
float stackStep = float(M_PI / stackCount);
|
||||
|
||||
Pointf3s vertices;
|
||||
vertices.reserve((stackCount - 1) * sectorCount + 2);
|
||||
|
|
|
@ -21,19 +21,13 @@ typedef std::vector<TriangleMesh*> TriangleMeshPtrs;
|
|||
class TriangleMesh
|
||||
{
|
||||
public:
|
||||
TriangleMesh() : repaired(false) { stl_initialize(&this->stl); }
|
||||
TriangleMesh() : repaired(false) {}
|
||||
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
|
||||
TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; }
|
||||
TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); }
|
||||
~TriangleMesh() { clear(); }
|
||||
TriangleMesh& operator=(const TriangleMesh &other);
|
||||
TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; }
|
||||
void clear() { stl_close(&this->stl); this->repaired = false; }
|
||||
void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); }
|
||||
void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); }
|
||||
void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); }
|
||||
void write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); }
|
||||
void repair();
|
||||
void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
|
||||
bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); }
|
||||
bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }
|
||||
bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); }
|
||||
void repair(bool update_shared_vertices = true);
|
||||
float volume();
|
||||
void check_topology();
|
||||
bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; }
|
||||
|
@ -58,7 +52,7 @@ public:
|
|||
TriangleMeshPtrs split() const;
|
||||
void merge(const TriangleMesh &mesh);
|
||||
ExPolygons horizontal_projection() const;
|
||||
const float* first_vertex() const { return this->stl.facet_start ? &this->stl.facet_start->vertex[0](0) : nullptr; }
|
||||
const float* first_vertex() const { return this->stl.facet_start.empty() ? nullptr : &this->stl.facet_start.front().vertex[0](0); }
|
||||
// 2D convex hull of a 3D mesh projected into the Z=0 plane.
|
||||
Polygon convex_hull();
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
|
@ -69,12 +63,13 @@ public:
|
|||
void reset_repair_stats();
|
||||
bool needed_repair() const;
|
||||
void require_shared_vertices();
|
||||
bool has_shared_vertices() const { return stl.v_shared != NULL; }
|
||||
bool has_shared_vertices() const { return ! this->its.vertices.empty(); }
|
||||
size_t facets_count() const { return this->stl.stats.number_of_facets; }
|
||||
bool empty() const { return this->facets_count() == 0; }
|
||||
bool is_splittable() const;
|
||||
|
||||
stl_file stl;
|
||||
indexed_triangle_set its;
|
||||
bool repaired;
|
||||
|
||||
private:
|
||||
|
|
|
@ -48,10 +48,33 @@ typedef double coordf_t;
|
|||
//FIXME Better to use an inline function with an explicit return type.
|
||||
//inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
|
||||
#define scale_(val) ((val) / SCALING_FACTOR)
|
||||
|
||||
#define SCALED_EPSILON scale_(EPSILON)
|
||||
|
||||
#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/"
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
# define SLIC3R_CONSTEXPR
|
||||
# define SLIC3R_NOEXCEPT
|
||||
#else
|
||||
#define SLIC3R_CONSTEXPR constexpr
|
||||
#define SLIC3R_NOEXCEPT noexcept
|
||||
#endif
|
||||
|
||||
template<class Tf> inline SLIC3R_CONSTEXPR coord_t scaled(Tf val)
|
||||
{
|
||||
static_assert (std::is_floating_point<Tf>::value, "Floating point only");
|
||||
return coord_t(val / Tf(SCALING_FACTOR));
|
||||
}
|
||||
|
||||
template<class Tf = double> inline SLIC3R_CONSTEXPR Tf unscaled(coord_t val)
|
||||
{
|
||||
static_assert (std::is_floating_point<Tf>::value, "Floating point only");
|
||||
return Tf(val * Tf(SCALING_FACTOR));
|
||||
}
|
||||
|
||||
inline SLIC3R_CONSTEXPR float unscaledf(coord_t val) { return unscaled<float>(val); }
|
||||
|
||||
inline std::string debug_out_path(const char *name, ...)
|
||||
{
|
||||
char buffer[2048];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue