Merge remote-tracking branch 'origin/master' into ys_bugfixing

This commit is contained in:
YuSanka 2019-06-19 16:32:40 +02:00
commit 09f4831f4e
86 changed files with 4705 additions and 4380 deletions

View file

@ -60,7 +60,7 @@ if (MSVC)
# /bigobj (Increase Number of Sections in .Obj file) # /bigobj (Increase Number of Sections in .Obj file)
# error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater
# Generate symbols at every build target, even for the release. # Generate symbols at every build target, even for the release.
add_compile_options(-bigobj -Zm316 /Zi) add_compile_options(-bigobj -Zm520 /Zi)
endif () endif ()
# Display and check CMAKE_PREFIX_PATH # Display and check CMAKE_PREFIX_PATH

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

View file

@ -7,10 +7,13 @@
#include <Windows.h> #include <Windows.h>
#include <wchar.h> #include <wchar.h>
#ifdef SLIC3R_GUI #ifdef SLIC3R_GUI
extern "C"
{
// Let the NVIDIA and AMD know we want to use their graphics card // Let the NVIDIA and AMD know we want to use their graphics card
// on a dual graphics card system. // on a dual graphics card system.
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif /* SLIC3R_GUI */ #endif /* SLIC3R_GUI */
#endif /* WIN32 */ #endif /* WIN32 */
@ -241,8 +244,7 @@ int CLI::run(int argc, char **argv)
} else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
std::vector<Model> new_models; std::vector<Model> new_models;
for (auto &model : m_models) { for (auto &model : m_models) {
model.repair(); model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0
model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0
size_t num_objects = model.objects.size(); size_t num_objects = model.objects.size();
for (size_t i = 0; i < num_objects; ++ i) { for (size_t i = 0; i < num_objects; ++ i) {
@ -301,8 +303,9 @@ int CLI::run(int argc, char **argv)
} }
} }
} else if (opt_key == "repair") { } else if (opt_key == "repair") {
for (auto &model : m_models) // Models are repaired by default.
model.repair(); //for (auto &model : m_models)
// model.repair();
} else { } else {
boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl;
return 1; return 1;

View file

@ -8,10 +8,13 @@
#include <wchar.h> #include <wchar.h>
#ifdef SLIC3R_GUI #ifdef SLIC3R_GUI
extern "C"
{
// Let the NVIDIA and AMD know we want to use their graphics card // Let the NVIDIA and AMD know we want to use their graphics card
// on a dual graphics card system. // on a dual graphics card system.
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif /* SLIC3R_GUI */ #endif /* SLIC3R_GUI */
#include <stdlib.h> #include <stdlib.h>

File diff suppressed because it is too large Load diff

View file

@ -25,271 +25,214 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
// Boost pool: Don't use mutexes to synchronize memory allocation.
#define BOOST_POOL_NO_MT
#include <boost/pool/object_pool.hpp>
#include "stl.h" #include "stl.h"
static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void reverse_facet(stl_file *stl, int facet_num)
{
++ stl->stats.facets_reversed;
static void int neighbor[3] = { stl->neighbors_start[facet_num].neighbor[0], stl->neighbors_start[facet_num].neighbor[1], stl->neighbors_start[facet_num].neighbor[2] };
stl_reverse_facet(stl_file *stl, int facet_num) { int vnot[3] = { stl->neighbors_start[facet_num].which_vertex_not[0], stl->neighbors_start[facet_num].which_vertex_not[1], stl->neighbors_start[facet_num].which_vertex_not[2] };
stl_vertex tmp_vertex;
/* int tmp_neighbor;*/
int neighbor[3];
int vnot[3];
stl->stats.facets_reversed += 1; // reverse the facet
stl_vertex tmp_vertex = stl->facet_start[facet_num].vertex[0];
stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1];
stl->facet_start[facet_num].vertex[1] = tmp_vertex;
neighbor[0] = stl->neighbors_start[facet_num].neighbor[0]; // fix the vnots of the neighboring facets
neighbor[1] = stl->neighbors_start[facet_num].neighbor[1]; if (neighbor[0] != -1)
neighbor[2] = stl->neighbors_start[facet_num].neighbor[2]; stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = (stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6;
vnot[0] = stl->neighbors_start[facet_num].which_vertex_not[0]; if (neighbor[1] != -1)
vnot[1] = stl->neighbors_start[facet_num].which_vertex_not[1]; stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = (stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6;
vnot[2] = stl->neighbors_start[facet_num].which_vertex_not[2]; if (neighbor[2] != -1)
stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = (stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6;
/* reverse the facet */ // swap the neighbors of the facet that is being reversed
tmp_vertex = stl->facet_start[facet_num].vertex[0]; stl->neighbors_start[facet_num].neighbor[1] = neighbor[2];
stl->facet_start[facet_num].vertex[0] = stl->neighbors_start[facet_num].neighbor[2] = neighbor[1];
stl->facet_start[facet_num].vertex[1];
stl->facet_start[facet_num].vertex[1] = tmp_vertex;
/* fix the vnots of the neighboring facets */ // swap the vnots of the facet that is being reversed
if(neighbor[0] != -1) stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2];
stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1];
(stl->neighbors_start[neighbor[0]].
which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6;
if(neighbor[1] != -1)
stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] =
(stl->neighbors_start[neighbor[1]].
which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6;
if(neighbor[2] != -1)
stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] =
(stl->neighbors_start[neighbor[2]].
which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6;
/* swap the neighbors of the facet that is being reversed */ // reverse the values of the vnots of the facet that is being reversed
stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; stl->neighbors_start[facet_num].which_vertex_not[0] = (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6;
stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; stl->neighbors_start[facet_num].which_vertex_not[1] = (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6;
stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6;
/* swap the vnots of the facet that is being reversed */
stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2];
stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1];
/* reverse the values of the vnots of the facet that is being reversed */
stl->neighbors_start[facet_num].which_vertex_not[0] =
(stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6;
stl->neighbors_start[facet_num].which_vertex_not[1] =
(stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6;
stl->neighbors_start[facet_num].which_vertex_not[2] =
(stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6;
} }
void // Returns true if the normal was flipped.
stl_fix_normal_directions(stl_file *stl) { static bool check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag)
char *norm_sw; {
/* int edge_num;*/ stl_facet *facet = &stl->facet_start[facet_num];
/* int vnot;*/
int checked = 0;
int facet_num;
/* int next_facet;*/
int i;
int j;
struct stl_normal {
int facet_num;
struct stl_normal *next;
};
struct stl_normal *head;
struct stl_normal *tail;
struct stl_normal *newn;
struct stl_normal *temp;
int* reversed_ids; stl_normal normal;
int reversed_count = 0; stl_calculate_normal(normal, facet);
int id; stl_normalize_vector(normal);
int force_exit = 0; stl_normal normal_dif = (normal - facet->normal).cwiseAbs();
if (stl->error) return; const float eps = 0.001f;
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
// Normal is within tolerance. It is not really necessary to change the values here, but just for consistency, I will.
facet->normal = normal;
return false;
}
// this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 stl_normal test_norm = facet->normal;
if (stl->stats.number_of_facets == 0) return; stl_normalize_vector(test_norm);
normal_dif = (normal - test_norm).cwiseAbs();
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
// The normal is not within tolerance, but direction is OK.
if (normal_fix_flag) {
facet->normal = normal;
++ stl->stats.normals_fixed;
}
return false;
}
/* Initialize linked list. */ test_norm *= -1.f;
head = (struct stl_normal*)malloc(sizeof(struct stl_normal)); normal_dif = (normal - test_norm).cwiseAbs();
if(head == NULL) perror("stl_fix_normal_directions"); if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
tail = (struct stl_normal*)malloc(sizeof(struct stl_normal)); // The normal is not within tolerance and backwards.
if(tail == NULL) perror("stl_fix_normal_directions"); if (normal_fix_flag) {
head->next = tail; facet->normal = normal;
tail->next = tail; ++ stl->stats.normals_fixed;
}
/* Initialize list that keeps track of already fixed facets. */ return true;
norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char)); }
if(norm_sw == NULL) perror("stl_fix_normal_directions"); if (normal_fix_flag) {
facet->normal = normal;
/* Initialize list that keeps track of reversed facets. */ ++ stl->stats.normals_fixed;
reversed_ids = (int*)calloc(stl->stats.number_of_facets, sizeof(int)); }
if (reversed_ids == NULL) perror("stl_fix_normal_directions reversed_ids"); // Status is unknown.
return false;
facet_num = 0;
/* If normal vector is not within tolerance and backwards:
Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances
of it being wrong randomly are low if most of the triangles are right: */
if (stl_check_normal_vector(stl, 0, 0) == 2) {
stl_reverse_facet(stl, 0);
reversed_ids[reversed_count++] = 0;
}
/* Say that we've fixed this facet: */
norm_sw[facet_num] = 1;
checked++;
for(;;) {
/* Add neighbors_to_list.
Add unconnected neighbors to the list:a */
for(j = 0; j < 3; j++) {
/* Reverse the neighboring facets if necessary. */
if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) {
/* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */
if(stl->neighbors_start[facet_num].neighbor[j] != -1) {
if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) {
/* trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) */
for (id = reversed_count - 1; id >= 0; --id) {
stl_reverse_facet(stl, reversed_ids[id]);
}
force_exit = 1;
break;
} else {
stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]);
reversed_ids[reversed_count++] = stl->neighbors_start[facet_num].neighbor[j];
}
}
}
/* If this edge of the facet is connected: */
if(stl->neighbors_start[facet_num].neighbor[j] != -1) {
/* If we haven't fixed this facet yet, add it to the list: */
if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) {
/* Add node to beginning of list. */
newn = (struct stl_normal*)malloc(sizeof(struct stl_normal));
if(newn == NULL) perror("stl_fix_normal_directions");
newn->facet_num = stl->neighbors_start[facet_num].neighbor[j];
newn->next = head->next;
head->next = newn;
}
}
}
/* an error occourred, quit the for loop and exit */
if (force_exit) break;
/* Get next facet to fix from top of list. */
if(head->next != tail) {
facet_num = head->next->facet_num;
if(norm_sw[facet_num] != 1) { /* If facet is in list mutiple times */
norm_sw[facet_num] = 1; /* Record this one as being fixed. */
checked++;
}
temp = head->next; /* Delete this facet from the list. */
head->next = head->next->next;
free(temp);
} else { /* if we ran out of facets to fix: */
/* All of the facets in this part have been fixed. */
stl->stats.number_of_parts += 1;
if(checked >= stl->stats.number_of_facets) {
/* All of the facets have been checked. Bail out. */
break;
} else {
/* There is another part here. Find it and continue. */
for(i = 0; i < stl->stats.number_of_facets; i++) {
if(norm_sw[i] == 0) {
/* This is the first facet of the next part. */
facet_num = i;
if(stl_check_normal_vector(stl, i, 0) == 2) {
stl_reverse_facet(stl, i);
reversed_ids[reversed_count++] = i;
}
norm_sw[facet_num] = 1;
checked++;
break;
}
}
}
}
}
free(head);
free(tail);
free(reversed_ids);
free(norm_sw);
} }
static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { void stl_fix_normal_directions(stl_file *stl)
/* Returns 0 if the normal is within tolerance */ {
/* Returns 1 if the normal is not within tolerance, but direction is OK */ // This may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
/* Returns 2 if the normal is not within tolerance and backwards */ if (stl->stats.number_of_facets == 0)
/* Returns 4 if the status is unknown. */ return;
stl_facet *facet; struct stl_normal {
int facet_num;
stl_normal *next;
};
facet = &stl->facet_start[facet_num]; // Initialize linked list.
boost::object_pool<stl_normal> pool;
stl_normal *head = pool.construct();
stl_normal *tail = pool.construct();
head->next = tail;
tail->next = tail;
stl_normal normal; // Initialize list that keeps track of already fixed facets.
stl_calculate_normal(normal, facet); std::vector<char> norm_sw(stl->stats.number_of_facets, 0);
stl_normalize_vector(normal); // Initialize list that keeps track of reversed facets.
stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); std::vector<int> reversed_ids(stl->stats.number_of_facets, 0);
const float eps = 0.001f; int facet_num = 0;
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { int reversed_count = 0;
/* It is not really necessary to change the values here */ // If normal vector is not within tolerance and backwards:
/* but just for consistency, I will. */ // Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances
facet->normal = normal; // of it being wrong randomly are low if most of the triangles are right:
return 0; if (check_normal_vector(stl, 0, 0)) {
} reverse_facet(stl, 0);
reversed_ids[reversed_count ++] = 0;
}
stl_normal test_norm = facet->normal; // Say that we've fixed this facet:
stl_normalize_vector(test_norm); norm_sw[facet_num] = 1;
normal_dif = (normal - test_norm).cwiseAbs(); int checked = 1;
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
if(normal_fix_flag) {
facet->normal = normal;
stl->stats.normals_fixed += 1;
}
return 1;
}
test_norm *= -1.f; for (;;) {
normal_dif = (normal - test_norm).cwiseAbs(); // Add neighbors_to_list. Add unconnected neighbors to the list.
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { bool force_exit = false;
// Facet is backwards. for (int j = 0; j < 3; ++ j) {
if(normal_fix_flag) { // Reverse the neighboring facets if necessary.
facet->normal = normal; if (stl->neighbors_start[facet_num].which_vertex_not[j] > 2) {
stl->stats.normals_fixed += 1; // If the facet has a neighbor that is -1, it means that edge isn't shared by another facet
} if (stl->neighbors_start[facet_num].neighbor[j] != -1) {
return 2; if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) {
} // trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206)
if(normal_fix_flag) { for (int id = reversed_count - 1; id >= 0; -- id)
facet->normal = normal; reverse_facet(stl, reversed_ids[id]);
stl->stats.normals_fixed += 1; force_exit = true;
} break;
return 4; }
reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]);
reversed_ids[reversed_count ++] = stl->neighbors_start[facet_num].neighbor[j];
}
}
// If this edge of the facet is connected:
if (stl->neighbors_start[facet_num].neighbor[j] != -1) {
// If we haven't fixed this facet yet, add it to the list:
if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) {
// Add node to beginning of list.
stl_normal *newn = pool.construct();
newn->facet_num = stl->neighbors_start[facet_num].neighbor[j];
newn->next = head->next;
head->next = newn;
}
}
}
// an error occourred, quit the for loop and exit
if (force_exit)
break;
// Get next facet to fix from top of list.
if (head->next != tail) {
facet_num = head->next->facet_num;
if (norm_sw[facet_num] != 1) { // If facet is in list mutiple times
norm_sw[facet_num] = 1; // Record this one as being fixed.
++ checked;
}
stl_normal *temp = head->next; // Delete this facet from the list.
head->next = head->next->next;
// pool.destroy(temp);
} else { // If we ran out of facets to fix: All of the facets in this part have been fixed.
++ stl->stats.number_of_parts;
if (checked >= stl->stats.number_of_facets)
// All of the facets have been checked. Bail out.
break;
// There is another part here. Find it and continue.
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
if (norm_sw[i] == 0) {
// This is the first facet of the next part.
facet_num = i;
if (check_normal_vector(stl, i, 0)) {
reverse_facet(stl, i);
reversed_ids[reversed_count++] = i;
}
norm_sw[facet_num] = 1;
++ checked;
break;
}
}
}
// pool.destroy(head);
// pool.destroy(tail);
} }
void stl_fix_normal_values(stl_file *stl) { void stl_fix_normal_values(stl_file *stl)
int i; {
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
if (stl->error) return; check_normal_vector(stl, i, 1);
for(i = 0; i < stl->stats.number_of_facets; i++) {
stl_check_normal_vector(stl, i, 1);
}
} }
void stl_reverse_all_facets(stl_file *stl) void stl_reverse_all_facets(stl_file *stl)
{ {
if (stl->error) stl_normal normal;
return; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
reverse_facet(stl, i);
stl_normal normal; stl_calculate_normal(normal, &stl->facet_start[i]);
for(int i = 0; i < stl->stats.number_of_facets; i++) { stl_normalize_vector(normal);
stl_reverse_facet(stl, i); stl->facet_start[i].normal = normal;
stl_calculate_normal(normal, &stl->facet_start[i]); }
stl_normalize_vector(normal);
stl->facet_start[i].normal = normal;
}
} }

View file

@ -23,242 +23,237 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <vector>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp> #include <boost/nowide/cstdio.hpp>
#include "stl.h" #include "stl.h"
void void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its)
stl_invalidate_shared_vertices(stl_file *stl) { {
if (stl->error) return; // 3 indices to vertex per face
its.indices.assign(stl->stats.number_of_facets, stl_triangle_vertex_indices(-1, -1, -1));
// Shared vertices (3D coordinates)
its.vertices.clear();
its.vertices.reserve(stl->stats.number_of_facets / 2);
if (stl->v_indices != NULL) { // A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop
free(stl->v_indices); // while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal
stl->v_indices = NULL; // are marked with a unique fan_traversal_stamp.
} unsigned int fan_traversal_stamp = 0;
if (stl->v_shared != NULL) { std::vector<unsigned int> fan_traversal_facet_visited(stl->stats.number_of_facets, 0);
free(stl->v_shared);
stl->v_shared = NULL; for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) {
} for (int j = 0; j < 3; ++ j) {
if (its.indices[facet_idx][j] != -1)
// Shared vertex was already assigned.
continue;
// Create a new shared vertex.
its.vertices.emplace_back(stl->facet_start[facet_idx].vertex[j]);
// Traverse the fan around the j-th vertex of the i-th face, assign the newly created shared vertex index to all the neighboring triangles in the triangle fan.
int facet_in_fan_idx = facet_idx;
bool edge_direction = false;
bool traversal_reversed = false;
int vnot = (j + 2) % 3;
// Increase the
++ fan_traversal_stamp;
for (;;) {
// Next edge on facet_in_fan_idx to be traversed. The edge is indexed by its starting vertex index.
int next_edge = 0;
// Vertex index in facet_in_fan_idx, which is being pivoted around, and which is being assigned a new shared vertex.
int pivot_vertex = 0;
if (vnot > 2) {
// The edge of facet_in_fan_idx opposite to vnot is equally oriented, therefore
// the neighboring facet is flipped.
if (! edge_direction) {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
} else {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot % 3;
}
edge_direction = ! edge_direction;
} else {
// The neighboring facet is correctly oriented.
if (! edge_direction) {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot;
} else {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
}
}
its.indices[facet_in_fan_idx][pivot_vertex] = its.vertices.size() - 1;
fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp;
// next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge!
int next_facet = stl->neighbors_start[facet_in_fan_idx].neighbor[next_edge];
if (next_facet == -1) {
// No neighbor going in the current direction.
if (traversal_reversed) {
// Went to one limit, then turned back and reached the other limit. Quit the fan traversal.
break;
} else {
// Reached the first limit. Now try to reverse and traverse up to the other limit.
edge_direction = true;
vnot = (j + 1) % 3;
traversal_reversed = true;
facet_in_fan_idx = facet_idx;
}
} else if (next_facet == facet_idx) {
// Traversed a closed fan all around.
// assert(! traversal_reversed);
break;
} else if (next_facet >= (int)stl->stats.number_of_facets) {
// The mesh is not valid!
// assert(false);
break;
} else if (fan_traversal_facet_visited[next_facet] == fan_traversal_stamp) {
// Traversed a closed fan all around, but did not reach the starting face.
// This indicates an invalid geometry (non-manifold).
//assert(false);
break;
} else {
// Continue traversal.
// next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge!
vnot = stl->neighbors_start[facet_in_fan_idx].which_vertex_not[next_edge];
facet_in_fan_idx = next_facet;
}
}
}
}
} }
void bool its_write_off(const indexed_triangle_set &its, const char *file)
stl_generate_shared_vertices(stl_file *stl) { {
int i; /* Open the file */
int j; FILE *fp = boost::nowide::fopen(file, "w");
int first_facet; if (fp == nullptr) {
int direction; BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing";
int facet_num; return false;
int vnot; }
int next_edge;
int pivot_vertex;
int next_facet;
int reversed;
if (stl->error) return; fprintf(fp, "OFF\n");
fprintf(fp, "%d %d 0\n", (int)its.vertices.size(), (int)its.indices.size());
for (int i = 0; i < its.vertices.size(); ++ i)
fprintf(fp, "\t%f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2));
for (uint32_t i = 0; i < its.indices.size(); ++ i)
fprintf(fp, "\t3 %d %d %d\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
fclose(fp);
return true;
}
/* make sure this function is idempotent and does not leak memory */ bool its_write_vrml(const indexed_triangle_set &its, const char *file)
stl_invalidate_shared_vertices(stl); {
/* Open the file */
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_vrml: Couldn't open " << file << " for writing";
return false;
}
stl->v_indices = (v_indices_struct*) fprintf(fp, "#VRML V1.0 ascii\n\n");
calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); fprintf(fp, "Separator {\n");
if(stl->v_indices == NULL) perror("stl_generate_shared_vertices"); fprintf(fp, "\tDEF STLShape ShapeHints {\n");
stl->v_shared = (stl_vertex*) fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n");
calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); fprintf(fp, "\t\tfaceType CONVEX\n");
if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); fprintf(fp, "\t\tshapeType SOLID\n");
stl->stats.shared_malloced = stl->stats.number_of_facets / 2; fprintf(fp, "\t\tcreaseAngle 0.0\n");
stl->stats.shared_vertices = 0; fprintf(fp, "\t}\n");
fprintf(fp, "\tDEF STLModel Separator {\n");
fprintf(fp, "\t\tDEF STLColor Material {\n");
fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n");
fprintf(fp, "\t\t}\n");
fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n");
fprintf(fp, "\t\t\tpoint [\n");
for(i = 0; i < stl->stats.number_of_facets; i++) { int i = 0;
stl->v_indices[i].vertex[0] = -1; for (; i + 1 < its.vertices.size(); ++ i)
stl->v_indices[i].vertex[1] = -1; fprintf(fp, "\t\t\t\t%f %f %f,\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2));
stl->v_indices[i].vertex[2] = -1; fprintf(fp, "\t\t\t\t%f %f %f]\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2));
} fprintf(fp, "\t\t}\n");
fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n");
fprintf(fp, "\t\t\tcoordIndex [\n");
for (size_t i = 0; i + 1 < its.indices.size(); ++ i)
fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
fprintf(fp, "\t\t}\n");
fprintf(fp, "\t}\n");
fprintf(fp, "}\n");
fclose(fp);
return true;
}
bool its_write_obj(const indexed_triangle_set &its, const char *file)
{
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing";
return false;
}
for (size_t i = 0; i < its.vertices.size(); ++ i)
fprintf(fp, "v %f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2));
for (size_t i = 0; i < its.indices.size(); ++ i)
fprintf(fp, "f %d %d %d\n", its.indices[i][0]+1, its.indices[i][1]+1, its.indices[i][2]+1);
fclose(fp);
return true;
}
for(i = 0; i < stl->stats.number_of_facets; i++) { // Check validity of the mesh, assert on error.
first_facet = i; bool stl_validate(const stl_file *stl, const indexed_triangle_set &its)
for(j = 0; j < 3; j++) { {
if(stl->v_indices[i].vertex[j] != -1) { assert(! stl->facet_start.empty());
continue; assert(stl->facet_start.size() == stl->stats.number_of_facets);
} assert(stl->neighbors_start.size() == stl->stats.number_of_facets);
if(stl->stats.shared_vertices == stl->stats.shared_malloced) { assert(stl->facet_start.size() == stl->neighbors_start.size());
stl->stats.shared_malloced += 1024; assert(! stl->neighbors_start.empty());
stl->v_shared = (stl_vertex*)realloc(stl->v_shared, assert((its.indices.empty()) == (its.vertices.empty()));
stl->stats.shared_malloced * sizeof(stl_vertex)); assert(stl->stats.number_of_facets > 0);
if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); assert(its.vertices.empty() || its.indices.size() == stl->stats.number_of_facets);
}
stl->v_shared[stl->stats.shared_vertices] = #ifdef _DEBUG
stl->facet_start[i].vertex[j]; // Verify validity of neighborship data.
for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) {
direction = 0; const stl_neighbors &nbr = stl->neighbors_start[facet_idx];
reversed = 0; const int *vertices = its.indices.empty() ? nullptr : its.indices[facet_idx].data();
facet_num = i; for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) {
vnot = (j + 2) % 3; int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx];
assert(nbr_face < (int)stl->stats.number_of_facets);
for(;;) { if (nbr_face != -1) {
if(vnot > 2) { int nbr_vnot = nbr.which_vertex_not[nbr_idx];
if(direction == 0) { assert(nbr_vnot >= 0 && nbr_vnot < 6);
pivot_vertex = (vnot + 2) % 3; // Neighbor of the neighbor is the original face.
next_edge = pivot_vertex; assert(stl->neighbors_start[nbr_face].neighbor[(nbr_vnot + 1) % 3] == facet_idx);
direction = 1; int vnot_back = stl->neighbors_start[nbr_face].which_vertex_not[(nbr_vnot + 1) % 3];
} else { assert(vnot_back >= 0 && vnot_back < 6);
pivot_vertex = (vnot + 1) % 3; assert((nbr_vnot < 3) == (vnot_back < 3));
next_edge = vnot % 3; assert(vnot_back % 3 == (nbr_idx + 2) % 3);
direction = 0; if (vertices != nullptr) {
} // Has shared vertices.
} else { if (nbr_vnot < 3) {
if(direction == 0) { // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented.
pivot_vertex = (vnot + 1) % 3; assert((its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[nbr_idx]));
next_edge = vnot; } else {
} else { // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped.
pivot_vertex = (vnot + 2) % 3; assert((its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[nbr_idx]));
next_edge = pivot_vertex; }
} }
}
} }
stl->v_indices[facet_num].vertex[pivot_vertex] =
stl->stats.shared_vertices;
next_facet = stl->neighbors_start[facet_num].neighbor[next_edge];
if(next_facet == -1) {
if(reversed) {
break;
} else {
direction = 1;
vnot = (j + 1) % 3;
reversed = 1;
facet_num = first_facet;
}
} else if(next_facet != first_facet) {
vnot = stl->neighbors_start[facet_num].
which_vertex_not[next_edge];
facet_num = next_facet;
} else {
break;
}
}
stl->stats.shared_vertices += 1;
} }
} #endif /* _DEBUG */
return true;
} }
void // Check validity of the mesh, assert on error.
stl_write_off(stl_file *stl, const char *file) { bool stl_validate(const stl_file *stl)
int i; {
FILE *fp; indexed_triangle_set its;
char *error_msg; return stl_validate(stl, its);
if (stl->error) return;
/* Open the file */
fp = boost::nowide::fopen(file, "w");
if(fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing",
file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
fprintf(fp, "OFF\n");
fprintf(fp, "%d %d 0\n",
stl->stats.shared_vertices, stl->stats.number_of_facets);
for(i = 0; i < stl->stats.shared_vertices; i++) {
fprintf(fp, "\t%f %f %f\n",
stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
}
for(i = 0; i < stl->stats.number_of_facets; i++) {
fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0],
stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]);
}
fclose(fp);
}
void
stl_write_vrml(stl_file *stl, const char *file) {
int i;
FILE *fp;
char *error_msg;
if (stl->error) return;
/* Open the file */
fp = boost::nowide::fopen(file, "w");
if(fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing",
file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
fprintf(fp, "#VRML V1.0 ascii\n\n");
fprintf(fp, "Separator {\n");
fprintf(fp, "\tDEF STLShape ShapeHints {\n");
fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n");
fprintf(fp, "\t\tfaceType CONVEX\n");
fprintf(fp, "\t\tshapeType SOLID\n");
fprintf(fp, "\t\tcreaseAngle 0.0\n");
fprintf(fp, "\t}\n");
fprintf(fp, "\tDEF STLModel Separator {\n");
fprintf(fp, "\t\tDEF STLColor Material {\n");
fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n");
fprintf(fp, "\t\t}\n");
fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n");
fprintf(fp, "\t\t\tpoint [\n");
for(i = 0; i < (stl->stats.shared_vertices - 1); i++) {
fprintf(fp, "\t\t\t\t%f %f %f,\n",
stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
}
fprintf(fp, "\t\t\t\t%f %f %f]\n",
stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
fprintf(fp, "\t\t}\n");
fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n");
fprintf(fp, "\t\t\tcoordIndex [\n");
for(i = 0; i < (stl->stats.number_of_facets - 1); i++) {
fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0],
stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]);
}
fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0],
stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]);
fprintf(fp, "\t\t}\n");
fprintf(fp, "\t}\n");
fprintf(fp, "}\n");
fclose(fp);
}
void stl_write_obj (stl_file *stl, const char *file) {
int i;
FILE* fp;
if (stl->error) return;
/* Open the file */
fp = boost::nowide::fopen(file, "w");
if (fp == NULL) {
char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
for (i = 0; i < stl->stats.shared_vertices; i++) {
fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
}
for (i = 0; i < stl->stats.number_of_facets; i++) {
fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1);
}
fclose(fp);
} }

View file

@ -27,6 +27,7 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <vector>
#include <Eigen/Geometry> #include <Eigen/Geometry>
// Size of the binary STL header, free form. // Size of the binary STL header, free form.
@ -40,22 +41,23 @@
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_vertex; typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_vertex;
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_normal; typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_normal;
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> stl_triangle_vertex_indices;
static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect");
static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect");
struct stl_facet { struct stl_facet {
stl_normal normal; stl_normal normal;
stl_vertex vertex[3]; stl_vertex vertex[3];
char extra[2]; char extra[2];
stl_facet rotated(const Eigen::Quaternion<float, Eigen::DontAlign> &rot) { stl_facet rotated(const Eigen::Quaternion<float, Eigen::DontAlign> &rot) const {
stl_facet out; stl_facet out;
out.normal = rot * this->normal; out.normal = rot * this->normal;
out.vertex[0] = rot * this->vertex[0]; out.vertex[0] = rot * this->vertex[0];
out.vertex[1] = rot * this->vertex[1]; out.vertex[1] = rot * this->vertex[1];
out.vertex[2] = rot * this->vertex[2]; out.vertex[2] = rot * this->vertex[2];
return out; return out;
} }
}; };
#define SIZEOF_STL_FACET 50 #define SIZEOF_STL_FACET 50
@ -67,104 +69,94 @@ static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrec
typedef enum {binary, ascii, inmemory} stl_type; typedef enum {binary, ascii, inmemory} stl_type;
typedef struct { struct stl_neighbors {
stl_vertex p1; stl_neighbors() { reset(); }
stl_vertex p2; void reset() {
int facet_number; neighbor[0] = -1;
} stl_edge; neighbor[1] = -1;
neighbor[2] = -1;
which_vertex_not[0] = -1;
which_vertex_not[1] = -1;
which_vertex_not[2] = -1;
}
int num_neighbors_missing() const { return (this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1); }
int num_neighbors() const { return 3 - this->num_neighbors_missing(); }
typedef struct stl_hash_edge { // Index of a neighbor facet.
// Key of a hash edge: sorted vertices of the edge. int neighbor[3];
uint32_t key[6]; // Index of an opposite vertex at the neighbor face.
// Compare two keys. char which_vertex_not[3];
bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } };
bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); }
int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; }
// Index of a facet owning this edge.
int facet_number;
// Index of this edge inside the facet with an index of facet_number.
// If this edge is stored backwards, which_edge is increased by 3.
int which_edge;
struct stl_hash_edge *next;
} stl_hash_edge;
typedef struct { struct stl_stats {
// Index of a neighbor facet. stl_stats() { this->reset(); }
int neighbor[3]; void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; }
// Index of an opposite vertex at the neighbor face. char header[81];
char which_vertex_not[3]; stl_type type;
} stl_neighbors; uint32_t number_of_facets;
stl_vertex max;
stl_vertex min;
stl_vertex size;
float bounding_diameter;
float shortest_edge;
float volume;
int connected_edges;
int connected_facets_1_edge;
int connected_facets_2_edge;
int connected_facets_3_edge;
int facets_w_1_bad_edge;
int facets_w_2_bad_edge;
int facets_w_3_bad_edge;
int original_num_facets;
int edges_fixed;
int degenerate_facets;
int facets_removed;
int facets_added;
int facets_reversed;
int backwards_edges;
int normals_fixed;
int number_of_parts;
};
typedef struct { struct stl_file {
int vertex[3]; stl_file() {}
} v_indices_struct;
typedef struct { void clear() {
char header[81]; this->facet_start.clear();
stl_type type; this->neighbors_start.clear();
uint32_t number_of_facets; this->stats.reset();
stl_vertex max; }
stl_vertex min;
stl_vertex size;
float bounding_diameter;
float shortest_edge;
float volume;
unsigned number_of_blocks;
int connected_edges;
int connected_facets_1_edge;
int connected_facets_2_edge;
int connected_facets_3_edge;
int facets_w_1_bad_edge;
int facets_w_2_bad_edge;
int facets_w_3_bad_edge;
int original_num_facets;
int edges_fixed;
int degenerate_facets;
int facets_removed;
int facets_added;
int facets_reversed;
int backwards_edges;
int normals_fixed;
int number_of_parts;
int malloced;
int freed;
int facets_malloced;
int collisions;
int shared_vertices;
int shared_malloced;
} stl_stats;
typedef struct { std::vector<stl_facet> facet_start;
FILE *fp; std::vector<stl_neighbors> neighbors_start;
stl_facet *facet_start; // Statistics
stl_hash_edge **heads; stl_stats stats;
stl_hash_edge *tail; };
int M;
stl_neighbors *neighbors_start;
v_indices_struct *v_indices;
stl_vertex *v_shared;
stl_stats stats;
char error;
} stl_file;
struct indexed_triangle_set
{
indexed_triangle_set() {}
extern void stl_open(stl_file *stl, const char *file); void clear() { indices.clear(); vertices.clear(); }
extern void stl_close(stl_file *stl);
std::vector<stl_triangle_vertex_indices> indices;
std::vector<stl_vertex> vertices;
//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
//std::vector<stl_normal> normals
};
extern bool stl_open(stl_file *stl, const char *file);
extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file);
extern void stl_print_neighbors(stl_file *stl, char *file); extern bool stl_print_neighbors(stl_file *stl, char *file);
extern void stl_put_little_int(FILE *fp, int value_in); extern bool stl_write_ascii(stl_file *stl, const char *file, const char *label);
extern void stl_put_little_float(FILE *fp, float value_in); extern bool stl_write_binary(stl_file *stl, const char *file, const char *label);
extern void stl_write_ascii(stl_file *stl, const char *file, const char *label);
extern void stl_write_binary(stl_file *stl, const char *file, const char *label);
extern void stl_write_binary_block(stl_file *stl, FILE *fp);
extern void stl_check_facets_exact(stl_file *stl); extern void stl_check_facets_exact(stl_file *stl);
extern void stl_check_facets_nearby(stl_file *stl, float tolerance); extern void stl_check_facets_nearby(stl_file *stl, float tolerance);
extern void stl_remove_unconnected_facets(stl_file *stl); extern void stl_remove_unconnected_facets(stl_file *stl);
extern void stl_write_vertex(stl_file *stl, int facet, int vertex); extern void stl_write_vertex(stl_file *stl, int facet, int vertex);
extern void stl_write_facet(stl_file *stl, char *label, int facet); extern void stl_write_facet(stl_file *stl, char *label, int facet);
extern void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge);
extern void stl_write_neighbor(stl_file *stl, int facet); extern void stl_write_neighbor(stl_file *stl, int facet);
extern void stl_write_quad_object(stl_file *stl, char *file); extern bool stl_write_quad_object(stl_file *stl, char *file);
extern void stl_verify_neighbors(stl_file *stl); extern void stl_verify_neighbors(stl_file *stl);
extern void stl_fill_holes(stl_file *stl); extern void stl_fill_holes(stl_file *stl);
extern void stl_fix_normal_directions(stl_file *stl); extern void stl_fix_normal_directions(stl_file *stl);
@ -186,36 +178,30 @@ extern void stl_get_size(stl_file *stl);
template<typename T> template<typename T>
extern void stl_transform(stl_file *stl, T *trafo3x4) extern void stl_transform(stl_file *stl, T *trafo3x4)
{ {
if (stl->error) for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) {
return; stl_facet &face = stl->facet_start[i_face];
for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) {
stl_vertex &v_dst = face.vertex[i_vertex];
stl_vertex v_src = v_dst;
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]);
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]);
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]);
}
stl_vertex &v_dst = face.normal;
stl_vertex v_src = v_dst;
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2));
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2));
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2));
}
for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { stl_get_size(stl);
stl_facet &face = stl->facet_start[i_face];
for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) {
stl_vertex &v_dst = face.vertex[i_vertex];
stl_vertex v_src = v_dst;
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]);
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]);
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]);
}
stl_vertex &v_dst = face.normal;
stl_vertex v_src = v_dst;
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2));
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2));
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2));
}
stl_get_size(stl);
} }
template<typename T> template<typename T>
inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t) inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t)
{ {
if (stl->error)
return;
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0); const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0);
for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) {
stl_facet &f = stl->facet_start[i]; stl_facet &f = stl->facet_start[i];
for (size_t j = 0; j < 3; ++j) for (size_t j = 0; j < 3; ++j)
f.vertex[j] = (t * f.vertex[j].template cast<T>()).template cast<float>().eval(); f.vertex[j] = (t * f.vertex[j].template cast<T>()).template cast<float>().eval();
@ -228,10 +214,7 @@ inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Aff
template<typename T> template<typename T>
inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m) inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m)
{ {
if (stl->error) for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) {
return;
for (size_t i = 0; i < stl->stats.number_of_facets; ++i) {
stl_facet &f = stl->facet_start[i]; stl_facet &f = stl->facet_start[i];
for (size_t j = 0; j < 3; ++j) for (size_t j = 0; j < 3; ++j)
f.vertex[j] = (m * f.vertex[j].template cast<T>()).template cast<float>().eval(); f.vertex[j] = (m * f.vertex[j].template cast<T>()).template cast<float>().eval();
@ -241,13 +224,43 @@ inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::Don
stl_get_size(stl); stl_get_size(stl);
} }
extern void stl_open_merge(stl_file *stl, char *file);
extern void stl_invalidate_shared_vertices(stl_file *stl); template<typename T>
extern void stl_generate_shared_vertices(stl_file *stl); extern void its_transform(indexed_triangle_set &its, T *trafo3x4)
extern void stl_write_obj(stl_file *stl, const char *file); {
extern void stl_write_off(stl_file *stl, const char *file); for (stl_vertex &v_dst : its.vertices) {
extern void stl_write_dxf(stl_file *stl, const char *file, char *label); stl_vertex v_src = v_dst;
extern void stl_write_vrml(stl_file *stl, const char *file); v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]);
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]);
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]);
}
}
template<typename T>
inline void its_transform(indexed_triangle_set &its, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t)
{
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0);
for (stl_vertex &v : its.vertices)
v = (t * v.template cast<T>()).template cast<float>().eval();
}
template<typename T>
inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m)
{
for (stl_vertex &v : its.vertices)
v = (m * v.template cast<T>()).template cast<float>().eval();
}
extern void its_rotate_x(indexed_triangle_set &its, float angle);
extern void its_rotate_y(indexed_triangle_set &its, float angle);
extern void its_rotate_z(indexed_triangle_set &its, float angle);
extern void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its);
extern bool its_write_obj(const indexed_triangle_set &its, const char *file);
extern bool its_write_off(const indexed_triangle_set &its, const char *file);
extern bool its_write_vrml(const indexed_triangle_set &its, const char *file);
extern bool stl_write_dxf(stl_file *stl, const char *file, char *label);
inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) { inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) {
normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]);
} }
@ -258,24 +271,18 @@ inline void stl_normalize_vector(stl_normal &normal) {
else else
normal *= float(1.0 / length); normal *= float(1.0 / length);
} }
inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) {
return (a(0) != b(0)) ? (a(0) < b(0)) :
((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2)));
}
extern void stl_calculate_volume(stl_file *stl); extern void stl_calculate_volume(stl_file *stl);
extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); extern void stl_repair(stl_file *stl, bool fixall_flag, bool exact_flag, bool tolerance_flag, float tolerance, bool increment_flag, float increment, bool nearby_flag, int iterations, bool remove_unconnected_flag, bool fill_holes_flag, bool normal_directions_flag, bool normal_values_flag, bool reverse_all_flag, bool verbose_flag);
extern void stl_initialize(stl_file *stl);
extern void stl_count_facets(stl_file *stl, const char *file);
extern void stl_allocate(stl_file *stl); extern void stl_allocate(stl_file *stl);
extern void stl_read(stl_file *stl, int first_facet, bool first); extern void stl_read(stl_file *stl, int first_facet, bool first);
extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first);
extern void stl_reallocate(stl_file *stl); extern void stl_reallocate(stl_file *stl);
extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); extern void stl_add_facet(stl_file *stl, const stl_facet *new_facet);
extern void stl_clear_error(stl_file *stl); // Validate the mesh, assert on error.
extern int stl_get_error(stl_file *stl); extern bool stl_validate(const stl_file *stl);
extern void stl_exit_on_error(stl_file *stl); extern bool stl_validate(const stl_file *stl, const indexed_triangle_set &its);
#endif #endif

View file

@ -22,159 +22,86 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/predef/other/endian.h>
#include "stl.h" #include "stl.h"
#include <boost/nowide/cstdio.hpp> void stl_stats_out(stl_file *stl, FILE *file, char *input_file)
#include <boost/detail/endian.hpp> {
// This is here for Slic3r, without our config.h it won't use this part of the code anyway.
#if !defined(SEEK_SET)
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#endif
void
stl_stats_out(stl_file *stl, FILE *file, char *input_file) {
if (stl->error) return;
/* this is here for Slic3r, without our config.h
it won't use this part of the code anyway */
#ifndef VERSION #ifndef VERSION
#define VERSION "unknown" #define VERSION "unknown"
#endif #endif
fprintf(file, "\n\ fprintf(file, "\n================= Results produced by ADMesh version " VERSION " ================\n");
================= Results produced by ADMesh version " VERSION " ================\n"); fprintf(file, "Input file : %s\n", input_file);
fprintf(file, "\ if (stl->stats.type == binary)
Input file : %s\n", input_file); fprintf(file, "File type : Binary STL file\n");
if(stl->stats.type == binary) { else
fprintf(file, "\ fprintf(file, "File type : ASCII STL file\n");
File type : Binary STL file\n"); fprintf(file, "Header : %s\n", stl->stats.header);
} else { fprintf(file, "============== Size ==============\n");
fprintf(file, "\ fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.min(0), stl->stats.max(0));
File type : ASCII STL file\n"); fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.min(1), stl->stats.max(1));
} fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.min(2), stl->stats.max(2));
fprintf(file, "\ fprintf(file, "========= Facet Status ========== Original ============ Final ====\n");
Header : %s\n", stl->stats.header); fprintf(file, "Number of facets : %5d %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets);
fprintf(file, "============== Size ==============\n"); fprintf(file, "Facets with 1 disconnected edge : %5d %5d\n",
fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge);
stl->stats.min(0), stl->stats.max(0)); fprintf(file, "Facets with 2 disconnected edges : %5d %5d\n",
fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge);
stl->stats.min(1), stl->stats.max(1)); fprintf(file, "Facets with 3 disconnected edges : %5d %5d\n",
fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge);
stl->stats.min(2), stl->stats.max(2)); fprintf(file, "Total disconnected facets : %5d %5d\n",
stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_3_edge);
fprintf(file, "\ fprintf(file, "=== Processing Statistics === ===== Other Statistics =====\n");
========= Facet Status ========== Original ============ Final ====\n"); fprintf(file, "Number of parts : %5d Volume : %f\n", stl->stats.number_of_parts, stl->stats.volume);
fprintf(file, "\ fprintf(file, "Degenerate facets : %5d\n", stl->stats.degenerate_facets);
Number of facets : %5d %5d\n", fprintf(file, "Edges fixed : %5d\n", stl->stats.edges_fixed);
stl->stats.original_num_facets, stl->stats.number_of_facets); fprintf(file, "Facets removed : %5d\n", stl->stats.facets_removed);
fprintf(file, "\ fprintf(file, "Facets added : %5d\n", stl->stats.facets_added);
Facets with 1 disconnected edge : %5d %5d\n", fprintf(file, "Facets reversed : %5d\n", stl->stats.facets_reversed);
stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - fprintf(file, "Backwards edges : %5d\n", stl->stats.backwards_edges);
stl->stats.connected_facets_3_edge); fprintf(file, "Normals fixed : %5d\n", stl->stats.normals_fixed);
fprintf(file, "\
Facets with 2 disconnected edges : %5d %5d\n",
stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge -
stl->stats.connected_facets_2_edge);
fprintf(file, "\
Facets with 3 disconnected edges : %5d %5d\n",
stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets -
stl->stats.connected_facets_1_edge);
fprintf(file, "\
Total disconnected facets : %5d %5d\n",
stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge +
stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets -
stl->stats.connected_facets_3_edge);
fprintf(file,
"=== Processing Statistics === ===== Other Statistics =====\n");
fprintf(file, "\
Number of parts : %5d Volume : % f\n",
stl->stats.number_of_parts, stl->stats.volume);
fprintf(file, "\
Degenerate facets : %5d\n", stl->stats.degenerate_facets);
fprintf(file, "\
Edges fixed : %5d\n", stl->stats.edges_fixed);
fprintf(file, "\
Facets removed : %5d\n", stl->stats.facets_removed);
fprintf(file, "\
Facets added : %5d\n", stl->stats.facets_added);
fprintf(file, "\
Facets reversed : %5d\n", stl->stats.facets_reversed);
fprintf(file, "\
Backwards edges : %5d\n", stl->stats.backwards_edges);
fprintf(file, "\
Normals fixed : %5d\n", stl->stats.normals_fixed);
} }
void bool stl_write_ascii(stl_file *stl, const char *file, const char *label)
stl_write_ascii(stl_file *stl, const char *file, const char *label) { {
int i; FILE *fp = boost::nowide::fopen(file, "w");
char *error_msg; if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing";
return false;
}
if (stl->error) return; fprintf(fp, "solid %s\n", label);
/* Open the file */ for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
FILE *fp = boost::nowide::fopen(file, "w"); fprintf(fp, " facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), stl->facet_start[i].normal(2));
if(fp == NULL) { fprintf(fp, " outer loop\n");
error_msg = (char*) fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2));
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2));
sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2));
file); fprintf(fp, " endloop\n");
perror(error_msg); fprintf(fp, " endfacet\n");
free(error_msg); }
stl->error = 1;
return;
}
fprintf(fp, "solid %s\n", label); fprintf(fp, "endsolid %s\n", label);
fclose(fp);
for(i = 0; i < stl->stats.number_of_facets; i++) { return true;
fprintf(fp, " facet normal % .8E % .8E % .8E\n",
stl->facet_start[i].normal(0), stl->facet_start[i].normal(1),
stl->facet_start[i].normal(2));
fprintf(fp, " outer loop\n");
fprintf(fp, " vertex % .8E % .8E % .8E\n",
stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1),
stl->facet_start[i].vertex[0](2));
fprintf(fp, " vertex % .8E % .8E % .8E\n",
stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1),
stl->facet_start[i].vertex[1](2));
fprintf(fp, " vertex % .8E % .8E % .8E\n",
stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1),
stl->facet_start[i].vertex[2](2));
fprintf(fp, " endloop\n");
fprintf(fp, " endfacet\n");
}
fprintf(fp, "endsolid %s\n", label);
fclose(fp);
} }
void bool stl_print_neighbors(stl_file *stl, char *file)
stl_print_neighbors(stl_file *stl, char *file) { {
int i; FILE *fp = boost::nowide::fopen(file, "w");
FILE *fp; if (fp == nullptr) {
char *error_msg; BOOST_LOG_TRIVIAL(error) << "stl_print_neighbors: Couldn't open " << file << " for writing";
return false;
}
if (stl->error) return; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n",
/* Open the file */
fp = boost::nowide::fopen(file, "w");
if(fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_print_neighbors: Couldn't open %s for writing",
file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
for(i = 0; i < stl->stats.number_of_facets; i++) {
fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n",
i, i,
stl->neighbors_start[i].neighbor[0], stl->neighbors_start[i].neighbor[0],
(int)stl->neighbors_start[i].which_vertex_not[0], (int)stl->neighbors_start[i].which_vertex_not[0],
@ -182,234 +109,142 @@ stl_print_neighbors(stl_file *stl, char *file) {
(int)stl->neighbors_start[i].which_vertex_not[1], (int)stl->neighbors_start[i].which_vertex_not[1],
stl->neighbors_start[i].neighbor[2], stl->neighbors_start[i].neighbor[2],
(int)stl->neighbors_start[i].which_vertex_not[2]); (int)stl->neighbors_start[i].which_vertex_not[2]);
} }
fclose(fp); fclose(fp);
return true;
} }
#ifndef BOOST_LITTLE_ENDIAN #if BOOST_ENDIAN_BIG_BYTE
// Swap a buffer of 32bit data from little endian to big endian and vice versa. // Swap a buffer of 32bit data from little endian to big endian and vice versa.
void stl_internal_reverse_quads(char *buf, size_t cnt) void stl_internal_reverse_quads(char *buf, size_t cnt)
{ {
for (size_t i = 0; i < cnt; i += 4) { for (size_t i = 0; i < cnt; i += 4) {
std::swap(buf[i], buf[i+3]); std::swap(buf[i], buf[i+3]);
std::swap(buf[i+1], buf[i+2]); std::swap(buf[i+1], buf[i+2]);
} }
} }
#endif #endif
void bool stl_write_binary(stl_file *stl, const char *file, const char *label)
stl_write_binary(stl_file *stl, const char *file, const char *label) { {
FILE *fp; FILE *fp = boost::nowide::fopen(file, "wb");
int i; if (fp == nullptr) {
char *error_msg; BOOST_LOG_TRIVIAL(error) << "stl_write_binary: Couldn't open " << file << " for writing";
return false;
}
if (stl->error) return; fprintf(fp, "%s", label);
for (size_t i = strlen(label); i < LABEL_SIZE; ++ i)
putc(0, fp);
/* Open the file */ #if !defined(SEEK_SET)
fp = boost::nowide::fopen(file, "wb"); #define SEEK_SET 0
if(fp == NULL) { #endif
error_msg = (char*) fseek(fp, LABEL_SIZE, SEEK_SET);
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ #if BOOST_ENDIAN_LITTLE_BYTE
sprintf(error_msg, "stl_write_binary: Couldn't open %s for writing", fwrite(&stl->stats.number_of_facets, 4, 1, fp);
file); for (const stl_facet &facet : stl->facet_start)
perror(error_msg); fwrite(&facet, SIZEOF_STL_FACET, 1, fp);
free(error_msg); #else /* BOOST_ENDIAN_LITTLE_BYTE */
stl->error = 1; char buffer[50];
return; // Convert the number of facets to little endian.
} memcpy(buffer, &stl->stats.number_of_facets, 4);
stl_internal_reverse_quads(buffer, 4);
fprintf(fp, "%s", label); fwrite(buffer, 4, 1, fp);
for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); for (i = 0; i < stl->stats.number_of_facets; ++ i) {
memcpy(buffer, stl->facet_start + i, 50);
fseek(fp, LABEL_SIZE, SEEK_SET); // Convert to little endian.
#ifdef BOOST_LITTLE_ENDIAN stl_internal_reverse_quads(buffer, 48);
fwrite(&stl->stats.number_of_facets, 4, 1, fp); fwrite(buffer, SIZEOF_STL_FACET, 1, fp);
for (i = 0; i < stl->stats.number_of_facets; ++ i) }
fwrite(stl->facet_start + i, SIZEOF_STL_FACET, 1, fp); #endif /* BOOST_ENDIAN_LITTLE_BYTE */
#else /* BOOST_LITTLE_ENDIAN */ fclose(fp);
char buffer[50]; return true;
// Convert the number of facets to little endian.
memcpy(buffer, &stl->stats.number_of_facets, 4);
stl_internal_reverse_quads(buffer, 4);
fwrite(buffer, 4, 1, fp);
for (i = 0; i < stl->stats.number_of_facets; ++ i) {
memcpy(buffer, stl->facet_start + i, 50);
// Convert to little endian.
stl_internal_reverse_quads(buffer, 48);
fwrite(buffer, SIZEOF_STL_FACET, 1, fp);
}
#endif /* BOOST_LITTLE_ENDIAN */
fclose(fp);
} }
void void stl_write_vertex(stl_file *stl, int facet, int vertex)
stl_write_vertex(stl_file *stl, int facet, int vertex) { {
if (stl->error) return; printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet,
printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet,
stl->facet_start[facet].vertex[vertex](0), stl->facet_start[facet].vertex[vertex](0),
stl->facet_start[facet].vertex[vertex](1), stl->facet_start[facet].vertex[vertex](1),
stl->facet_start[facet].vertex[vertex](2)); stl->facet_start[facet].vertex[vertex](2));
} }
void void stl_write_facet(stl_file *stl, char *label, int facet)
stl_write_facet(stl_file *stl, char *label, int facet) { {
if (stl->error) return; printf("facet (%d)/ %s\n", facet, label);
printf("facet (%d)/ %s\n", facet, label); stl_write_vertex(stl, facet, 0);
stl_write_vertex(stl, facet, 0); stl_write_vertex(stl, facet, 1);
stl_write_vertex(stl, facet, 1); stl_write_vertex(stl, facet, 2);
stl_write_vertex(stl, facet, 2);
} }
void void stl_write_neighbor(stl_file *stl, int facet)
stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge) { {
if (stl->error) return; printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet,
printf("edge (%d)/(%d) %s\n", edge.facet_number, edge.which_edge, label); stl->neighbors_start[facet].neighbor[0],
if(edge.which_edge < 3) { stl->neighbors_start[facet].neighbor[1],
stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); stl->neighbors_start[facet].neighbor[2],
stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); stl->neighbors_start[facet].which_vertex_not[0],
} else { stl->neighbors_start[facet].which_vertex_not[1],
stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); stl->neighbors_start[facet].which_vertex_not[2]);
stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3);
}
} }
void bool stl_write_quad_object(stl_file *stl, char *file)
stl_write_neighbor(stl_file *stl, int facet) { {
if (stl->error) return; stl_vertex connect_color = stl_vertex::Zero();
printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet, stl_vertex uncon_1_color = stl_vertex::Zero();
stl->neighbors_start[facet].neighbor[0], stl_vertex uncon_2_color = stl_vertex::Zero();
stl->neighbors_start[facet].neighbor[1], stl_vertex uncon_3_color = stl_vertex::Zero();
stl->neighbors_start[facet].neighbor[2], stl_vertex color;
stl->neighbors_start[facet].which_vertex_not[0],
stl->neighbors_start[facet].which_vertex_not[1],
stl->neighbors_start[facet].which_vertex_not[2]);
}
void FILE *fp = boost::nowide::fopen(file, "w");
stl_write_quad_object(stl_file *stl, char *file) { if (fp == nullptr) {
FILE *fp; BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing";
int i; return false;
int j; }
char *error_msg;
stl_vertex connect_color = stl_vertex::Zero();
stl_vertex uncon_1_color = stl_vertex::Zero();
stl_vertex uncon_2_color = stl_vertex::Zero();
stl_vertex uncon_3_color = stl_vertex::Zero();
stl_vertex color;
if (stl->error) return; fprintf(fp, "CQUAD\n");
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
/* Open the file */ switch (stl->neighbors_start[i].num_neighbors_missing()) {
fp = boost::nowide::fopen(file, "w"); case 0: color = connect_color; break;
if(fp == NULL) { case 1: color = uncon_1_color; break;
error_msg = (char*) case 2: color = uncon_2_color; break;
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ default: color = uncon_3_color;
sprintf(error_msg, "stl_write_quad_object: Couldn't open %s for writing", }
file); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2));
perror(error_msg); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2));
free(error_msg); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
stl->error = 1; fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
return;
}
fprintf(fp, "CQUAD\n");
for(i = 0; i < stl->stats.number_of_facets; i++) {
j = ((stl->neighbors_start[i].neighbor[0] == -1) +
(stl->neighbors_start[i].neighbor[1] == -1) +
(stl->neighbors_start[i].neighbor[2] == -1));
if(j == 0) {
color = connect_color;
} else if(j == 1) {
color = uncon_1_color;
} else if(j == 2) {
color = uncon_2_color;
} else {
color = uncon_3_color;
}
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n",
stl->facet_start[i].vertex[0](0),
stl->facet_start[i].vertex[0](1),
stl->facet_start[i].vertex[0](2), color(0), color(1), color(2));
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n",
stl->facet_start[i].vertex[1](0),
stl->facet_start[i].vertex[1](1),
stl->facet_start[i].vertex[1](2), color(0), color(1), color(2));
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n",
stl->facet_start[i].vertex[2](0),
stl->facet_start[i].vertex[2](1),
stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n",
stl->facet_start[i].vertex[2](0),
stl->facet_start[i].vertex[2](1),
stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
} }
fclose(fp); fclose(fp);
return true;
} }
void bool stl_write_dxf(stl_file *stl, const char *file, char *label)
stl_write_dxf(stl_file *stl, const char *file, char *label) { {
int i; FILE *fp = boost::nowide::fopen(file, "w");
FILE *fp; if (fp == nullptr) {
char *error_msg; BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing";
return false;
}
if (stl->error) return; fprintf(fp, "999\n%s\n", label);
fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n");
fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\
0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n");
fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n");
/* Open the file */ fprintf(fp, "0\nSECTION\n2\nENTITIES\n");
fp = boost::nowide::fopen(file, "w");
if(fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing",
file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
fprintf(fp, "999\n%s\n", label); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); fprintf(fp, "0\n3DFACE\n8\n0\n");
fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\ fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2));
0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2));
fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2));
fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2));
}
fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); fprintf(fp, "0\nENDSEC\n0\nEOF\n");
fclose(fp);
for(i = 0; i < stl->stats.number_of_facets; i++) { return true;
fprintf(fp, "0\n3DFACE\n8\n0\n");
fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n",
stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1),
stl->facet_start[i].vertex[0](2));
fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n",
stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1),
stl->facet_start[i].vertex[1](2));
fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n",
stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1),
stl->facet_start[i].vertex[2](2));
fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n",
stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1),
stl->facet_start[i].vertex[2](2));
}
fprintf(fp, "0\nENDSEC\n0\nEOF\n");
fclose(fp);
}
void
stl_clear_error(stl_file *stl) {
stl->error = 0;
}
void
stl_exit_on_error(stl_file *stl) {
if (!stl->error) return;
stl->error = 0;
stl_close(stl);
exit(1);
}
int
stl_get_error(stl_file *stl) {
return stl->error;
} }

View file

@ -26,6 +26,7 @@
#include <math.h> #include <math.h>
#include <assert.h> #include <assert.h>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp> #include <boost/nowide/cstdio.hpp>
#include <boost/detail/endian.hpp> #include <boost/detail/endian.hpp>
@ -35,351 +36,236 @@
#error "SEEK_SET not defined" #error "SEEK_SET not defined"
#endif #endif
void static FILE* stl_open_count_facets(stl_file *stl, const char *file)
stl_open(stl_file *stl, const char *file) { {
stl_initialize(stl); // Open the file in binary mode first.
stl_count_facets(stl, file); FILE *fp = boost::nowide::fopen(file, "rb");
stl_allocate(stl); if (fp == nullptr) {
stl_read(stl, 0, true); BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading";
if (stl->fp != nullptr) { return nullptr;
fclose(stl->fp); }
stl->fp = nullptr; // Find size of file.
} fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
// Check for binary or ASCII file.
fseek(fp, HEADER_SIZE, SEEK_SET);
unsigned char chtest[128];
if (! fread(chtest, sizeof(chtest), 1, fp)) {
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The input is an empty file: " << file;
fclose(fp);
return nullptr;
}
stl->stats.type = ascii;
for (size_t s = 0; s < sizeof(chtest); s++) {
if (chtest[s] > 127) {
stl->stats.type = binary;
break;
}
}
rewind(fp);
uint32_t num_facets = 0;
// Get the header and the number of facets in the .STL file.
// If the .STL file is binary, then do the following:
if (stl->stats.type == binary) {
// Test if the STL file has the right size.
if (((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) {
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The file " << file << " has the wrong size.";
fclose(fp);
return nullptr;
}
num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET;
// Read the header.
if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79)
stl->stats.header[80] = '\0';
// Read the int following the header. This should contain # of facets.
uint32_t header_num_facets;
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0;
#ifndef BOOST_LITTLE_ENDIAN
// Convert from little endian to big endian.
stl_internal_reverse_quads((char*)&header_num_facets, 4);
#endif /* BOOST_LITTLE_ENDIAN */
if (! header_num_faces_read || num_facets != header_num_facets)
BOOST_LOG_TRIVIAL(info) << "stl_open_count_facets: Warning: File size doesn't match number of facets in the header: " << file;
}
// Otherwise, if the .STL file is ASCII, then do the following:
else
{
// Reopen the file in text mode (for getting correct newlines on Windows)
// fix to silence a warning about unused return value.
// obviously if it fails we have problems....
fp = boost::nowide::freopen(file, "r", fp);
// do another null check to be safe
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading";
fclose(fp);
return nullptr;
}
// Find the number of facets.
char linebuf[100];
int num_lines = 1;
while (fgets(linebuf, 100, fp) != nullptr) {
// Don't count short lines.
if (strlen(linebuf) <= 4)
continue;
// Skip solid/endsolid lines as broken STL file generators may put several of them.
if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0)
continue;
++ num_lines;
}
rewind(fp);
// Get the header.
int i = 0;
for (; i < 80 && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ;
stl->stats.header[i] = '\0'; // Lose the '\n'
stl->stats.header[80] = '\0';
num_facets = num_lines / ASCII_LINES_PER_FACET;
}
stl->stats.number_of_facets += num_facets;
stl->stats.original_num_facets = stl->stats.number_of_facets;
return fp;
} }
void /* Reads the contents of the file pointed to by fp into the stl structure,
stl_initialize(stl_file *stl) { starting at facet first_facet. The second argument says if it's our first
memset(stl, 0, sizeof(stl_file)); time running this for the stl and therefore we should reset our max and min stats. */
stl->stats.volume = -1.0; static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first)
{
if (stl->stats.type == binary)
fseek(fp, HEADER_SIZE, SEEK_SET);
else
rewind(fp);
char normal_buf[3][32];
for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++ i) {
stl_facet facet;
if (stl->stats.type == binary) {
// Read a single facet from a binary .STL file. We assume little-endian architecture!
if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET)
return false;
#ifndef BOOST_LITTLE_ENDIAN
// Convert the loaded little endian data to big endian.
stl_internal_reverse_quads((char*)&facet, 48);
#endif /* BOOST_LITTLE_ENDIAN */
} else {
// Read a single facet from an ASCII .STL file
// skip solid/endsolid
// (in this order, otherwise it won't work when they are paired in the middle of a file)
fscanf(fp, "endsolid%*[^\n]\n");
fscanf(fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid")
// Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs.
int res_normal = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
assert(res_normal == 3);
int res_outer_loop = fscanf(fp, " outer loop");
assert(res_outer_loop == 0);
int res_vertex1 = fscanf(fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
assert(res_vertex1 == 3);
int res_vertex2 = fscanf(fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
assert(res_vertex2 == 3);
int res_vertex3 = fscanf(fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2));
assert(res_vertex3 == 3);
int res_endloop = fscanf(fp, " endloop");
assert(res_endloop == 0);
// There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines.
int res_endfacet = fscanf(fp, " endfacet ");
if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) {
BOOST_LOG_TRIVIAL(error) << "Something is syntactically very wrong with this ASCII STL! ";
return false;
}
// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
// Normal was mangled. Maybe denormals or "not a number" were stored?
// Just reset the normal and silently ignore it.
memset(&facet.normal, 0, sizeof(facet.normal));
}
}
#if 0
// Report close to zero vertex coordinates. Due to the nature of the floating point numbers,
// close to zero values may be represented with singificantly higher precision than the rest of the vertices.
// It may be worth to round these numbers to zero during loading to reduce the number of errors reported
// during the STL import.
for (size_t j = 0; j < 3; ++ j) {
if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f)
printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0));
if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f)
printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1));
if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f)
printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2));
}
#endif
// Write the facet into memory.
stl->facet_start[i] = facet;
stl_facet_stats(stl, facet, first);
}
stl->stats.size = stl->stats.max - stl->stats.min;
stl->stats.bounding_diameter = stl->stats.size.norm();
return true;
}
bool stl_open(stl_file *stl, const char *file)
{
stl->clear();
FILE *fp = stl_open_count_facets(stl, file);
if (fp == nullptr)
return false;
stl_allocate(stl);
bool result = stl_read(stl, fp, 0, true);
fclose(fp);
return result;
} }
#ifndef BOOST_LITTLE_ENDIAN #ifndef BOOST_LITTLE_ENDIAN
extern void stl_internal_reverse_quads(char *buf, size_t cnt); extern void stl_internal_reverse_quads(char *buf, size_t cnt);
#endif /* BOOST_LITTLE_ENDIAN */ #endif /* BOOST_LITTLE_ENDIAN */
void void stl_allocate(stl_file *stl)
stl_count_facets(stl_file *stl, const char *file) { {
long file_size; // Allocate memory for the entire .STL file.
uint32_t header_num_facets; stl->facet_start.assign(stl->stats.number_of_facets, stl_facet());
uint32_t num_facets; // Allocate memory for the neighbors list.
int i; stl->neighbors_start.assign(stl->stats.number_of_facets, stl_neighbors());
size_t s;
unsigned char chtest[128];
int num_lines = 1;
char *error_msg;
if (stl->error) return;
/* Open the file in binary mode first */
stl->fp = boost::nowide::fopen(file, "rb");
if(stl->fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_initialize: Couldn't open %s for reading",
file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
/* Find size of file */
fseek(stl->fp, 0, SEEK_END);
file_size = ftell(stl->fp);
/* Check for binary or ASCII file */
fseek(stl->fp, HEADER_SIZE, SEEK_SET);
if (!fread(chtest, sizeof(chtest), 1, stl->fp)) {
perror("The input is an empty file");
stl->error = 1;
return;
}
stl->stats.type = ascii;
for(s = 0; s < sizeof(chtest); s++) {
if(chtest[s] > 127) {
stl->stats.type = binary;
break;
}
}
rewind(stl->fp);
/* Get the header and the number of facets in the .STL file */
/* If the .STL file is binary, then do the following */
if(stl->stats.type == binary) {
/* Test if the STL file has the right size */
if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0)
|| (file_size < STL_MIN_FILE_SIZE)) {
fprintf(stderr, "The file %s has the wrong size.\n", file);
stl->error = 1;
return;
}
num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET;
/* Read the header */
if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) {
stl->stats.header[80] = '\0';
}
/* Read the int following the header. This should contain # of facets */
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0;
#ifndef BOOST_LITTLE_ENDIAN
// Convert from little endian to big endian.
stl_internal_reverse_quads((char*)&header_num_facets, 4);
#endif /* BOOST_LITTLE_ENDIAN */
if (! header_num_faces_read || num_facets != header_num_facets) {
fprintf(stderr,
"Warning: File size doesn't match number of facets in the header\n");
}
}
/* Otherwise, if the .STL file is ASCII, then do the following */
else {
/* Reopen the file in text mode (for getting correct newlines on Windows) */
// fix to silence a warning about unused return value.
// obviously if it fails we have problems....
stl->fp = boost::nowide::freopen(file, "r", stl->fp);
// do another null check to be safe
if(stl->fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_initialize: Couldn't open %s for reading",
file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
/* Find the number of facets */
char linebuf[100];
while (fgets(linebuf, 100, stl->fp) != NULL) {
/* don't count short lines */
if (strlen(linebuf) <= 4) continue;
/* skip solid/endsolid lines as broken STL file generators may put several of them */
if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue;
++num_lines;
}
rewind(stl->fp);
/* Get the header */
for(i = 0;
(i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++);
stl->stats.header[i] = '\0'; /* Lose the '\n' */
stl->stats.header[80] = '\0';
num_facets = num_lines / ASCII_LINES_PER_FACET;
}
stl->stats.number_of_facets += num_facets;
stl->stats.original_num_facets = stl->stats.number_of_facets;
} }
void void stl_reallocate(stl_file *stl)
stl_allocate(stl_file *stl) { {
if (stl->error) return; stl->facet_start.resize(stl->stats.number_of_facets);
stl->neighbors_start.resize(stl->stats.number_of_facets);
/* Allocate memory for the entire .STL file */
stl->facet_start = (stl_facet*)calloc(stl->stats.number_of_facets,
sizeof(stl_facet));
if(stl->facet_start == NULL) perror("stl_initialize");
stl->stats.facets_malloced = stl->stats.number_of_facets;
/* Allocate memory for the neighbors list */
stl->neighbors_start = (stl_neighbors*)
calloc(stl->stats.number_of_facets, sizeof(stl_neighbors));
if(stl->facet_start == NULL) perror("stl_initialize");
}
void
stl_open_merge(stl_file *stl, char *file_to_merge) {
int num_facets_so_far;
stl_type origStlType;
FILE *origFp;
stl_file stl_to_merge;
if (stl->error) return;
/* Record how many facets we have so far from the first file. We will start putting
facets in the next position. Since we're 0-indexed, it'l be the same position. */
num_facets_so_far = stl->stats.number_of_facets;
/* Record the file type we started with: */
origStlType=stl->stats.type;
/* Record the file pointer too: */
origFp=stl->fp;
/* Initialize the sturucture with zero stats, header info and sizes: */
stl_initialize(&stl_to_merge);
stl_count_facets(&stl_to_merge, file_to_merge);
/* Copy what we need to into stl so that we can read the file_to_merge directly into it
using stl_read: Save the rest of the valuable info: */
stl->stats.type=stl_to_merge.stats.type;
stl->fp=stl_to_merge.fp;
/* Add the number of facets we already have in stl with what we we found in stl_to_merge but
haven't read yet. */
stl->stats.number_of_facets=num_facets_so_far+stl_to_merge.stats.number_of_facets;
/* Allocate enough room for stl->stats.number_of_facets facets and neighbors: */
stl_reallocate(stl);
/* Read the file to merge directly into stl, adding it to what we have already.
Start at num_facets_so_far, the index to the first unused facet. Also say
that this isn't our first time so we should augment stats like min and max
instead of erasing them. */
stl_read(stl, num_facets_so_far, false);
/* Restore the stl information we overwrote (for stl_read) so that it still accurately
reflects the subject part: */
stl->stats.type=origStlType;
stl->fp=origFp;
}
extern void
stl_reallocate(stl_file *stl) {
if (stl->error) return;
/* Reallocate more memory for the .STL file(s) */
stl->facet_start = (stl_facet*)realloc(stl->facet_start, stl->stats.number_of_facets *
sizeof(stl_facet));
if(stl->facet_start == NULL) perror("stl_initialize");
stl->stats.facets_malloced = stl->stats.number_of_facets;
/* Reallocate more memory for the neighbors list */
stl->neighbors_start = (stl_neighbors*)
realloc(stl->neighbors_start, stl->stats.number_of_facets *
sizeof(stl_neighbors));
if(stl->facet_start == NULL) perror("stl_initialize");
}
/* Reads the contents of the file pointed to by stl->fp into the stl structure,
starting at facet first_facet. The second argument says if it's our first
time running this for the stl and therefore we should reset our max and min stats. */
void stl_read(stl_file *stl, int first_facet, bool first) {
stl_facet facet;
if (stl->error) return;
if(stl->stats.type == binary) {
fseek(stl->fp, HEADER_SIZE, SEEK_SET);
} else {
rewind(stl->fp);
}
char normal_buf[3][32];
for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) {
if(stl->stats.type == binary)
/* Read a single facet from a binary .STL file */
{
/* we assume little-endian architecture! */
if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) {
stl->error = 1;
return;
}
#ifndef BOOST_LITTLE_ENDIAN
// Convert the loaded little endian data to big endian.
stl_internal_reverse_quads((char*)&facet, 48);
#endif /* BOOST_LITTLE_ENDIAN */
} else
/* Read a single facet from an ASCII .STL file */
{
// skip solid/endsolid
// (in this order, otherwise it won't work when they are paired in the middle of a file)
fscanf(stl->fp, "endsolid%*[^\n]\n");
fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid")
// Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs.
int res_normal = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
assert(res_normal == 3);
int res_outer_loop = fscanf(stl->fp, " outer loop");
assert(res_outer_loop == 0);
int res_vertex1 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
assert(res_vertex1 == 3);
int res_vertex2 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
assert(res_vertex2 == 3);
int res_vertex3 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2));
assert(res_vertex3 == 3);
int res_endloop = fscanf(stl->fp, " endloop");
assert(res_endloop == 0);
// There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines.
int res_endfacet = fscanf(stl->fp, " endfacet ");
if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) {
perror("Something is syntactically very wrong with this ASCII STL!");
stl->error = 1;
return;
}
// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
// Normal was mangled. Maybe denormals or "not a number" were stored?
// Just reset the normal and silently ignore it.
memset(&facet.normal, 0, sizeof(facet.normal));
}
}
#if 0
// Report close to zero vertex coordinates. Due to the nature of the floating point numbers,
// close to zero values may be represented with singificantly higher precision than the rest of the vertices.
// It may be worth to round these numbers to zero during loading to reduce the number of errors reported
// during the STL import.
for (size_t j = 0; j < 3; ++ j) {
if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f)
printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0));
if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f)
printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1));
if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f)
printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2));
}
#endif
/* Write the facet into memory. */
stl->facet_start[i] = facet;
stl_facet_stats(stl, facet, first);
}
stl->stats.size = stl->stats.max - stl->stats.min;
stl->stats.bounding_diameter = stl->stats.size.norm();
} }
void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first)
{ {
if (stl->error) // While we are going through all of the facets, let's find the
return; // maximum and minimum values for x, y, and z
// While we are going through all of the facets, let's find the if (first) {
// maximum and minimum values for x, y, and z // Initialize the max and min values the first time through
stl->stats.min = facet.vertex[0];
stl->stats.max = facet.vertex[0];
stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs();
stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2)));
first = false;
}
if (first) { // Now find the max and min values.
// Initialize the max and min values the first time through for (size_t i = 0; i < 3; ++ i) {
stl->stats.min = facet.vertex[0]; stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]);
stl->stats.max = facet.vertex[0]; stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]);
stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); }
stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2)));
first = false;
}
// Now find the max and min values.
for (size_t i = 0; i < 3; ++ i) {
stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]);
stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]);
}
}
void stl_close(stl_file *stl)
{
assert(stl->fp == nullptr);
assert(stl->heads == nullptr);
assert(stl->tail == nullptr);
if (stl->facet_start != NULL)
free(stl->facet_start);
if (stl->neighbors_start != NULL)
free(stl->neighbors_start);
if (stl->v_indices != NULL)
free(stl->v_indices);
if (stl->v_shared != NULL)
free(stl->v_shared);
memset(stl, 0, sizeof(stl_file));
} }

View file

@ -25,435 +25,375 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include <boost/log/trivial.hpp>
#include "stl.h" #include "stl.h"
static void stl_rotate(float *x, float *y, const double c, const double s); void stl_verify_neighbors(stl_file *stl)
static float get_area(stl_facet *facet); {
static float get_volume(stl_file *stl); stl->stats.backwards_edges = 0;
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
void for (int j = 0; j < 3; ++ j) {
stl_verify_neighbors(stl_file *stl) { struct stl_edge {
int i; stl_vertex p1;
int j; stl_vertex p2;
stl_edge edge_a; int facet_number;
stl_edge edge_b; };
int neighbor; stl_edge edge_a;
int vnot; edge_a.p1 = stl->facet_start[i].vertex[j];
edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3];
if (stl->error) return; int neighbor = stl->neighbors_start[i].neighbor[j];
if (neighbor == -1)
stl->stats.backwards_edges = 0; continue; // this edge has no neighbor... Continue.
int vnot = stl->neighbors_start[i].which_vertex_not[j];
for(i = 0; i < stl->stats.number_of_facets; i++) { stl_edge edge_b;
for(j = 0; j < 3; j++) { if (vnot < 3) {
edge_a.p1 = stl->facet_start[i].vertex[j]; edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3];
edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3];
neighbor = stl->neighbors_start[i].neighbor[j]; } else {
vnot = stl->neighbors_start[i].which_vertex_not[j]; stl->stats.backwards_edges += 1;
edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3];
if(neighbor == -1) edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3];
continue; /* this edge has no neighbor... Continue. */ }
if(vnot < 3) { if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) {
edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; // These edges should match but they don't. Print results.
edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; BOOST_LOG_TRIVIAL(info) << "edge " << j << " of facet " << i << " doesn't match edge " << (vnot + 1) << " of facet " << neighbor;
} else { stl_write_facet(stl, (char*)"first facet", i);
stl->stats.backwards_edges += 1; stl_write_facet(stl, (char*)"second facet", neighbor);
edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; }
edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; }
} }
if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) {
/* These edges should match but they don't. Print results. */
printf("edge %d of facet %d doesn't match edge %d of facet %d\n",
j, i, vnot + 1, neighbor);
stl_write_facet(stl, (char*)"first facet", i);
stl_write_facet(stl, (char*)"second facet", neighbor);
}
}
}
} }
void stl_translate(stl_file *stl, float x, float y, float z) void stl_translate(stl_file *stl, float x, float y, float z)
{ {
if (stl->error) stl_vertex new_min(x, y, z);
return; stl_vertex shift = new_min - stl->stats.min;
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
stl_vertex new_min(x, y, z); for (int j = 0; j < 3; ++ j)
stl_vertex shift = new_min - stl->stats.min; stl->facet_start[i].vertex[j] += shift;
for (int i = 0; i < stl->stats.number_of_facets; ++ i) stl->stats.min = new_min;
for (int j = 0; j < 3; ++ j) stl->stats.max += shift;
stl->facet_start[i].vertex[j] += shift;
stl->stats.min = new_min;
stl->stats.max += shift;
stl_invalidate_shared_vertices(stl);
} }
/* Translates the stl by x,y,z, relatively from wherever it is currently */ /* Translates the stl by x,y,z, relatively from wherever it is currently */
void stl_translate_relative(stl_file *stl, float x, float y, float z) void stl_translate_relative(stl_file *stl, float x, float y, float z)
{ {
if (stl->error) stl_vertex shift(x, y, z);
return; for (int i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
stl_vertex shift(x, y, z); stl->facet_start[i].vertex[j] += shift;
for (int i = 0; i < stl->stats.number_of_facets; ++ i) stl->stats.min += shift;
for (int j = 0; j < 3; ++ j) stl->stats.max += shift;
stl->facet_start[i].vertex[j] += shift;
stl->stats.min += shift;
stl->stats.max += shift;
stl_invalidate_shared_vertices(stl);
} }
void stl_scale_versor(stl_file *stl, const stl_vertex &versor) void stl_scale_versor(stl_file *stl, const stl_vertex &versor)
{ {
if (stl->error) // Scale extents.
return; auto s = versor.array();
stl->stats.min.array() *= s;
// Scale extents. stl->stats.max.array() *= s;
auto s = versor.array(); // Scale size.
stl->stats.min.array() *= s; stl->stats.size.array() *= s;
stl->stats.max.array() *= s; // Scale volume.
// Scale size. if (stl->stats.volume > 0.0)
stl->stats.size.array() *= s; stl->stats.volume *= versor(0) * versor(1) * versor(2);
// Scale volume. // Scale the mesh.
if (stl->stats.volume > 0.0) for (int i = 0; i < stl->stats.number_of_facets; ++ i)
stl->stats.volume *= versor(0) * versor(1) * versor(2); for (int j = 0; j < 3; ++ j)
// Scale the mesh. stl->facet_start[i].vertex[j].array() *= s;
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
stl->facet_start[i].vertex[j].array() *= s;
stl_invalidate_shared_vertices(stl);
} }
static void calculate_normals(stl_file *stl) static void calculate_normals(stl_file *stl)
{ {
if (stl->error) stl_normal normal;
return; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
stl_calculate_normal(normal, &stl->facet_start[i]);
stl_normal normal; stl_normalize_vector(normal);
for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { stl->facet_start[i].normal = normal;
stl_calculate_normal(normal, &stl->facet_start[i]); }
stl_normalize_vector(normal);
stl->facet_start[i].normal = normal;
}
} }
void static inline void rotate_point_2d(float &x, float &y, const double c, const double s)
stl_rotate_x(stl_file *stl, float angle) { {
int i; double xold = x;
int j; double yold = y;
double radian_angle = (angle / 180.0) * M_PI; x = float(c * xold - s * yold);
double c = cos(radian_angle); y = float(s * xold + c * yold);
double s = sin(radian_angle);
if (stl->error) return;
for(i = 0; i < stl->stats.number_of_facets; i++) {
for(j = 0; j < 3; j++) {
stl_rotate(&stl->facet_start[i].vertex[j](1),
&stl->facet_start[i].vertex[j](2), c, s);
}
}
stl_get_size(stl);
calculate_normals(stl);
} }
void void stl_rotate_x(stl_file *stl, float angle)
stl_rotate_y(stl_file *stl, float angle) { {
int i; double radian_angle = (angle / 180.0) * M_PI;
int j; double c = cos(radian_angle);
double radian_angle = (angle / 180.0) * M_PI; double s = sin(radian_angle);
double c = cos(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
double s = sin(radian_angle); for (int j = 0; j < 3; ++ j)
rotate_point_2d(stl->facet_start[i].vertex[j](1), stl->facet_start[i].vertex[j](2), c, s);
if (stl->error) return; stl_get_size(stl);
calculate_normals(stl);
for(i = 0; i < stl->stats.number_of_facets; i++) {
for(j = 0; j < 3; j++) {
stl_rotate(&stl->facet_start[i].vertex[j](2),
&stl->facet_start[i].vertex[j](0), c, s);
}
}
stl_get_size(stl);
calculate_normals(stl);
} }
void void stl_rotate_y(stl_file *stl, float angle)
stl_rotate_z(stl_file *stl, float angle) { {
int i; double radian_angle = (angle / 180.0) * M_PI;
int j; double c = cos(radian_angle);
double radian_angle = (angle / 180.0) * M_PI; double s = sin(radian_angle);
double c = cos(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
double s = sin(radian_angle); for (int j = 0; j < 3; ++ j)
rotate_point_2d(stl->facet_start[i].vertex[j](2), stl->facet_start[i].vertex[j](0), c, s);
if (stl->error) return; stl_get_size(stl);
calculate_normals(stl);
for(i = 0; i < stl->stats.number_of_facets; i++) {
for(j = 0; j < 3; j++) {
stl_rotate(&stl->facet_start[i].vertex[j](0),
&stl->facet_start[i].vertex[j](1), c, s);
}
}
stl_get_size(stl);
calculate_normals(stl);
} }
void stl_rotate_z(stl_file *stl, float angle)
{
double radian_angle = (angle / 180.0) * M_PI;
double c = cos(radian_angle);
double s = sin(radian_angle);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
rotate_point_2d(stl->facet_start[i].vertex[j](0), stl->facet_start[i].vertex[j](1), c, s);
stl_get_size(stl);
calculate_normals(stl);
}
void its_rotate_x(indexed_triangle_set &its, float angle)
{
double radian_angle = (angle / 180.0) * M_PI;
double c = cos(radian_angle);
double s = sin(radian_angle);
for (stl_vertex &v : its.vertices)
rotate_point_2d(v(1), v(2), c, s);
}
static void void its_rotate_y(indexed_triangle_set& its, float angle)
stl_rotate(float *x, float *y, const double c, const double s) { {
double xold = *x; double radian_angle = (angle / 180.0) * M_PI;
double yold = *y; double c = cos(radian_angle);
*x = float(c * xold - s * yold); double s = sin(radian_angle);
*y = float(s * xold + c * yold); for (stl_vertex& v : its.vertices)
rotate_point_2d(v(2), v(0), c, s);
}
void its_rotate_z(indexed_triangle_set& its, float angle)
{
double radian_angle = (angle / 180.0) * M_PI;
double c = cos(radian_angle);
double s = sin(radian_angle);
for (stl_vertex& v : its.vertices)
rotate_point_2d(v(0), v(1), c, s);
} }
void stl_get_size(stl_file *stl) void stl_get_size(stl_file *stl)
{ {
if (stl->error || stl->stats.number_of_facets == 0) if (stl->stats.number_of_facets == 0)
return; return;
stl->stats.min = stl->facet_start[0].vertex[0]; stl->stats.min = stl->facet_start[0].vertex[0];
stl->stats.max = stl->stats.min; stl->stats.max = stl->stats.min;
for (int i = 0; i < stl->stats.number_of_facets; ++ i) { for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
const stl_facet &face = stl->facet_start[i]; const stl_facet &face = stl->facet_start[i];
for (int j = 0; j < 3; ++ j) { for (int j = 0; j < 3; ++ j) {
stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]);
stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]);
} }
} }
stl->stats.size = stl->stats.max - stl->stats.min; stl->stats.size = stl->stats.max - stl->stats.min;
stl->stats.bounding_diameter = stl->stats.size.norm(); stl->stats.bounding_diameter = stl->stats.size.norm();
} }
void stl_mirror_xy(stl_file *stl) void stl_mirror_xy(stl_file *stl)
{ {
if (stl->error) for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
return; for (int j = 0; j < 3; ++ j)
stl->facet_start[i].vertex[j](2) *= -1.0;
for(int i = 0; i < stl->stats.number_of_facets; i++) { float temp_size = stl->stats.min(2);
for(int j = 0; j < 3; j++) { stl->stats.min(2) = stl->stats.max(2);
stl->facet_start[i].vertex[j](2) *= -1.0; stl->stats.max(2) = temp_size;
} stl->stats.min(2) *= -1.0;
} stl->stats.max(2) *= -1.0;
float temp_size = stl->stats.min(2); stl_reverse_all_facets(stl);
stl->stats.min(2) = stl->stats.max(2); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
stl->stats.max(2) = temp_size;
stl->stats.min(2) *= -1.0;
stl->stats.max(2) *= -1.0;
stl_reverse_all_facets(stl);
stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
} }
void stl_mirror_yz(stl_file *stl) void stl_mirror_yz(stl_file *stl)
{ {
if (stl->error) return; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; j++)
for (int i = 0; i < stl->stats.number_of_facets; i++) { stl->facet_start[i].vertex[j](0) *= -1.0;
for (int j = 0; j < 3; j++) { float temp_size = stl->stats.min(0);
stl->facet_start[i].vertex[j](0) *= -1.0; stl->stats.min(0) = stl->stats.max(0);
} stl->stats.max(0) = temp_size;
} stl->stats.min(0) *= -1.0;
float temp_size = stl->stats.min(0); stl->stats.max(0) *= -1.0;
stl->stats.min(0) = stl->stats.max(0); stl_reverse_all_facets(stl);
stl->stats.max(0) = temp_size; stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
stl->stats.min(0) *= -1.0;
stl->stats.max(0) *= -1.0;
stl_reverse_all_facets(stl);
stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
} }
void stl_mirror_xz(stl_file *stl) void stl_mirror_xz(stl_file *stl)
{ {
if (stl->error) for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
return; for (int j = 0; j < 3; ++ j)
stl->facet_start[i].vertex[j](1) *= -1.0;
for (int i = 0; i < stl->stats.number_of_facets; i++) { float temp_size = stl->stats.min(1);
for (int j = 0; j < 3; j++) { stl->stats.min(1) = stl->stats.max(1);
stl->facet_start[i].vertex[j](1) *= -1.0; stl->stats.max(1) = temp_size;
} stl->stats.min(1) *= -1.0;
} stl->stats.max(1) *= -1.0;
float temp_size = stl->stats.min(1); stl_reverse_all_facets(stl);
stl->stats.min(1) = stl->stats.max(1); stl->stats.facets_reversed -= stl->stats.number_of_facets; // for not altering stats
stl->stats.max(1) = temp_size;
stl->stats.min(1) *= -1.0;
stl->stats.max(1) *= -1.0;
stl_reverse_all_facets(stl);
stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
}
static float get_volume(stl_file *stl)
{
if (stl->error)
return 0;
// Choose a point, any point as the reference.
stl_vertex p0 = stl->facet_start[0].vertex[0];
float volume = 0.f;
for(uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
// Do dot product to get distance from point to plane.
float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0);
float area = get_area(&stl->facet_start[i]);
volume += (area * height) / 3.0f;
}
return volume;
}
void stl_calculate_volume(stl_file *stl)
{
if (stl->error) return;
stl->stats.volume = get_volume(stl);
if(stl->stats.volume < 0.0) {
stl_reverse_all_facets(stl);
stl->stats.volume = -stl->stats.volume;
}
} }
static float get_area(stl_facet *facet) static float get_area(stl_facet *facet)
{ {
/* cast to double before calculating cross product because large coordinates /* cast to double before calculating cross product because large coordinates
can result in overflowing product can result in overflowing product
(bad area is responsible for bad volume and bad facets reversal) */ (bad area is responsible for bad volume and bad facets reversal) */
double cross[3][3]; double cross[3][3];
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) -
((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1)));
cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) -
((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2)));
cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) -
((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0)));
} }
stl_normal sum; stl_normal sum;
sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; sum(0) = cross[0][0] + cross[1][0] + cross[2][0];
sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; sum(1) = cross[0][1] + cross[1][1] + cross[2][1];
sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; sum(2) = cross[0][2] + cross[1][2] + cross[2][2];
// This should already be done. But just in case, let's do it again. // This should already be done. But just in case, let's do it again.
//FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy. //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy.
stl_normal n; stl_normal n;
stl_calculate_normal(n, facet); stl_calculate_normal(n, facet);
stl_normalize_vector(n); stl_normalize_vector(n);
return 0.5f * n.dot(sum); return 0.5f * n.dot(sum);
} }
void stl_repair(stl_file *stl, static float get_volume(stl_file *stl)
int fixall_flag, {
int exact_flag, // Choose a point, any point as the reference.
int tolerance_flag, stl_vertex p0 = stl->facet_start[0].vertex[0];
float tolerance, float volume = 0.f;
int increment_flag, for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
float increment, // Do dot product to get distance from point to plane.
int nearby_flag, float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0);
int iterations, float area = get_area(&stl->facet_start[i]);
int remove_unconnected_flag, volume += (area * height) / 3.0f;
int fill_holes_flag, }
int normal_directions_flag, return volume;
int normal_values_flag, }
int reverse_all_flag,
int verbose_flag) { void stl_calculate_volume(stl_file *stl)
{
int i; stl->stats.volume = get_volume(stl);
int last_edges_fixed = 0; if (stl->stats.volume < 0.0) {
stl_reverse_all_facets(stl);
if (stl->error) return; stl->stats.volume = -stl->stats.volume;
}
if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag }
|| fill_holes_flag || normal_directions_flag) {
if (verbose_flag) void stl_repair(
printf("Checking exact...\n"); stl_file *stl,
exact_flag = 1; bool fixall_flag,
stl_check_facets_exact(stl); bool exact_flag,
stl->stats.facets_w_1_bad_edge = bool tolerance_flag,
(stl->stats.connected_facets_2_edge - float tolerance,
stl->stats.connected_facets_3_edge); bool increment_flag,
stl->stats.facets_w_2_bad_edge = float increment,
(stl->stats.connected_facets_1_edge - bool nearby_flag,
stl->stats.connected_facets_2_edge); int iterations,
stl->stats.facets_w_3_bad_edge = bool remove_unconnected_flag,
(stl->stats.number_of_facets - bool fill_holes_flag,
stl->stats.connected_facets_1_edge); bool normal_directions_flag,
} bool normal_values_flag,
bool reverse_all_flag,
if(nearby_flag || fixall_flag) { bool verbose_flag)
if(!tolerance_flag) { {
tolerance = stl->stats.shortest_edge; if (exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) {
} if (verbose_flag)
if(!increment_flag) { printf("Checking exact...\n");
increment = stl->stats.bounding_diameter / 10000.0; exact_flag = true;
} stl_check_facets_exact(stl);
stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge);
if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { stl->stats.facets_w_2_bad_edge = (stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge);
for(i = 0; i < iterations; i++) { stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_edge);
if(stl->stats.connected_facets_3_edge < }
stl->stats.number_of_facets) {
if (verbose_flag) if (nearby_flag || fixall_flag) {
printf("\ if (! tolerance_flag)
Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance = stl->stats.shortest_edge;
tolerance, i + 1, iterations); if (! increment_flag)
stl_check_facets_nearby(stl, tolerance); increment = stl->stats.bounding_diameter / 10000.0;
if (verbose_flag) }
printf(" Fixed %d edges.\n",
stl->stats.edges_fixed - last_edges_fixed); if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
last_edges_fixed = stl->stats.edges_fixed; int last_edges_fixed = 0;
tolerance += increment; for (int i = 0; i < iterations; ++ i) {
} else { if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
if (verbose_flag) if (verbose_flag)
printf("\ printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations);
All facets connected. No further nearby check necessary.\n"); stl_check_facets_nearby(stl, tolerance);
break; if (verbose_flag)
} printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed);
} last_edges_fixed = stl->stats.edges_fixed;
} else { tolerance += increment;
if (verbose_flag) } else {
printf("All facets connected. No nearby check necessary.\n"); if (verbose_flag)
} printf("All facets connected. No further nearby check necessary.\n");
} break;
}
if(remove_unconnected_flag || fixall_flag || fill_holes_flag) { }
if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { } else if (verbose_flag)
if (verbose_flag) printf("All facets connected. No nearby check necessary.\n");
printf("Removing unconnected facets...\n");
stl_remove_unconnected_facets(stl); if (remove_unconnected_flag || fixall_flag || fill_holes_flag) {
} else if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
if (verbose_flag) if (verbose_flag)
printf("No unconnected need to be removed.\n"); printf("Removing unconnected facets...\n");
} stl_remove_unconnected_facets(stl);
} else if (verbose_flag)
if(fill_holes_flag || fixall_flag) { printf("No unconnected need to be removed.\n");
if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { }
if (verbose_flag)
printf("Filling holes...\n"); if (fill_holes_flag || fixall_flag) {
stl_fill_holes(stl); if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
} else if (verbose_flag)
if (verbose_flag) printf("Filling holes...\n");
printf("No holes need to be filled.\n"); stl_fill_holes(stl);
} } else if (verbose_flag)
printf("No holes need to be filled.\n");
if(reverse_all_flag) { }
if (verbose_flag)
printf("Reversing all facets...\n"); if (reverse_all_flag) {
stl_reverse_all_facets(stl); if (verbose_flag)
} printf("Reversing all facets...\n");
stl_reverse_all_facets(stl);
if(normal_directions_flag || fixall_flag) { }
if (verbose_flag)
printf("Checking normal directions...\n"); if (normal_directions_flag || fixall_flag) {
stl_fix_normal_directions(stl); if (verbose_flag)
} printf("Checking normal directions...\n");
stl_fix_normal_directions(stl);
if(normal_values_flag || fixall_flag) { }
if (verbose_flag)
printf("Checking normal values...\n"); if (normal_values_flag || fixall_flag) {
stl_fix_normal_values(stl); if (verbose_flag)
} printf("Checking normal values...\n");
stl_fix_normal_values(stl);
/* Always calculate the volume. It shouldn't take too long */ }
if (verbose_flag)
printf("Calculating volume...\n"); // Always calculate the volume. It shouldn't take too long.
stl_calculate_volume(stl); if (verbose_flag)
printf("Calculating volume...\n");
if(exact_flag) { stl_calculate_volume(stl);
if (verbose_flag)
printf("Verifying neighbors...\n"); if (exact_flag) {
stl_verify_neighbors(stl); if (verbose_flag)
} printf("Verifying neighbors...\n");
stl_verify_neighbors(stl);
}
} }

View file

@ -48,6 +48,9 @@ set(LIBNEST2D_SRCFILES
${SRC_DIR}/libnest2d/optimizer.hpp ${SRC_DIR}/libnest2d/optimizer.hpp
${SRC_DIR}/libnest2d/utils/metaloop.hpp ${SRC_DIR}/libnest2d/utils/metaloop.hpp
${SRC_DIR}/libnest2d/utils/rotfinder.hpp ${SRC_DIR}/libnest2d/utils/rotfinder.hpp
${SRC_DIR}/libnest2d/utils/rotcalipers.hpp
${SRC_DIR}/libnest2d/utils/bigint.hpp
${SRC_DIR}/libnest2d/utils/rational.hpp
${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp ${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp
${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp ${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp
${SRC_DIR}/libnest2d/placers/nfpplacer.hpp ${SRC_DIR}/libnest2d/placers/nfpplacer.hpp
@ -70,12 +73,13 @@ if(TBB_FOUND)
# The Intel TBB library will use the std::exception_ptr feature of C++11. # The Intel TBB library will use the std::exception_ptr feature of C++11.
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
target_link_libraries(libnest2d INTERFACE tbb) find_package(Threads REQUIRED)
# The following breaks compilation on Visual Studio in Debug mode. target_link_libraries(libnest2d INTERFACE
#find_package(Threads REQUIRED) tbb # VS debug mode needs linking this way:
#target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} # ${TBB_LIBRARIES}
# Threads::Threads ${CMAKE_DL_LIBS}
# ) Threads::Threads
)
else() else()
find_package(OpenMP QUIET) find_package(OpenMP QUIET)
@ -92,10 +96,11 @@ endif()
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES}) add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend) target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend)
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER}) add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer) target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer)
#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) # target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
target_include_directories(libnest2d INTERFACE ${SRC_DIR}) target_include_directories(libnest2d INTERFACE ${SRC_DIR})
if(NOT LIBNEST2D_HEADER_ONLY) if(NOT LIBNEST2D_HEADER_ONLY)

View file

@ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer<Box>;
// This supports only box shaped bins // This supports only box shaped bins
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>; using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif
template<class Placer = NfpPlacer, template<class Placer = NfpPlacer,
class Selector = FirstFitSelection, class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator> class Iterator = std::vector<Item>::iterator>
@ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to,
return nester.execute(from, to); return nester.execute(from, to);
} }
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, dist, pconf, sconf);
}
template<class Placer = NfpPlacer, template<class Placer = NfpPlacer,
class Selector = FirstFitSelection, class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator> class Iterator = std::vector<Item>::iterator>
@ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to,
return nester.execute(from, to); return nester.execute(from, to);
} }
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
extern template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, dist, pconf, sconf);
}
template<class Placer = NfpPlacer, template<class Placer = NfpPlacer,
class Selector = FirstFitSelection, class Selector = FirstFitSelection,
class Container = std::vector<Item>> class Container = std::vector<Item>>
@ -105,71 +139,6 @@ PackGroup nest(Container&& cont,
bin, prg, scond, dist, pconf, sconf); bin, prg, scond, dist, pconf, sconf);
} }
#ifdef LIBNEST2D_STATIC
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
std::vector<Item>& cont,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
std::vector<Item>& cont,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
std::vector<Item>&& cont,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
std::vector<Item>&& cont,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
#endif
} }
#endif // LIBNEST2D_H #endif // LIBNEST2D_H

View file

@ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a
# ${clipper_library_BINARY_DIR} # ${clipper_library_BINARY_DIR}
# ) # )
add_library(ClipperBackend STATIC add_library(clipperBackend STATIC
${clipper_library_SOURCE_DIR}/clipper.cpp ${clipper_library_SOURCE_DIR}/clipper.cpp
${clipper_library_SOURCE_DIR}/clipper.hpp) ${clipper_library_SOURCE_DIR}/clipper.hpp)
target_include_directories(ClipperBackend INTERFACE target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR})
${clipper_library_SOURCE_DIR})
else() else()
message(FATAL_ERROR "Can't find clipper library and no SVN client found to download. message(FATAL_ERROR "Can't find clipper library and no SVN client found to download.
You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.") You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.")
endif() endif()
else() else()
add_library(ClipperBackend INTERFACE) add_library(clipperBackend INTERFACE)
target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper) target_link_libraries(clipperBackend INTERFACE Clipper::Clipper)
endif() endif()
else() else()
# set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE) # set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE)
@ -69,6 +68,6 @@ target_link_libraries(clipperBackend INTERFACE Boost::boost )
target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
# And finally plug the ClipperBackend into libnest2d # And finally plug the clipperBackend into libnest2d
#target_link_libraries(libnest2d INTERFACE ClipperBackend) # target_link_libraries(libnest2d INTERFACE clipperBackend)

View file

@ -12,13 +12,13 @@ struct Polygon {
inline Polygon() = default; inline Polygon() = default;
inline explicit Polygon(const Path& cont): Contour(cont) {} inline explicit Polygon(const Path& cont): Contour(cont) {}
inline explicit Polygon(const Paths& holes): // inline explicit Polygon(const Paths& holes):
Holes(holes) {} // Holes(holes) {}
inline Polygon(const Path& cont, const Paths& holes): inline Polygon(const Path& cont, const Paths& holes):
Contour(cont), Holes(holes) {} Contour(cont), Holes(holes) {}
inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {}
inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} // inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
inline Polygon(Path&& cont, Paths&& holes): inline Polygon(Path&& cont, Paths&& holes):
Contour(std::move(cont)), Holes(std::move(holes)) {} Contour(std::move(cont)), Holes(std::move(holes)) {}
}; };
@ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) {
return p; return p;
} }
inline IntPoint operator -(IntPoint& p ) { inline IntPoint operator -(const IntPoint& p ) {
IntPoint ret = p; IntPoint ret = p;
ret.X = -ret.X; ret.X = -ret.X;
ret.Y = -ret.Y; ret.Y = -ret.Y;

View file

@ -20,43 +20,23 @@ using PathImpl = ClipperLib::Path;
using HoleStore = ClipperLib::Paths; using HoleStore = ClipperLib::Paths;
using PolygonImpl = ClipperLib::Polygon; using PolygonImpl = ClipperLib::Polygon;
// Type of coordinate units used by Clipper
template<> struct CoordType<PointImpl> {
using Type = ClipperLib::cInt;
};
// Type of point used by Clipper
template<> struct PointType<PolygonImpl> {
using Type = PointImpl;
};
template<> struct PointType<PathImpl> {
using Type = PointImpl;
};
template<> struct PointType<PointImpl> {
using Type = PointImpl;
};
template<> struct CountourType<PolygonImpl> {
using Type = PathImpl;
};
template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; }; template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
template<> struct ShapeTag<PathImpl> { using Type = PathTag; }; template<> struct ShapeTag<PathImpl> { using Type = PathTag; };
template<> struct ShapeTag<PointImpl> { using Type = PointTag; }; template<> struct ShapeTag<PointImpl> { using Type = PointTag; };
template<> struct ShapeTag<TMultiShape<PolygonImpl>> { // Type of coordinate units used by Clipper. Enough to specialize for point,
using Type = MultiPolygonTag; // the rest of the types will work (Path, Polygon)
}; template<> struct CoordType<PointImpl> { using Type = ClipperLib::cInt; };
template<> struct PointType<TMultiShape<PolygonImpl>> { // Enough to specialize for path, it will work for multishape and Polygon
using Type = PointImpl; template<> struct PointType<PathImpl> { using Type = PointImpl; };
};
template<> struct HolesContainer<PolygonImpl> { // This is crucial. CountourType refers to itself by default, so we don't have
using Type = ClipperLib::Paths; // to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
}; template<> struct ContourType<PolygonImpl> { using Type = PathImpl; };
// The holes are contained in Clipper::Paths
template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; };
namespace pointlike { namespace pointlike {
@ -86,39 +66,11 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p)
} }
// Using the libnest2d default area implementation
#define DISABLE_BOOST_AREA #define DISABLE_BOOST_AREA
namespace _smartarea {
template<Orientation o>
inline double area(const PolygonImpl& /*sh*/) {
return std::nan("");
}
template<>
inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
return std::accumulate(sh.Holes.begin(), sh.Holes.end(),
ClipperLib::Area(sh.Contour),
[](double a, const ClipperLib::Path& pt){
return a + ClipperLib::Area(pt);
});
}
template<>
inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
return -area<Orientation::COUNTER_CLOCKWISE>(sh);
}
}
namespace shapelike { namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline double area(const PolygonImpl& sh, const PolygonTag&)
{
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
}
template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance) template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
{ {
#define DISABLE_BOOST_OFFSET #define DISABLE_BOOST_OFFSET
@ -200,43 +152,16 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
{ {
PolygonImpl p; PolygonImpl p;
p.Contour = path; p.Contour = path;
// Expecting that the coordinate system Y axis is positive in upwards
// direction
if(ClipperLib::Orientation(p.Contour)) {
// Not clockwise then reverse the b*tch
ClipperLib::ReversePath(p.Contour);
}
p.Holes = holes; p.Holes = holes;
for(auto& h : p.Holes) {
if(!ClipperLib::Orientation(h)) {
ClipperLib::ReversePath(h);
}
}
return p; return p;
} }
template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
PolygonImpl p; PolygonImpl p;
p.Contour.swap(path); p.Contour.swap(path);
// Expecting that the coordinate system Y axis is positive in upwards
// direction
if(ClipperLib::Orientation(p.Contour)) {
// Not clockwise then reverse the b*tch
ClipperLib::ReversePath(p.Contour);
}
p.Holes.swap(holes); p.Holes.swap(holes);
for(auto& h : p.Holes) {
if(!ClipperLib::Orientation(h)) {
ClipperLib::ReversePath(h);
}
}
return p; return p;
} }
@ -314,13 +239,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads)
} // namespace shapelike } // namespace shapelike
#define DISABLE_BOOST_NFP_MERGE #define DISABLE_BOOST_NFP_MERGE
inline std::vector<PolygonImpl> clipper_execute( inline TMultiShape<PolygonImpl> clipper_execute(
ClipperLib::Clipper& clipper, ClipperLib::Clipper& clipper,
ClipperLib::ClipType clipType, ClipperLib::ClipType clipType,
ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
{ {
shapelike::Shapes<PolygonImpl> retv; TMultiShape<PolygonImpl> retv;
ClipperLib::PolyTree result; ClipperLib::PolyTree result;
clipper.Execute(clipType, result, subjFillType, clipFillType); clipper.Execute(clipType, result, subjFillType, clipFillType);
@ -370,8 +295,8 @@ inline std::vector<PolygonImpl> clipper_execute(
namespace nfp { namespace nfp {
template<> inline std::vector<PolygonImpl> template<> inline TMultiShape<PolygonImpl>
merge(const std::vector<PolygonImpl>& shapes) merge(const TMultiShape<PolygonImpl>& shapes)
{ {
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
@ -394,6 +319,8 @@ merge(const std::vector<PolygonImpl>& shapes)
} }
#define DISABLE_BOOST_CONVEX_HULL
//#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_SERIALIZE
//#define DISABLE_BOOST_UNSERIALIZE //#define DISABLE_BOOST_UNSERIALIZE

View file

@ -9,6 +9,7 @@
#include <string> #include <string>
#include <cmath> #include <cmath>
#include <type_traits> #include <type_traits>
#include <limits>
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
#define BP2D_NOEXCEPT #define BP2D_NOEXCEPT
@ -197,6 +198,33 @@ public:
} }
}; };
struct ScalarTag {};
struct BigIntTag {};
struct RationalTag {};
template<class T> struct _NumTag {
using Type =
enable_if_t<std::is_arithmetic<T>::value, ScalarTag>;
};
template<class T> using NumTag = typename _NumTag<remove_cvref_t<T>>::Type;
/// A local version for abs that is garanteed to work with libnest2d types
template <class T> inline T abs(const T& v, ScalarTag)
{
return std::abs(v);
}
template<class T> inline T abs(const T& v) { return abs(v, NumTag<T>()); }
template<class T2, class T1> inline T2 cast(const T1& v, ScalarTag, ScalarTag)
{
return static_cast<T2>(v);
}
template<class T2, class T1> inline T2 cast(const T1& v) {
return cast<T2, T1>(v, NumTag<T1>(), NumTag<T2>());
}
} }
#endif // LIBNEST2D_CONFIG_HPP #endif // LIBNEST2D_CONFIG_HPP

View file

@ -7,45 +7,125 @@
#include <array> #include <array>
#include <vector> #include <vector>
#include <numeric> #include <numeric>
#include <limits>
#include <iterator> #include <iterator>
#include <cmath> #include <cmath>
#include <cstdint>
#include "common.hpp" #include <libnest2d/common.hpp>
namespace libnest2d { namespace libnest2d {
// Meta tags for different geometry concepts.
struct PointTag {};
struct PolygonTag {};
struct PathTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
/// Meta-function to derive the tag of a shape type.
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
/// Tag<S> will be used instead of `typename ShapeTag<S>::Type`
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
/// Meta function to derive the contour type for a polygon which could be itself
template<class RawShape> struct ContourType { using Type = RawShape; };
/// TContour<RawShape> instead of `typename ContourType<RawShape>::type`
template<class RawShape>
using TContour = typename ContourType<remove_cvref_t<RawShape>>::Type;
/// Getting the type of point structure used by a shape.
template<class Sh> struct PointType {
using Type = typename PointType<TContour<Sh>>::Type;
};
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
/// Getting the coordinate data type for a geometry class. /// Getting the coordinate data type for a geometry class.
template<class GeomClass> struct CoordType { using Type = long; }; template<class GeomClass> struct CoordType {
using Type = typename CoordType<TPoint<GeomClass>>::Type;
};
/// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`. /// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
template<class GeomType> template<class GeomType>
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type; using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
/// Getting the type of point structure used by a shape. /// Getting the computation type for a certain geometry type.
template<class Sh> struct PointType { using Type = typename Sh::PointType; }; /// It is the coordinate type by default but it is advised that a type with
/// larger precision and (or) range is specified.
template<class T, bool = std::is_arithmetic<T>::value> struct ComputeType {};
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`. /// A compute type is introduced to hold the results of computations on
template<class Shape> /// coordinates and points. It should be larger in range than the coordinate
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type; /// type or the range of coordinates should be limited to not loose precision.
template<class GeomClass> struct ComputeType<GeomClass, false> {
using Type = typename ComputeType<TCoord<GeomClass>>::Type;
};
/// libnest2d will choose a default compute type for various coordinate types
/// if the backend has not specified anything.
template<class T> struct DoublePrecision { using Type = T; };
template<> struct DoublePrecision<int8_t> { using Type = int16_t; };
template<> struct DoublePrecision<int16_t> { using Type = int32_t; };
template<> struct DoublePrecision<int32_t> { using Type = int64_t; };
template<> struct DoublePrecision<float> { using Type = double; };
template<> struct DoublePrecision<double> { using Type = long double; };
template<class I> struct ComputeType<I, true> {
using Type = typename DoublePrecision<I>::Type;
};
template<class RawShape> struct CountourType { using Type = RawShape; }; /// TCompute<T> shorthand for `typename ComputeType<T>::Type`
template<class T> using TCompute = typename ComputeType<remove_cvref_t<T>>::Type;
template<class RawShape>
using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type;
/// A meta function to derive a container type for holes in a polygon
template<class RawShape> template<class RawShape>
struct HolesContainer { using Type = std::vector<TContour<RawShape>>; }; struct HolesContainer { using Type = std::vector<TContour<RawShape>>; };
/// Shorthand for `typename HolesContainer<RawShape>::Type`
template<class RawShape> template<class RawShape>
using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type; using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type;
/*
* TContour, TPoint, TCoord and TCompute should be usable for any type for which
* it makes sense. For example, the point type could be derived from the contour,
* the polygon and (or) the multishape as well. The coordinate type also and
* including the point type. TCoord<Polygon>, TCoord<Path>, TCoord<Point> are
* all valid types and derives the coordinate type of template argument Polygon,
* Path and Point. This is also true for TCompute, but it can also take the
* coordinate type as argument.
*/
template<class RawShape> /*
struct LastPointIsFirst { static const bool Value = true; }; * A Multi shape concept is also introduced. A multi shape is something that
* can contain the result of an operation where the input is one polygon and
* the result could be many polygons or path -> paths. The MultiShape should be
* a container type. If the backend does not specialize the MultiShape template,
* a default multi shape container will be used.
*/
/// The default multi shape container.
template<class S> struct DefaultMultiShape: public std::vector<S> {
using Tag = MultiPolygonTag;
template<class...Args> DefaultMultiShape(Args&&...args):
std::vector<S>(std::forward<Args>(args)...) {}
};
/// The MultiShape Type trait which gets the container type for a geometry type.
template<class S> struct MultiShape { using Type = DefaultMultiShape<S>; };
/// use TMultiShape<S> instead of `typename MultiShape<S>::Type`
template<class S>
using TMultiShape = typename MultiShape<remove_cvref_t<S>>::Type;
// A specialization of ContourType to work with the default multishape type
template<class S> struct ContourType<DefaultMultiShape<S>> {
using Type = typename ContourType<S>::Type;
};
enum class Orientation { enum class Orientation {
CLOCKWISE, CLOCKWISE,
@ -59,6 +139,11 @@ struct OrientationType {
static const Orientation Value = Orientation::CLOCKWISE; static const Orientation Value = Orientation::CLOCKWISE;
}; };
template<class T> inline /*constexpr*/ bool is_clockwise() {
return OrientationType<TContour<T>>::Value == Orientation::CLOCKWISE;
}
/** /**
* \brief A point pair base class for other point pairs (segment, box, ...). * \brief A point pair base class for other point pairs (segment, box, ...).
* \tparam RawPoint The actual point type to use. * \tparam RawPoint The actual point type to use.
@ -69,21 +154,6 @@ struct PointPair {
RawPoint p2; RawPoint p2;
}; };
struct PointTag {};
struct PolygonTag {};
struct PathTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
/// Meta-functions to derive the tags
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
template<class S> struct MultiShape { using Type = std::vector<S>; };
template<class S>
using TMultiShape =typename MultiShape<remove_cvref_t<S>>::Type;
/** /**
* \brief An abstraction of a box; * \brief An abstraction of a box;
*/ */
@ -114,11 +184,16 @@ public:
inline RawPoint center() const BP2D_NOEXCEPT; inline RawPoint center() const BP2D_NOEXCEPT;
inline double area() const BP2D_NOEXCEPT { template<class Unit = TCompute<RawPoint>>
return double(width()*height()); inline Unit area() const BP2D_NOEXCEPT {
return Unit(width())*height();
} }
}; };
template<class S> struct PointType<_Box<S>> {
using Type = typename _Box<S>::PointType;
};
template<class RawPoint> template<class RawPoint>
class _Circle { class _Circle {
RawPoint center_; RawPoint center_;
@ -129,7 +204,6 @@ public:
using PointType = RawPoint; using PointType = RawPoint;
_Circle() = default; _Circle() = default;
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {} _Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; }
@ -137,12 +211,16 @@ public:
inline double radius() const BP2D_NOEXCEPT { return radius_; } inline double radius() const BP2D_NOEXCEPT { return radius_; }
inline void radius(double r) { radius_ = r; } inline void radius(double r) { radius_ = r; }
inline double area() const BP2D_NOEXCEPT { inline double area() const BP2D_NOEXCEPT {
return 2.0*Pi*radius_*radius_; return Pi_2 * radius_ * radius_;
} }
}; };
template<class S> struct PointType<_Circle<S>> {
using Type = typename _Circle<S>::PointType;
};
/** /**
* \brief An abstraction of a directed line segment with two points. * \brief An abstraction of a directed line segment with two points.
*/ */
@ -185,7 +263,12 @@ public:
inline Radians angleToXaxis() const; inline Radians angleToXaxis() const;
/// The length of the segment in the measure of the coordinate system. /// The length of the segment in the measure of the coordinate system.
inline double length(); template<class Unit = TCompute<RawPoint>> inline Unit sqlength() const;
};
template<class S> struct PointType<_Segment<S>> {
using Type = typename _Circle<S>::PointType;
}; };
// This struct serves almost as a namespace. The only difference is that is can // This struct serves almost as a namespace. The only difference is that is can
@ -216,33 +299,56 @@ inline TCoord<RawPoint>& y(RawPoint& p)
return p.y(); return p.y();
} }
template<class RawPoint> template<class RawPoint, class Unit = TCompute<RawPoint>>
inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2)
{ {
static_assert(always_false<RawPoint>::value, auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2));
"PointLike::distance(point, point) unimplemented!"); Unit a = (x2 - x1), b = (y2 - y1);
return 0; return a * a + b * b;
} }
template<class RawPoint> template<class RawPoint>
inline double distance(const RawPoint& /*p1*/, inline double distance(const RawPoint& p1, const RawPoint& p2)
const _Segment<RawPoint>& /*s*/)
{ {
static_assert(always_false<RawPoint>::value, return std::sqrt(squaredDistance<RawPoint, double>(p1, p2));
"PointLike::distance(point, segment) unimplemented!");
return 0;
} }
template<class RawPoint> // create perpendicular vector
inline std::pair<TCoord<RawPoint>, bool> horizontalDistance( template<class Pt> inline Pt perp(const Pt& p)
{
return Pt(y(p), -x(p));
}
template<class Pt, class Unit = TCompute<Pt>>
inline Unit dotperp(const Pt& a, const Pt& b)
{
return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b));
}
// dot product
template<class Pt, class Unit = TCompute<Pt>>
inline Unit dot(const Pt& a, const Pt& b)
{
return Unit(x(a)) * x(b) + Unit(y(a)) * y(b);
}
// squared vector magnitude
template<class Pt, class Unit = TCompute<Pt>>
inline Unit magnsq(const Pt& p)
{
return Unit(x(p)) * x(p) + Unit(y(p)) * y(p);
}
template<class RawPoint, class Unit = TCompute<RawPoint>>
inline std::pair<Unit, bool> horizontalDistance(
const RawPoint& p, const _Segment<RawPoint>& s) const RawPoint& p, const _Segment<RawPoint>& s)
{ {
using Unit = TCoord<RawPoint>; namespace pl = pointlike;
auto x = pointlike::x(p), y = pointlike::y(p); auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
TCoord<RawPoint> ret; Unit ret;
if( (y < y1 && y < y2) || (y > y1 && y > y2) ) if( (y < y1 && y < y2) || (y > y1 && y > y2) )
return {0, false}; return {0, false};
@ -250,8 +356,7 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
ret = std::min( x-x1, x -x2); ret = std::min( x-x1, x -x2);
else if( (y == y1 && y == y2) && (x < x1 && x < x2)) else if( (y == y1 && y == y2) && (x < x1 && x < x2))
ret = -std::min(x1 - x, x2 - x); ret = -std::min(x1 - x, x2 - x);
else if(std::abs(y - y1) <= std::numeric_limits<Unit>::epsilon() && else if(y == y1 && y == y2)
std::abs(y - y2) <= std::numeric_limits<Unit>::epsilon())
ret = 0; ret = 0;
else else
ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2); ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2);
@ -259,16 +364,16 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
return {ret, true}; return {ret, true};
} }
template<class RawPoint> template<class RawPoint, class Unit = TCompute<RawPoint>>
inline std::pair<TCoord<RawPoint>, bool> verticalDistance( inline std::pair<Unit, bool> verticalDistance(
const RawPoint& p, const _Segment<RawPoint>& s) const RawPoint& p, const _Segment<RawPoint>& s)
{ {
using Unit = TCoord<RawPoint>; namespace pl = pointlike;
auto x = pointlike::x(p), y = pointlike::y(p); auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
TCoord<RawPoint> ret; Unit ret;
if( (x < x1 && x < x2) || (x > x1 && x > x2) ) if( (x < x1 && x < x2) || (x > x1 && x > x2) )
return {0, false}; return {0, false};
@ -276,8 +381,7 @@ inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
ret = std::min( y-y1, y -y2); ret = std::min( y-y1, y -y2);
else if( (x == x1 && x == x2) && (y < y1 && y < y2)) else if( (x == x1 && x == x2) && (y < y1 && y < y2))
ret = -std::min(y1 - y, y2 - y); ret = -std::min(y1 - y, y2 - y);
else if(std::abs(x - x1) <= std::numeric_limits<Unit>::epsilon() && else if(x == x1 && x == x2)
std::abs(x - x2) <= std::numeric_limits<Unit>::epsilon())
ret = 0; ret = 0;
else else
ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2); ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2);
@ -333,9 +437,10 @@ inline Radians _Segment<RawPoint>::angleToXaxis() const
} }
template<class RawPoint> template<class RawPoint>
inline double _Segment<RawPoint>::length() template<class Unit>
inline Unit _Segment<RawPoint>::sqlength() const
{ {
return pointlike::distance(first(), second()); return pointlike::squaredDistance<RawPoint, Unit>(first(), second());
} }
template<class RawPoint> template<class RawPoint>
@ -346,8 +451,8 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<RawPoint>; using Coord = TCoord<RawPoint>;
RawPoint ret = { // No rounding here, we dont know if these are int coords RawPoint ret = { // No rounding here, we dont know if these are int coords
static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ), Coord( (getX(minc) + getX(maxc)) / Coord(2) ),
static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 ) Coord( (getY(minc) + getY(maxc)) / Coord(2) )
}; };
return ret; return ret;
@ -362,9 +467,6 @@ enum class Formats {
// used in friend declarations and can be aliased at class scope. // used in friend declarations and can be aliased at class scope.
namespace shapelike { namespace shapelike {
template<class RawShape>
using Shapes = TMultiShape<RawShape>;
template<class RawShape> template<class RawShape>
inline RawShape create(const TContour<RawShape>& contour, inline RawShape create(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes) const THolesContainer<RawShape>& holes)
@ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&)
template<class RawShape, class...Args> template<class RawShape, class...Args>
inline void addVertex(RawShape& sh, const PathTag&, Args...args) inline void addVertex(RawShape& sh, const PathTag&, Args...args)
{ {
return sh.emplace_back(std::forward<Args>(args)...); sh.emplace_back(std::forward<Args>(args)...);
} }
template<class RawShape, class Fn> template<class RawShape, class Fn>
@ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
"shapelike::unserialize() unimplemented!"); "shapelike::unserialize() unimplemented!");
} }
template<class RawShape> template<class Cntr, class Unit = double>
inline double area(const RawShape& /*sh*/, const PolygonTag&) inline Unit area(const Cntr& poly, const PathTag& );
{
static_assert(always_false<RawShape>::value,
"shapelike::area() unimplemented!");
return 0;
}
template<class RawShape> template<class RawShape>
inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
@ -556,14 +653,14 @@ inline bool touches( const TPoint<RawShape>& /*point*/,
template<class RawShape> template<class RawShape>
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/, inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
const PolygonTag&) const PathTag&)
{ {
static_assert(always_false<RawShape>::value, static_assert(always_false<RawShape>::value,
"shapelike::boundingBox(shape) unimplemented!"); "shapelike::boundingBox(shape) unimplemented!");
} }
template<class RawShapes> template<class RawShapes>
inline _Box<TPoint<typename RawShapes::value_type>> inline _Box<TPoint<RawShapes>>
boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
{ {
static_assert(always_false<RawShapes>::value, static_assert(always_false<RawShapes>::value,
@ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
} }
template<class RawShape> template<class RawShape>
inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) inline RawShape convexHull(const RawShape& sh, const PathTag&);
{
static_assert(always_false<RawShape>::value,
"shapelike::convexHull(shape) unimplemented!");
return RawShape();
}
template<class RawShapes> template<class RawShapes, class S = typename RawShapes::value_type>
inline typename RawShapes::value_type inline S convexHull(const RawShapes& sh, const MultiPolygonTag&);
convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShapes>::value,
"shapelike::convexHull(shapes) unimplemented!");
return typename RawShapes::value_type();
}
template<class RawShape> template<class RawShape>
inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
@ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) {
template<class RawShape, class...Args> template<class RawShape, class...Args>
inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) inline void addVertex(RawShape& sh, const PolygonTag&, Args...args)
{ {
return addVertex(contour(sh), PathTag(), std::forward<Args>(args)...); addVertex(contour(sh), PathTag(), std::forward<Args>(args)...);
} }
template<class RawShape, class...Args> // Tag dispatcher template<class RawShape, class...Args> // Tag dispatcher
inline void addVertex(RawShape& sh, Args...args) inline void addVertex(RawShape& sh, Args...args)
{ {
return addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...); addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...);
}
template<class RawShape>
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& poly, const PolygonTag&)
{
return boundingBox(contour(poly), PathTag());
} }
template<class Box> template<class Box>
@ -786,7 +878,7 @@ inline _Box<TPoint<S>> boundingBox(const S& sh)
template<class Box> template<class Box>
inline double area(const Box& box, const BoxTag& ) inline double area(const Box& box, const BoxTag& )
{ {
return box.area(); return box.template area<double>();
} }
template<class Circle> template<class Circle>
@ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& )
return circ.area(); return circ.area();
} }
template<class Cntr, class Unit>
inline Unit area(const Cntr& poly, const PathTag& )
{
namespace sl = shapelike;
if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0;
Unit a = 0;
for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly));
i < sl::cend(poly); ++i)
{
auto xj = Unit(getX(*j)), yj = Unit(getY(*j));
auto xi = Unit(getX(*i)), yi = Unit(getY(*i));
a += (xj + xi) * (yj - yi);
j = i;
}
a /= 2;
return is_clockwise<Cntr>() ? a : -a;
}
template<class S> inline double area(const S& poly, const PolygonTag& )
{
auto hls = holes(poly);
return std::accumulate(hls.begin(), hls.end(),
area(contour(poly), PathTag()),
[](double a, const TContour<S> &h){
return a + area(h, PathTag());
});
}
template<class RawShape> // Dispatching function template<class RawShape> // Dispatching function
inline double area(const RawShape& sh) inline double area(const RawShape& sh)
{ {
@ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&)
}); });
} }
template<class RawShape>
inline RawShape convexHull(const RawShape& sh, const PolygonTag&)
{
return create<RawShape>(convexHull(contour(sh), PathTag()));
}
template<class RawShape> template<class RawShape>
inline auto convexHull(const RawShape& sh) inline auto convexHull(const RawShape& sh)
-> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce -> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
@ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh)
return convexHull(sh, Tag<RawShape>()); return convexHull(sh, Tag<RawShape>());
} }
template<class RawShape>
inline RawShape convexHull(const RawShape& sh, const PathTag&)
{
using Unit = TCompute<RawShape>;
using Point = TPoint<RawShape>;
namespace sl = shapelike;
size_t edges = sl::cend(sh) - sl::cbegin(sh);
if(edges <= 3) return {};
bool closed = false;
std::vector<Point> U, L;
U.reserve(1 + edges / 2); L.reserve(1 + edges / 2);
std::vector<Point> pts; pts.reserve(edges);
std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts));
auto fpt = pts.front(), lpt = pts.back();
if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) {
closed = true; pts.pop_back();
}
std::sort(pts.begin(), pts.end(),
[](const Point& v1, const Point& v2)
{
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
return x1 == x2 ? y1 < y2 : x1 < x2;
});
auto dir = [](const Point& p, const Point& q, const Point& r) {
return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
};
auto ik = pts.begin();
while(ik != pts.end()) {
while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0)
U.pop_back();
while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0)
L.pop_back();
U.emplace_back(*ik);
L.emplace_back(*ik);
++ik;
}
RawShape ret; reserve(ret, U.size() + L.size());
if(is_clockwise<RawShape>()) {
for(auto it = U.begin(); it != std::prev(U.end()); ++it)
addVertex(ret, *it);
for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it)
addVertex(ret, *it);
if(closed) addVertex(ret, *std::prev(L.rend()));
} else {
for(auto it = L.begin(); it != std::prev(L.end()); ++it)
addVertex(ret, *it);
for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it)
addVertex(ret, *it);
if(closed) addVertex(ret, *std::prev(U.rend()));
}
return ret;
}
template<class RawShapes, class S>
inline S convexHull(const RawShapes& sh, const MultiPolygonTag&)
{
namespace sl = shapelike;
S cntr;
for(auto& poly : sh)
for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it)
addVertex(cntr, *it);
return convexHull(cntr, Tag<S>());
}
template<class TP, class TC> template<class TP, class TC>
inline bool isInside(const TP& point, const TC& circ, inline bool isInside(const TP& point, const TC& circ,
const PointTag&, const CircleTag&) const PointTag&, const CircleTag&)
{ {
return pointlike::distance(point, circ.center()) < circ.radius(); auto r = circ.radius();
return pointlike::squaredDistance(point, circ.center()) < r * r;
} }
template<class TP, class TB> template<class TP, class TB>
@ -972,6 +1179,9 @@ template<class RawShape> inline bool isConvex(const RawShape& sh) // dispatch
using Segment = _Segment<Point>; \ using Segment = _Segment<Point>; \
using Polygons = TMultiShape<T> using Polygons = TMultiShape<T>
namespace sl = shapelike;
namespace pl = pointlike;
} }
#endif // GEOMETRY_TRAITS_HPP #endif // GEOMETRY_TRAITS_HPP

View file

@ -1,26 +1,22 @@
#ifndef GEOMETRIES_NOFITPOLYGON_HPP #ifndef GEOMETRIES_NOFITPOLYGON_HPP
#define GEOMETRIES_NOFITPOLYGON_HPP #define GEOMETRIES_NOFITPOLYGON_HPP
#include "geometry_traits.hpp"
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include <vector> #include <vector>
#include <iterator> #include <iterator>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d { namespace libnest2d {
namespace __nfp { namespace __nfp {
// Do not specialize this... // Do not specialize this...
template<class RawShape> template<class RawShape, class Unit = TCompute<RawShape>>
inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2) inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
{ {
using Coord = TCoord<TPoint<RawShape>>; Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); return y1 == y2 ? x1 < x2 : y1 < y2;
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
} }
template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>> template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
@ -202,7 +198,7 @@ inline TPoint<RawShape> referenceVertex(const RawShape& sh)
* convex as well in this case. * convex as well in this case.
* *
*/ */
template<class RawShape> template<class RawShape, class Ratio = double>
inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh, inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other) const RawShape& other)
{ {
@ -238,12 +234,62 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
++first; ++next; ++first; ++next;
} }
} }
// Sort the edges by angle to X axis. std::sort(edgelist.begin(), edgelist.end(),
std::sort(edgelist.begin(), edgelist.end(), [](const Edge& e1, const Edge& e2)
[](const Edge& e1, const Edge& e2)
{ {
return e1.angleToXaxis() > e2.angleToXaxis(); Vertex ax(1, 0); // Unit vector for the X axis
// get cectors from the edges
Vertex p1 = e1.second() - e1.first();
Vertex p2 = e2.second() - e2.first();
// Quadrant mapping array. The quadrant of a vector can be determined
// from the dot product of the vector and its perpendicular pair
// with the unit vector X axis. The products will carry the values
// lcos = dot(p, ax) = l * cos(phi) and
// lsin = -dotperp(p, ax) = l * sin(phi) where
// l is the length of vector p. From the signs of these values we can
// construct an index which has the sign of lcos as MSB and the
// sign of lsin as LSB. This index can be used to retrieve the actual
// quadrant where vector p resides using the following map:
// (+ is 0, - is 1)
// cos | sin | decimal | quadrant
// + | + | 0 | 0
// + | - | 1 | 3
// - | + | 2 | 1
// - | - | 3 | 2
std::array<int, 4> quadrants {0, 3, 1, 2 };
std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
using TDots = std::array<TCompute<Vertex>, 2>;
TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) };
TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) };
// Construct the quadrant indices for p1 and p2
for(size_t i = 0; i < 2; ++i)
if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3;
else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2;
else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)];
if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt
auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt
// We will actually compare l^2 * cos^2(phi) which saturates the
// cos function. But with the quadrant info we can get the sign back
int sign = q[0] == 1 || q[0] == 2 ? -1 : 1;
// If Ratio is an actual rational type, there is no precision loss
auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0];
auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1];
return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2;
}
// If in different quadrants, compare the quadrant indices only.
return q[0] > q[1];
}); });
__nfp::buildPolygon(edgelist, rsh, top_nfp); __nfp::buildPolygon(edgelist, rsh, top_nfp);
@ -253,456 +299,9 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
template<class RawShape> template<class RawShape>
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary, NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother) const RawShape& cother)
{ {
return {};
// Algorithms are from the original algorithm proposed in paper:
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
// /////////////////////////////////////////////////////////////////////////
// Algorithm 1: Obtaining the minkowski sum
// /////////////////////////////////////////////////////////////////////////
// I guess this is not a full minkowski sum of the two input polygons by
// definition. This yields a subset that is compatible with the next 2
// algorithms.
using Result = NfpResult<RawShape>;
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
namespace sl = shapelike;
using std::signbit;
using std::sort;
using std::vector;
using std::ref;
using std::reference_wrapper;
// TODO The original algorithms expects the stationary polygon in
// counter clockwise and the orbiter in clockwise order.
// So for preventing any further complication, I will make the input
// the way it should be, than make my way around the orientations.
// Reverse the stationary contour to counter clockwise
auto stcont = sl::contour(cstationary);
{
std::reverse(sl::begin(stcont), sl::end(stcont));
stcont.pop_back();
auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(sl::begin(stcont), it, sl::end(stcont));
sl::addVertex(stcont, sl::front(stcont));
}
RawShape stationary;
sl::contour(stationary) = stcont;
// Reverse the orbiter contour to counter clockwise
auto orbcont = sl::contour(cother);
{
std::reverse(orbcont.begin(), orbcont.end());
// Step 1: Make the orbiter reverse oriented
orbcont.pop_back();
auto it = std::min_element(orbcont.begin(), orbcont.end(),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(orbcont.begin(), it, orbcont.end());
orbcont.emplace_back(orbcont.front());
for(auto &v : orbcont) v = -v;
}
// Copy the orbiter (contour only), we will have to work on it
RawShape orbiter;
sl::contour(orbiter) = orbcont;
// An edge with additional data for marking it
struct MarkedEdge {
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
MarkedEdge() = default;
MarkedEdge(const Edge& ed, Radians ta, bool tp):
e(ed), turn_angle(ta), is_turning_point(tp) {}
// debug
std::string label;
};
// Container for marked edges
using EdgeList = vector<MarkedEdge>;
EdgeList A, B;
// This is how an edge list is created from the polygons
auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) {
auto& poly = sl::contour(ppoly);
L.reserve(sl::contourVertexCount(poly));
if(dir > 0) {
auto it = poly.begin();
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != poly.end()) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
} else {
auto it = sl::rbegin(poly);
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != sl::rend(poly)) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
}
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
auto phi = e1.angleToXaxis();
auto phi_prev = e2.angleToXaxis();
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= TwoPi;
if(turn_angle < -Pi) turn_angle += TwoPi;
return turn_angle;
};
auto eit = L.begin();
auto enext = std::next(eit);
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
while(enext != L.end()) {
enext->turn_angle = getTurnAngle( enext->e, eit->e);
eit->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
++eit; ++enext;
}
L.back().is_turning_point = signbit(L.back().turn_angle) !=
signbit(L.front().turn_angle);
};
// Step 2: Fill the edgelists
fillEdgeList(A, stationary, 1);
fillEdgeList(B, orbiter, 1);
int i = 1;
for(MarkedEdge& me : A) {
std::cout << "a" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "a"; me.label += std::to_string(i);
i++;
}
i = 1;
for(MarkedEdge& me : B) {
std::cout << "b" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "b"; me.label += std::to_string(i);
i++;
}
// A reference to a marked edge that also knows its container
struct MarkedEdgeRef {
reference_wrapper<MarkedEdge> eref;
reference_wrapper<vector<MarkedEdgeRef>> container;
Coord dir = 1; // Direction modifier
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
inline const Edge& edge() const { return eref.get().e; }
inline Edge& edge() { return eref.get().e; }
inline bool isTurningPoint() const {
return eref.get().is_turning_point;
}
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
return &(container.get()) == &cont;
}
inline bool eq(const MarkedEdgeRef& mr) {
return &(eref.get()) == &(mr.eref.get());
}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec):
eref(er), container(ec), dir(1) {}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec,
Coord d):
eref(er), container(ec), dir(d) {}
};
using EdgeRefList = vector<MarkedEdgeRef>;
// Comparing two marked edges
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
return e1.angleX() < e2.angleX();
};
EdgeRefList Aref, Bref; // We create containers for the references
Aref.reserve(A.size()); Bref.reserve(B.size());
// Fill reference container for the stationary polygon
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
Aref.emplace_back( ref(me), ref(Aref) );
});
// Fill reference container for the orbiting polygon
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
Bref.emplace_back( ref(me), ref(Bref) );
});
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
(const EdgeRefList& Q, const EdgeRefList& R, bool positive)
{
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
// Sort the containers of edge references and merge them.
// Q could be sorted only once and be reused here but we would still
// need to merge it with sorted(R).
EdgeRefList merged;
EdgeRefList S, seq;
merged.reserve(Q.size() + R.size());
merged.insert(merged.end(), R.begin(), R.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
merged.insert(merged.end(), Q.begin(), Q.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
// we don't use i, instead, q is an iterator into Q. k would be an index
// into the merged sequence but we use "it" as an iterator for that
// here we obtain references for the containers for later comparisons
const auto& Rcont = R.begin()->container.get();
const auto& Qcont = Q.begin()->container.get();
// Set the initial direction
Coord dir = 1;
// roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
if(positive) {
auto q = Q.begin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.end();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.begin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
} else {
auto q = Q.rbegin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.rend();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
S.back().dir = -1;
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.rbegin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
}
// Step 4:
// "Let starting edge r1 be in position si in sequence"
// whaaat? I guess this means the following:
auto it = S.begin();
while(!it->eq(*R.begin())) ++it;
// "Set j = 1, next = 2, direction = 1, seq1 = si"
// we don't use j, seq is expanded dynamically.
dir = 1;
auto next = std::next(R.begin()); seq.emplace_back(*it);
// Step 5:
// "If all si edges have been allocated to seqj" should mean that
// we loop until seq has equal size with S
auto send = it; //it == S.begin() ? it : std::prev(it);
while(it != S.end()) {
++it; if(it == S.end()) it = S.begin();
if(it == send) break;
if(it->isFrom(Qcont)) {
seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
// "If si is a turning point in Q,
// direction = - direction, next = next + direction"
if(it->isTurningPoint()) {
dir = -dir;
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
// "j = j + 1, seqj = si, next = next + direction"
seq.emplace_back(*it);
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
return seq;
};
std::vector<EdgeRefList> seqlist;
seqlist.reserve(Bref.size());
EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
// make the slope diagram of B
std::sort(Bslope.begin(), Bslope.end(), sortfn);
auto slopeit = Bslope.begin(); // search for the first turning point
while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
if(slopeit == Bslope.end()) {
// no turning point means convex polygon.
seqlist.emplace_back(mink(Aref, Bref, true));
} else {
int dir = 1;
auto firstturn = Bref.begin();
while(!firstturn->eq(*slopeit)) ++firstturn;
assert(firstturn != Bref.end());
EdgeRefList bgroup; bgroup.reserve(Bref.size());
bgroup.emplace_back(*slopeit);
auto b_it = std::next(firstturn);
while(b_it != firstturn) {
if(b_it == Bref.end()) b_it = Bref.begin();
while(!slopeit->eq(*b_it)) {
__nfp::advance(slopeit, Bslope, dir > 0);
}
if(!slopeit->isTurningPoint()) {
bgroup.emplace_back(*slopeit);
} else {
if(!bgroup.empty()) {
if(dir > 0) bgroup.emplace_back(*slopeit);
for(auto& me : bgroup) {
std::cout << me.eref.get().label << ", ";
}
std::cout << std::endl;
seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
bgroup.clear();
if(dir < 0) bgroup.emplace_back(*slopeit);
} else {
bgroup.emplace_back(*slopeit);
}
dir *= -1;
}
++b_it;
}
}
// while(it != Bref.end()) // This is step 3 and step 4 in one loop
// if(it->isTurningPoint()) {
// R = {R.last, it++};
// auto seq = mink(Q, R, orientation);
// // TODO step 6 (should be 5 shouldn't it?): linking edges from A
// // I don't get this step
// seqlist.insert(seqlist.end(), seq.begin(), seq.end());
// orientation = !orientation;
// } else ++it;
// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
// /////////////////////////////////////////////////////////////////////////
// Algorithm 2: breaking Minkowski sums into track line trips
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Algorithm 3: finding the boundary of the NFP from track line trips
// /////////////////////////////////////////////////////////////////////////
for(auto& seq : seqlist) {
std::cout << "seqlist size: " << seq.size() << std::endl;
for(auto& s : seq) {
std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
}
std::cout << std::endl;
}
auto& seq = seqlist.front();
RawShape rsh;
Vertex top_nfp;
std::vector<Edge> edgelist; edgelist.reserve(seq.size());
for(auto& s : seq) {
edgelist.emplace_back(s.eref.get().e);
}
__nfp::buildPolygon(edgelist, rsh, top_nfp);
return Result(rsh, top_nfp);
} }
// Specializable NFP implementation class. Specialize it if you have a faster // Specializable NFP implementation class. Specialize it if you have a faster

View file

@ -8,13 +8,10 @@
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include "geometry_traits.hpp" #include <libnest2d/geometry_traits.hpp>
namespace libnest2d { namespace libnest2d {
namespace sl = shapelike;
namespace pl = pointlike;
/** /**
* \brief An item to be placed on a bin. * \brief An item to be placed on a bin.
* *
@ -422,13 +419,9 @@ private:
static inline bool vsort(const Vertex& v1, const Vertex& v2) static inline bool vsort(const Vertex& v1, const Vertex& v2)
{ {
Coord &&x1 = getX(v1), &&x2 = getX(v2); TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
Coord &&y1 = getY(v1), &&y2 = getY(v2); TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
auto diff = y1 - y2; return y1 == y2 ? x1 < x2 : y1 < y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
} }
}; };

View file

@ -4,7 +4,8 @@
#include <tuple> #include <tuple>
#include <functional> #include <functional>
#include <limits> #include <limits>
#include "common.hpp"
#include <libnest2d/common.hpp>
namespace libnest2d { namespace opt { namespace libnest2d { namespace opt {
@ -60,6 +61,7 @@ enum class Method {
L_SIMPLEX, L_SIMPLEX,
L_SUBPLEX, L_SUBPLEX,
G_GENETIC, G_GENETIC,
G_PARTICLE_SWARM
//... //...
}; };

View file

@ -48,7 +48,7 @@ else()
target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt) target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt)
endif() endif()
#target_sources( NloptOptimizer INTERFACE #target_sources( nloptOptimizer INTERFACE
#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp #${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
@ -57,5 +57,5 @@ endif()
target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
# And finally plug the NloptOptimizer into libnest2d # And finally plug the nloptOptimizer into libnest2d
#target_link_libraries(libnest2d INTERFACE NloptOptimizer) #target_link_libraries(libnest2d INTERFACE nloptOptimizer)

View file

@ -1,5 +0,0 @@
find_package(Armadillo REQUIRED)
add_library(OptimlibOptimizer INTERFACE)
target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS})
target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES})

View file

@ -7,15 +7,15 @@
namespace libnest2d { namespace placers { namespace libnest2d { namespace placers {
template<class T, class = T> struct Epsilon {}; template<class T, class = T> struct DefaultEpsilon {};
template<class T> template<class T>
struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > { struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
static const T Value = 1; static const T Value = 1;
}; };
template<class T> template<class T>
struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > { struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
static const T Value = 1e-3; static const T Value = 1e-3;
}; };
@ -24,7 +24,7 @@ struct BLConfig {
DECLARE_MAIN_TYPES(RawShape); DECLARE_MAIN_TYPES(RawShape);
Coord min_obj_distance = 0; Coord min_obj_distance = 0;
Coord epsilon = Epsilon<Coord>::Value; Coord epsilon = DefaultEpsilon<Coord>::Value;
bool allow_rotations = false; bool allow_rotations = false;
}; };

View file

@ -103,14 +103,14 @@ Key hash(const _Item<S>& item) {
while(deg > N) { ms++; deg -= N; } while(deg > N) { ms++; deg -= N; }
ls += int(deg); ls += int(deg);
ret.push_back(char(ms)); ret.push_back(char(ls)); ret.push_back(char(ms)); ret.push_back(char(ls));
circ += seg.length(); circ += std::sqrt(seg.template sqlength<double>());
} }
it = ctr.begin(); nx = std::next(it); it = ctr.begin(); nx = std::next(it);
while(nx != ctr.end()) { while(nx != ctr.end()) {
Segment seg(*it++, *nx++); Segment seg(*it++, *nx++);
auto l = int(M * seg.length() / circ); auto l = int(M * std::sqrt(seg.template sqlength<double>()) / circ);
int ms = 'A', ls = 'A'; int ms = 'A', ls = 'A';
while(l > N) { ms++; l -= N; } while(l > N) { ms++; l -= N; }
ls += l; ls += l;
@ -249,6 +249,11 @@ template<class RawShape> class EdgeCache {
std::vector<ContourCache> holes_; std::vector<ContourCache> holes_;
double accuracy_ = 1.0; double accuracy_ = 1.0;
static double length(const Edge &e)
{
return std::sqrt(e.template sqlength<double>());
}
void createCache(const RawShape& sh) { void createCache(const RawShape& sh) {
{ // For the contour { // For the contour
@ -260,7 +265,7 @@ template<class RawShape> class EdgeCache {
while(next != endit) { while(next != endit) {
contour_.emap.emplace_back(*(first++), *(next++)); contour_.emap.emplace_back(*(first++), *(next++));
contour_.full_distance += contour_.emap.back().length(); contour_.full_distance += length(contour_.emap.back());
contour_.distances.emplace_back(contour_.full_distance); contour_.distances.emplace_back(contour_.full_distance);
} }
} }
@ -275,7 +280,7 @@ template<class RawShape> class EdgeCache {
while(next != endit) { while(next != endit) {
hc.emap.emplace_back(*(first++), *(next++)); hc.emap.emplace_back(*(first++), *(next++));
hc.full_distance += hc.emap.back().length(); hc.full_distance += length(hc.emap.back());
hc.distances.emplace_back(hc.full_distance); hc.distances.emplace_back(hc.full_distance);
} }

View file

@ -311,19 +311,19 @@ struct range_value<bp2d::Shapes> {
namespace libnest2d { // Now the algorithms that boost can provide... namespace libnest2d { // Now the algorithms that boost can provide...
namespace pointlike { //namespace pointlike {
template<> //template<>
inline double distance(const PointImpl& p1, const PointImpl& p2 ) //inline double distance(const PointImpl& p1, const PointImpl& p2 )
{ //{
return boost::geometry::distance(p1, p2); // return boost::geometry::distance(p1, p2);
} //}
template<> //template<>
inline double distance(const PointImpl& p, const bp2d::Segment& seg ) //inline double distance(const PointImpl& p, const bp2d::Segment& seg )
{ //{
return boost::geometry::distance(p, seg); // return boost::geometry::distance(p, seg);
} //}
} //}
namespace shapelike { namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object // Tell libnest2d how to make string out of a ClipperPolygon object
@ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape)
} }
#ifndef DISABLE_BOOST_BOUNDING_BOX #ifndef DISABLE_BOOST_BOUNDING_BOX
template<>
inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
return b;
}
template<> template<>
inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&) inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&)
{ {
bp2d::Box b; bp2d::Box b;
boost::geometry::envelope(sh, b); boost::geometry::envelope(sh, b);
@ -410,9 +403,9 @@ inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
#ifndef DISABLE_BOOST_CONVEX_HULL #ifndef DISABLE_BOOST_CONVEX_HULL
template<> template<>
inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) inline PathImpl convexHull(const PathImpl& sh, const PathTag&)
{ {
PolygonImpl ret; PathImpl ret;
boost::geometry::convex_hull(sh, ret); boost::geometry::convex_hull(sh, ret);
return ret; return ret;
} }

View file

@ -0,0 +1,268 @@
#ifndef ROTCALIPERS_HPP
#define ROTCALIPERS_HPP
#include <numeric>
#include <functional>
#include <array>
#include <cmath>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
template<class Pt, class Unit = TCompute<Pt>> class RotatedBox {
Pt axis_;
Unit bottom_ = Unit(0), right_ = Unit(0);
public:
RotatedBox() = default;
RotatedBox(const Pt& axis, Unit b, Unit r):
axis_(axis), bottom_(b), right_(r) {}
inline long double area() const {
long double asq = pl::magnsq<Pt, long double>(axis_);
return cast<long double>(bottom_) * cast<long double>(right_) / asq;
}
inline long double width() const {
return abs(bottom_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline long double height() const {
return abs(right_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline Unit bottom_extent() const { return bottom_; }
inline Unit right_extent() const { return right_; }
inline const Pt& axis() const { return axis_; }
inline Radians angleToX() const {
double ret = std::atan2(getY(axis_), getX(axis_));
auto s = std::signbit(ret);
if(s) ret += Pi_2;
return -ret;
}
};
template <class Poly, class Pt = TPoint<Poly>, class Unit = TCompute<Pt>>
Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0))
{
Poly ret; sl::reserve(ret, sl::contourVertexCount(sh));
Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh));
auto it = sl::cbegin(sh);
auto itx = std::next(it);
if(itx != sl::cend(sh)) while (it != sl::cend(sh))
{
Pt enext = *itx - *it;
auto dp = pl::dotperp<Pt, Unit>(eprev, enext);
if(abs(dp) > eps) sl::addVertex(ret, *it);
eprev = enext;
if (++itx == sl::cend(sh)) itx = sl::cbegin(sh);
++it;
}
return ret;
}
// The area of the bounding rectangle with the axis dir and support vertices
template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>
inline R rectarea(const Pt& w, // the axis
const Pt& vb, const Pt& vr,
const Pt& vt, const Pt& vl)
{
Unit a = pl::dot<Pt, Unit>(w, vr - vl);
Unit b = pl::dot<Pt, Unit>(-pl::perp(w), vt - vb);
R m = R(a) / pl::magnsq<Pt, Unit>(w);
m = m * b;
return m;
};
template<class Pt,
class Unit = TCompute<Pt>,
class R = TCompute<Pt>,
class It = typename std::vector<Pt>::const_iterator>
inline R rectarea(const Pt& w, const std::array<It, 4>& rect)
{
return rectarea<Pt, Unit, R>(w, *rect[0], *rect[1], *rect[2], *rect[3]);
}
// This function is only applicable to counter-clockwise oriented convex
// polygons where only two points can be collinear witch each other.
template <class RawShape,
class Unit = TCompute<RawShape>,
class Ratio = TCompute<RawShape>>
RotatedBox<TPoint<RawShape>, Unit> minAreaBoundingBox(const RawShape& sh)
{
using Point = TPoint<RawShape>;
using Iterator = typename TContour<RawShape>::const_iterator;
using pointlike::dot; using pointlike::magnsq; using pointlike::perp;
// Get the first and the last vertex iterator
auto first = sl::cbegin(sh);
auto last = std::prev(sl::cend(sh));
// Check conditions and return undefined box if input is not sane.
if(last == first) return {};
if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last;
if(last - first < 2) return {};
RawShape shcpy; // empty at this point
{
Point p = *first, q = *std::next(first), r = *last;
// Determine orientation from first 3 vertex (should be consistent)
Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
if(d > 0) {
// The polygon is clockwise. A flip is needed (for now)
sl::reserve(shcpy, last - first);
auto it = last; while(it != first) sl::addVertex(shcpy, *it--);
sl::addVertex(shcpy, *first);
first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy));
}
}
// Cyclic iterator increment
auto inc = [&first, &last](Iterator& it) {
if(it == last) it = first; else ++it;
};
// Cyclic previous iterator
auto prev = [&first, &last](Iterator it) {
return it == first ? last : std::prev(it);
};
// Cyclic next iterator
auto next = [&first, &last](Iterator it) {
return it == last ? first : std::next(it);
};
// Establish initial (axis aligned) rectangle support verices by determining
// polygon extremes:
auto it = first;
Iterator minX = it, maxX = it, minY = it, maxY = it;
do { // Linear walk through the vertices and save the extreme positions
Point v = *it, d = v - *minX;
if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it;
d = v - *maxX;
if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it;
d = v - *minY;
if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it;
d = v - *maxY;
if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it;
} while(++it != std::next(last));
// Update the vertices defining the bounding rectangle. The rectangle with
// the smallest rotation is selected and the supporting vertices are
// returned in the 'rect' argument.
auto update = [&next, &inc]
(const Point& w, std::array<Iterator, 4>& rect)
{
Iterator B = rect[0], Bn = next(B);
Iterator R = rect[1], Rn = next(R);
Iterator T = rect[2], Tn = next(T);
Iterator L = rect[3], Ln = next(L);
Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L;
Point pw = perp(w);
using Pt = Point;
Unit dotwpb = dot<Pt, Unit>( w, b), dotwpr = dot<Pt, Unit>(-pw, r);
Unit dotwpt = dot<Pt, Unit>(-w, t), dotwpl = dot<Pt, Unit>( pw, l);
Unit dw = magnsq<Pt, Unit>(w);
std::array<Ratio, 4> angles;
angles[0] = (Ratio(dotwpb) / magnsq<Pt, Unit>(b)) * dotwpb;
angles[1] = (Ratio(dotwpr) / magnsq<Pt, Unit>(r)) * dotwpr;
angles[2] = (Ratio(dotwpt) / magnsq<Pt, Unit>(t)) * dotwpt;
angles[3] = (Ratio(dotwpl) / magnsq<Pt, Unit>(l)) * dotwpl;
using AngleIndex = std::pair<Ratio, size_t>;
std::vector<AngleIndex> A; A.reserve(4);
for (size_t i = 3, j = 0; j < 4; i = j++) {
if(rect[i] != rect[j] && angles[i] < dw) {
auto iv = std::make_pair(angles[i], i);
auto it = std::lower_bound(A.begin(), A.end(), iv,
[](const AngleIndex& ai,
const AngleIndex& aj)
{
return ai.first > aj.first;
});
A.insert(it, iv);
}
}
// The polygon is supposed to be a rectangle.
if(A.empty()) return false;
auto amin = A.front().first;
auto imin = A.front().second;
for(auto& a : A) if(a.first == amin) inc(rect[a.second]);
std::rotate(rect.begin(), rect.begin() + imin, rect.end());
return true;
};
Point w(1, 0);
Point w_min = w;
Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) *
(Unit(getY(*maxY)) - getY(*minY)));
std::array<Iterator, 4> rect = {minY, maxX, maxY, minX};
std::array<Iterator, 4> minrect = rect;
// An edge might be examined twice in which case the algorithm terminates.
size_t c = 0, count = last - first + 1;
std::vector<bool> edgemask(count, false);
while(c++ < count)
{
// Update the support vertices, if cannot be updated, break the cycle.
if(! update(w, rect)) break;
size_t eidx = size_t(rect[0] - first);
if(edgemask[eidx]) break;
edgemask[eidx] = true;
// get the unnormalized direction vector
w = *rect[0] - *prev(rect[0]);
// get the area of the rotated rectangle
Ratio rarea = rectarea<Point, Unit, Ratio>(w, rect);
// Update min area and the direction of the min bounding box;
if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; }
}
Unit a = dot<Point, Unit>(w_min, *minrect[1] - *minrect[3]);
Unit b = dot<Point, Unit>(-perp(w_min), *minrect[2] - *minrect[0]);
RotatedBox<Point, Unit> bb(w_min, a, b);
return bb;
}
template <class RawShape> Radians minAreaBoundingBoxRotation(const RawShape& sh)
{
return minAreaBoundingBox(sh).angleToX();
}
}
#endif // ROTCALIPERS_HPP

View file

@ -0,0 +1,23 @@
#include <libnest2d.h>
namespace libnest2d {
template class Nester<NfpPlacer, FirstFitSelection>;
template class Nester<BottomLeftPlacer, FirstFitSelection>;
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
}

View file

@ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt
target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} ) target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} )
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS})
${GTEST_INCLUDE_DIRS})
if(NOT LIBNEST2D_HEADER_ONLY)
target_link_libraries(tests_clipper_nlopt ${LIBNAME})
else()
target_link_libraries(tests_clipper_nlopt libnest2d)
endif()
add_test(libnest2d_tests tests_clipper_nlopt) add_test(libnest2d_tests tests_clipper_nlopt)

View file

@ -3,11 +3,43 @@
#include <libnest2d.h> #include <libnest2d.h>
#include "printer_parts.h" #include "printer_parts.h"
#include <libnest2d/geometry_traits_nfp.hpp> //#include <libnest2d/geometry_traits_nfp.hpp>
#include "../tools/svgtools.hpp" #include "../tools/svgtools.hpp"
#include <libnest2d/utils/rotcalipers.hpp>
#include "boost/multiprecision/integer.hpp"
#include "boost/rational.hpp"
//#include "../tools/Int128.hpp"
//#include "gte/Mathematics/GteMinimumAreaBox2.h"
//#include "../tools/libnfpglue.hpp" //#include "../tools/libnfpglue.hpp"
//#include "../tools/nfp_svgnest_glue.hpp" //#include "../tools/nfp_svgnest_glue.hpp"
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
#endif
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
namespace nfp {
template<class S>
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
}
}
std::vector<libnest2d::Item>& prusaParts() { std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret; static std::vector<libnest2d::Item> ret;
@ -31,8 +63,8 @@ TEST(BasicFunctionality, Angles)
ASSERT_DOUBLE_EQ(rad, Pi); ASSERT_DOUBLE_EQ(rad, Pi);
ASSERT_DOUBLE_EQ(deg, 180); ASSERT_DOUBLE_EQ(deg, 180);
ASSERT_DOUBLE_EQ(deg2, 180); ASSERT_DOUBLE_EQ(deg2, 180);
ASSERT_DOUBLE_EQ(rad, (Radians) deg); ASSERT_DOUBLE_EQ(rad, Radians(deg));
ASSERT_DOUBLE_EQ( (Degrees) rad, deg); ASSERT_DOUBLE_EQ( Degrees(rad), deg);
ASSERT_TRUE(rad == deg); ASSERT_TRUE(rad == deg);
@ -151,12 +183,12 @@ TEST(GeometryAlgorithms, Distance) {
Segment seg(p1, p3); Segment seg(p1, p3);
ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); // ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
auto result = pointlike::horizontalDistance(p2, seg); auto result = pointlike::horizontalDistance(p2, seg);
auto check = [](Coord val, Coord expected) { auto check = [](TCompute<Coord> val, TCompute<Coord> expected) {
if(std::is_floating_point<Coord>::value) if(std::is_floating_point<TCompute<Coord>>::value)
ASSERT_DOUBLE_EQ(static_cast<double>(val), ASSERT_DOUBLE_EQ(static_cast<double>(val),
static_cast<double>(expected)); static_cast<double>(expected));
else else
@ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
namespace { namespace {
using namespace libnest2d; using namespace libnest2d;
template<unsigned long SCALE = 1, class Bin> template<long long SCALE = 1, class Bin>
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) { void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
@ -500,6 +532,41 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) {
} }
} }
TEST(GeometryAlgorithms, convexHull) {
using namespace libnest2d;
ClipperLib::Path poly = PRINTER_PART_POLYGONS[0];
auto chull = sl::convexHull(poly);
ASSERT_EQ(chull.size(), poly.size());
}
TEST(GeometryAlgorithms, NestTest) {
std::vector<Item> input = prusaParts();
PackGroup result = libnest2d::nest(input,
Box(250000000, 210000000),
[](unsigned cnt) {
std::cout
<< "parts left: " << cnt
<< std::endl;
});
ASSERT_LE(result.size(), 2);
int partsum = std::accumulate(result.begin(),
result.end(),
0,
[](int s,
const decltype(result)::value_type &bin) {
return s += bin.size();
});
ASSERT_EQ(input.size(), partsum);
}
namespace { namespace {
struct ItemPair { struct ItemPair {
@ -713,7 +780,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
auto& exportfun = exportSVG<SCALE, Box>; auto& exportfun = exportSVG<SCALE, Box>;
auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
testcase++; testcase++;
orbiter.translate({210*SCALE, 0}); orbiter.translate({210*SCALE, 0});
@ -820,7 +887,7 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
rect2.translate({10, 0}); rect2.translate({10, 0});
rect3.translate({25, 0}); rect3.translate({25, 0});
shapelike::Shapes<PolygonImpl> pile; TMultiShape<PolygonImpl> pile;
pile.push_back(rect1.transformedShape()); pile.push_back(rect1.transformedShape());
pile.push_back(rect2.transformedShape()); pile.push_back(rect2.transformedShape());
@ -833,6 +900,126 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
ASSERT_EQ(shapelike::area(result.front()), ref.area()); ASSERT_EQ(shapelike::area(result.front()), ref.area());
} }
namespace {
long double refMinAreaBox(const PolygonImpl& p) {
auto it = sl::cbegin(p), itx = std::next(it);
long double min_area = std::numeric_limits<long double>::max();
auto update_min = [&min_area, &it, &itx, &p]() {
Segment s(*it, *itx);
PolygonImpl rotated = p;
sl::rotate(rotated, -s.angleToXaxis());
auto bb = sl::boundingBox(rotated);
auto area = cast<long double>(sl::area(bb));
if(min_area > area) min_area = area;
};
while(itx != sl::cend(p)) {
update_min();
++it; ++itx;
}
it = std::prev(sl::cend(p)); itx = sl::cbegin(p);
update_min();
return min_area;
}
template<class T> struct BoostGCD {
T operator()(const T &a, const T &b) { return boost::gcd(a, b); }
};
using Unit = int64_t;
using Ratio = boost::rational<boost::multiprecision::int128_t>;// Rational<boost::multiprecision::int256_t>;
//double gteMinAreaBox(const PolygonImpl& p) {
// using GteCoord = ClipperLib::cInt;
// using GtePoint = gte::Vector2<GteCoord>;
// gte::MinimumAreaBox2<GteCoord, Ratio> mb;
// std::vector<GtePoint> points;
// points.reserve(p.Contour.size());
// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)});
// mb(int(points.size()), points.data(), 0, nullptr, true);
// auto min_area = double(mb.GetArea());
// return min_area;
//}
}
TEST(RotatingCalipers, MinAreaBBCClk) {
// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}});
// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}});
auto u = [](ClipperLib::cInt n) { return n*1000000; };
PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
long double arearef = refMinAreaBox(poly);
long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
// double gtearea = gteMinAreaBox(poly);
ASSERT_LE(std::abs(area - arearef), 500e6 );
// ASSERT_LE(std::abs(gtearea - arearef), 500 );
// ASSERT_DOUBLE_EQ(gtearea, arearef);
}
TEST(RotatingCalipers, AllPrusaMinBB) {
size_t idx = 0;
long double err_epsilon = 500e6l;
for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx];
// rinput.pop_back();
// std::reverse(rinput.begin(), rinput.end());
// PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
PolygonImpl poly(rinput);
long double arearef = refMinAreaBox(poly);
auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput);
long double area = cast<long double>(bb.area());
// double area = gteMinAreaBox(poly);
bool succ = std::abs(arearef - area) < err_epsilon;
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
<< arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ);
}
for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) {
rinput.pop_back();
std::reverse(rinput.begin(), rinput.end());
PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
long double arearef = refMinAreaBox(poly);
auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly);
long double area = cast<long double>(bb.area());
// double area = gteMinAreaBox(poly);
bool succ = std::abs(arearef - area) < err_epsilon;
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
<< arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ);
}
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();

View file

@ -130,13 +130,10 @@ add_library(libslic3r STATIC
Print.hpp Print.hpp
PrintBase.cpp PrintBase.cpp
PrintBase.hpp PrintBase.hpp
PrintExport.hpp
PrintConfig.cpp PrintConfig.cpp
PrintConfig.hpp PrintConfig.hpp
PrintObject.cpp PrintObject.cpp
PrintRegion.cpp PrintRegion.cpp
Rasterizer/Rasterizer.hpp
Rasterizer/Rasterizer.cpp
SLAPrint.cpp SLAPrint.cpp
SLAPrint.hpp SLAPrint.hpp
SLA/SLAAutoSupports.hpp SLA/SLAAutoSupports.hpp
@ -163,6 +160,8 @@ add_library(libslic3r STATIC
MTUtils.hpp MTUtils.hpp
Zipper.hpp Zipper.hpp
Zipper.cpp Zipper.cpp
MinAreaBoundingBox.hpp
MinAreaBoundingBox.cpp
miniz_extension.hpp miniz_extension.hpp
miniz_extension.cpp miniz_extension.cpp
SLA/SLABoilerPlate.hpp SLA/SLABoilerPlate.hpp
@ -175,6 +174,10 @@ add_library(libslic3r STATIC
SLA/SLARotfinder.cpp SLA/SLARotfinder.cpp
SLA/SLABoostAdapter.hpp SLA/SLABoostAdapter.hpp
SLA/SLASpatIndex.hpp SLA/SLASpatIndex.hpp
SLA/SLARaster.hpp
SLA/SLARaster.cpp
SLA/SLARasterWriter.hpp
SLA/SLARasterWriter.cpp
) )
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)

View file

@ -15,7 +15,7 @@
#include "FillRectilinear3.hpp" #include "FillRectilinear3.hpp"
#define SLIC3R_DEBUG // #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG

View file

@ -1489,10 +1489,10 @@ namespace Slic3r {
} }
// splits volume out of imported geometry // splits volume out of imported geometry
unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; TriangleMesh triangle_mesh;
ModelVolume* volume = object.add_volume(TriangleMesh()); stl_file &stl = triangle_mesh.stl;
stl_file& stl = volume->mesh.stl; unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
stl.stats.type = inmemory; stl.stats.type = inmemory;
stl.stats.number_of_facets = (uint32_t)triangles_count; stl.stats.number_of_facets = (uint32_t)triangles_count;
stl.stats.original_num_facets = (int)stl.stats.number_of_facets; stl.stats.original_num_facets = (int)stl.stats.number_of_facets;
stl_allocate(&stl); stl_allocate(&stl);
@ -1509,9 +1509,11 @@ namespace Slic3r {
} }
} }
stl_get_size(&stl); stl_get_size(&stl);
volume->mesh.repair(); triangle_mesh.repair();
volume->center_geometry();
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
volume->center_geometry_after_creation();
volume->calculate_convex_hull(); volume->calculate_convex_hull();
// apply volume's name and config data // apply volume's name and config data
@ -1879,29 +1881,28 @@ namespace Slic3r {
if (volume == nullptr) if (volume == nullptr)
continue; 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; volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first;
if (!volume->mesh.repaired) const indexed_triangle_set &its = volume->mesh().its;
volume->mesh.repair(); if (its.vertices.empty())
stl_file& stl = volume->mesh.stl;
if (stl.v_shared == nullptr)
stl_generate_shared_vertices(&stl);
if (stl.stats.shared_vertices == 0)
{ {
add_error("Found invalid mesh"); add_error("Found invalid mesh");
return false; return false;
} }
vertices_count += stl.stats.shared_vertices; vertices_count += its.vertices.size();
const Transform3d& matrix = volume->get_matrix(); 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 << " "; 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 << "x=\"" << v(0) << "\" ";
stream << "y=\"" << v(1) << "\" "; stream << "y=\"" << v(1) << "\" ";
stream << "z=\"" << v(2) << "\" />\n"; stream << "z=\"" << v(2) << "\" />\n";
@ -1920,19 +1921,19 @@ namespace Slic3r {
VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume);
assert(volume_it != volumes_offsets.end()); assert(volume_it != volumes_offsets.end());
stl_file& stl = volume->mesh.stl; const indexed_triangle_set &its = volume->mesh().its;
// updates triangle offsets // updates triangle offsets
volume_it->second.first_triangle_id = triangles_count; 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; 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 << " "; stream << " <" << TRIANGLE_TAG << " ";
for (int j = 0; j < 3; ++j) 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"; stream << "/>\n";
} }

View file

@ -522,7 +522,8 @@ void AMFParserContext::endElement(const char * /* name */)
case NODE_TYPE_VOLUME: case NODE_TYPE_VOLUME:
{ {
assert(m_object && m_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.type = inmemory;
stl.stats.number_of_facets = int(m_volume_facets.size() / 3); stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
stl.stats.original_num_facets = stl.stats.number_of_facets; 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)); memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
} }
stl_get_size(&stl); stl_get_size(&stl);
m_volume->mesh.repair(); mesh.repair();
m_volume->center_geometry(); m_volume->set_mesh(std::move(mesh));
m_volume->center_geometry_after_creation();
m_volume->calculate_convex_hull(); m_volume->calculate_convex_hull();
m_volume_facets.clear(); m_volume_facets.clear();
m_volume = nullptr; m_volume = nullptr;
@ -923,23 +925,23 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
int num_vertices = 0; int num_vertices = 0;
for (ModelVolume *volume : object->volumes) { for (ModelVolume *volume : object->volumes) {
vertices_offsets.push_back(num_vertices); vertices_offsets.push_back(num_vertices);
if (! volume->mesh.repaired) if (! volume->mesh().repaired)
throw std::runtime_error("store_amf() requires repair()"); throw std::runtime_error("store_amf() requires repair()");
auto &stl = volume->mesh.stl; if (! volume->mesh().has_shared_vertices())
if (stl.v_shared == nullptr) throw std::runtime_error("store_amf() requires shared vertices");
stl_generate_shared_vertices(&stl); const indexed_triangle_set &its = volume->mesh().its;
const Transform3d& matrix = volume->get_matrix(); 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 << " <vertex>\n";
stream << " <coordinates>\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 << " <x>" << v(0) << "</x>\n";
stream << " <y>" << v(1) << "</y>\n"; stream << " <y>" << v(1) << "</y>\n";
stream << " <z>" << v(2) << "</z>\n"; stream << " <z>" << v(2) << "</z>\n";
stream << " </coordinates>\n"; stream << " </coordinates>\n";
stream << " </vertex>\n"; stream << " </vertex>\n";
} }
num_vertices += stl.stats.shared_vertices; num_vertices += its.vertices.size();
} }
stream << " </vertices>\n"; stream << " </vertices>\n";
for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { 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()) if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n"; stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</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"; stream << " <triangle>\n";
for (int j = 0; j < 3; ++j) 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 << " </triangle>\n";
} }
stream << " </volume>\n"; stream << " </volume>\n";

View file

@ -161,16 +161,15 @@ static void extract_model_from_archive(
else { else {
// Header has been extracted. Now read the faces. // Header has been extracted. Now read the faces.
stl_file &stl = mesh.stl; stl_file &stl = mesh.stl;
stl.error = 0;
stl.stats.type = inmemory; stl.stats.type = inmemory;
stl.stats.number_of_facets = header.nTriangles; stl.stats.number_of_facets = header.nTriangles;
stl.stats.original_num_facets = header.nTriangles; stl.stats.original_num_facets = header.nTriangles;
stl_allocate(&stl); stl_allocate(&stl);
if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) { 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) { if (sizeof(stl_facet) > SIZEOF_STL_FACET) {
// The stl.facet_start is not packed tightly. Unpack the array of stl_facets. // 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) for (size_t i = header.nTriangles - 1; i > 0; -- i)
memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); 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.number_of_facets = (uint32_t)facets.size();
stl.stats.original_num_facets = (int)facets.size(); stl.stats.original_num_facets = (int)facets.size();
stl_allocate(&stl); 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); stl_get_size(&stl);
mesh.repair(); mesh.repair();
// Add a mesh to a model. // Add a mesh to a model.

View file

@ -17,8 +17,7 @@ namespace Slic3r {
bool load_stl(const char *path, Model *model, const char *object_name_in) bool load_stl(const char *path, Model *model, const char *object_name_in)
{ {
TriangleMesh mesh; TriangleMesh mesh;
mesh.ReadSTLFile(path); if (! mesh.ReadSTLFile(path)) {
if (mesh.stl.error) {
// die "Failed to open $file\n" if !-e $path; // die "Failed to open $file\n" if !-e $path;
return false; return false;
} }

View file

@ -37,6 +37,8 @@
* * * *
*******************************************************************************/ *******************************************************************************/
#ifndef SLIC3R_INT128_HPP
#define SLIC3R_INT128_HPP
// #define SLIC3R_DEBUG // #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG
@ -48,6 +50,8 @@
#endif #endif
#include <cassert> #include <cassert>
#include <cstdint>
#include <cmath>
#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) #if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__)
#define HAS_INTRINSIC_128_TYPE #define HAS_INTRINSIC_128_TYPE
@ -293,3 +297,5 @@ public:
return sign_determinant_2x2(p1, q1, p2, q2) * invert; return sign_determinant_2x2(p1, q1, p2, q2) * invert;
} }
}; };
#endif // SLIC3R_INT128_HPP

View file

@ -5,6 +5,7 @@
#include <mutex> // for std::lock_guard #include <mutex> // for std::lock_guard
#include <functional> // for std::function #include <functional> // for std::function
#include <utility> // for std::forward #include <utility> // for std::forward
#include <algorithm>
namespace Slic3r { namespace Slic3r {
@ -182,6 +183,14 @@ public:
inline bool empty() const { return size() == 0; } 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 #endif // MTUTILS_HPP

View file

@ -0,0 +1,142 @@
#include "MinAreaBoundingBox.hpp"
#include <libslic3r/ExPolygon.hpp>
#include <boost/rational.hpp>
#include <libslic3r/Int128.hpp>
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
#include <boost/multiprecision/integer.hpp>
#endif
#include <libnest2d/geometry_traits.hpp>
#include <libnest2d/utils/rotcalipers.hpp>
namespace libnest2d {
template<> struct PointType<Slic3r::Points> { using Type = Slic3r::Point; };
template<> struct CoordType<Slic3r::Point> { using Type = coord_t; };
template<> struct ShapeTag<Slic3r::ExPolygon> { using Type = PolygonTag; };
template<> struct ShapeTag<Slic3r::Polygon> { using Type = PolygonTag; };
template<> struct ShapeTag<Slic3r::Points> { using Type = PathTag; };
template<> struct ShapeTag<Slic3r::Point> { using Type = PointTag; };
template<> struct ContourType<Slic3r::ExPolygon> { using Type = Slic3r::Points; };
template<> struct ContourType<Slic3r::Polygon> { using Type = Slic3r::Points; };
namespace pointlike {
template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); }
template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); }
template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); }
template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); }
} // pointlike
namespace shapelike {
template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; }
template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; }
template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; }
template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; }
template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();}
template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); }
template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();}
template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); }
template<> inline Slic3r::ExPolygon create<Slic3r::ExPolygon>(Slic3r::Points&& contour)
{
Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour);
return expoly;
}
template<> inline Slic3r::Polygon create<Slic3r::Polygon>(Slic3r::Points&& contour)
{
Slic3r::Polygon poly; poly.points.swap(contour);
return poly;
}
} // shapelike
} // libnest2d
namespace Slic3r {
// Used as compute type.
using Unit = int64_t;
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
using Rational = boost::rational<boost::multiprecision::int128_t>;
#else
using Rational = boost::rational<__int128>;
#endif
MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc)
{
const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<Polygon, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc)
{
const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<ExPolygon, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc)
{
const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<Points, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
double MinAreaBoundigBox::angle_to_X() const
{
double ret = std::atan2(m_axis.y(), m_axis.x());
auto s = std::signbit(ret);
if(s) ret += 2 * PI;
return -ret;
}
long double MinAreaBoundigBox::width() const
{
return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
}
long double MinAreaBoundigBox::height() const
{
return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
}
long double MinAreaBoundigBox::area() const
{
long double asq = libnest2d::pl::magnsq<Point, long double>(m_axis);
return m_bottom * m_right / asq;
}
void remove_collinear_points(Polygon &p)
{
p = libnest2d::removeCollinearPoints<Polygon>(p, Unit(0));
}
void remove_collinear_points(ExPolygon &p)
{
p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0));
}
}

View file

@ -0,0 +1,59 @@
#ifndef MINAREABOUNDINGBOX_HPP
#define MINAREABOUNDINGBOX_HPP
#include "libslic3r/Point.hpp"
namespace Slic3r {
class Polygon;
class ExPolygon;
void remove_collinear_points(Polygon& p);
void remove_collinear_points(ExPolygon& p);
/// A class that holds a rotated bounding box. If instantiated with a polygon
/// type it will hold the minimum area bounding box for the given polygon.
/// If the input polygon is convex, the complexity is linear to the number of
/// points. Otherwise a convex hull of O(n*log(n)) has to be performed.
class MinAreaBoundigBox {
Point m_axis;
long double m_bottom = 0.0l, m_right = 0.0l;
public:
// Polygons can be convex or simple (convex or concave with possible holes)
enum PolygonLevel {
pcConvex, pcSimple
};
// Constructors with various types of geometry data used in Slic3r.
// If the convexity is known apriory, pcConvex can be used to skip
// convex hull calculation. It is very important that the input polygons
// do NOT have any collinear points (except for the first and the last
// vertex being the same -- meaning a closed polygon for boost)
// To make sure this constraint is satisfied, you can call
// remove_collinear_points on the input polygon before handing over here)
explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple);
explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple);
explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple);
// Returns the angle in radians needed for the box to be aligned with the
// X axis. Rotate the polygon by this angle and it will be aligned.
double angle_to_X() const;
// The box width
long double width() const;
// The box height
long double height() const;
// The box area
long double area() const;
// The axis of the rotated box. If the angle_to_X is not sufficiently
// precise, use this unnormalized direction vector.
const Point& axis() const { return m_axis; }
};
}
#endif // MINAREABOUNDINGBOX_HPP

View file

@ -160,12 +160,6 @@ Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig
return model; return model;
} }
void Model::repair()
{
for (ModelObject *o : this->objects)
o->repair();
}
ModelObject* Model::add_object() ModelObject* Model::add_object()
{ {
this->objects.emplace_back(new ModelObject(this)); 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) if (obj->volumes.size() > 1 || obj->config.keys().size() > 1)
return false; return false;
for (const ModelVolume *vol : obj->volumes) { 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()) if (zmin == std::numeric_limits<double>::max())
zmin = zmin_this; zmin = zmin_this;
else if (std::abs(zmin - zmin_this) > EPSILON) 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); ModelVolume* v = new ModelVolume(this, mesh);
this->volumes.push_back(v); this->volumes.push_back(v);
v->center_geometry(); v->center_geometry_after_creation();
this->invalidate_bounding_box(); this->invalidate_bounding_box();
return v; return v;
} }
@ -688,7 +682,7 @@ ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh)
{ {
ModelVolume* v = new ModelVolume(this, std::move(mesh)); ModelVolume* v = new ModelVolume(this, std::move(mesh));
this->volumes.push_back(v); this->volumes.push_back(v);
v->center_geometry(); v->center_geometry_after_creation();
this->invalidate_bounding_box(); this->invalidate_bounding_box();
return v; return v;
} }
@ -697,8 +691,9 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other)
{ {
ModelVolume* v = new ModelVolume(this, other); ModelVolume* v = new ModelVolume(this, other);
this->volumes.push_back(v); this->volumes.push_back(v);
v->center_geometry(); // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull.
this->invalidate_bounding_box(); // v->center_geometry_after_creation();
// this->invalidate_bounding_box();
return v; return v;
} }
@ -706,7 +701,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&me
{ {
ModelVolume* v = new ModelVolume(this, other, std::move(mesh)); ModelVolume* v = new ModelVolume(this, other, std::move(mesh));
this->volumes.push_back(v); this->volumes.push_back(v);
v->center_geometry(); v->center_geometry_after_creation();
this->invalidate_bounding_box(); this->invalidate_bounding_box();
return v; return v;
} }
@ -827,7 +822,7 @@ TriangleMesh ModelObject::raw_mesh() const
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (v->is_model_part()) if (v->is_model_part())
{ {
TriangleMesh vol_mesh(v->mesh); TriangleMesh vol_mesh(v->mesh());
vol_mesh.transform(v->get_matrix()); vol_mesh.transform(v->get_matrix());
mesh.merge(vol_mesh); mesh.merge(vol_mesh);
} }
@ -840,7 +835,7 @@ TriangleMesh ModelObject::full_raw_mesh() const
TriangleMesh mesh; TriangleMesh mesh;
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
{ {
TriangleMesh vol_mesh(v->mesh); TriangleMesh vol_mesh(v->mesh());
vol_mesh.transform(v->get_matrix()); vol_mesh.transform(v->get_matrix());
mesh.merge(vol_mesh); mesh.merge(vol_mesh);
} }
@ -854,7 +849,7 @@ const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const
m_raw_mesh_bounding_box.reset(); m_raw_mesh_bounding_box.reset();
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (v->is_model_part()) 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; return m_raw_mesh_bounding_box;
} }
@ -863,7 +858,7 @@ BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const
{ {
BoundingBoxf3 bb; BoundingBoxf3 bb;
for (const ModelVolume *v : this->volumes) 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; return bb;
} }
@ -881,7 +876,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
{ {
if (v->is_model_part()) 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; 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) for (ModelVolume *v : this->volumes)
{ {
if (v->is_model_part()) 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; return bb;
} }
@ -908,21 +903,20 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const
Points pts; Points pts;
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (v->is_model_part()) { if (v->is_model_part()) {
const stl_file &stl = v->mesh.stl;
Transform3d trafo = trafo_instance * v->get_matrix(); 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. // Using the STL faces.
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++ i) { const stl_file& stl = v->mesh().stl;
const stl_facet &facet = stl.facet_start[i]; for (const stl_facet &facet : stl.facet_start)
for (size_t j = 0; j < 3; ++ j) { for (size_t j = 0; j < 3; ++ j) {
Vec3d p = trafo * facet.vertex[j].cast<double>(); Vec3d p = trafo * facet.vertex[j].cast<double>();
pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y())));
} }
}
} else { } else {
// Using the shared vertices should be a bit quicker than using the STL faces. // Using the shared vertices should be a bit quicker than using the STL faces.
for (int i = 0; i < stl.stats.shared_vertices; ++ i) { for (size_t i = 0; i < its.vertices.size(); ++ i) {
Vec3d p = trafo * stl.v_shared[i].cast<double>(); Vec3d p = trafo * its.vertices[i].cast<double>();
pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); 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->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) void ModelObject::scale_mesh(const Vec3d &versor)
{ {
for (ModelVolume *v : this->volumes) for (ModelVolume *v : this->volumes)
@ -1062,14 +1057,14 @@ size_t ModelObject::facets_count() const
size_t num = 0; size_t num = 0;
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (v->is_model_part()) if (v->is_model_part())
num += v->mesh.stl.stats.number_of_facets; num += v->mesh().stl.stats.number_of_facets;
return num; return num;
} }
bool ModelObject::needed_repair() const bool ModelObject::needed_repair() const
{ {
for (const ModelVolume *v : this->volumes) 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 true;
return false; 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. // Transform the mesh by the combined transformation matrix.
// Flip the triangles in case the composite transformation is left handed. // 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 // Perform cut
volume->mesh.require_shared_vertices(); // TriangleMeshSlicer needs this TriangleMeshSlicer tms(&mesh);
TriangleMeshSlicer tms(&volume->mesh);
tms.cut(float(z), &upper_mesh, &lower_mesh); tms.cut(float(z), &upper_mesh, &lower_mesh);
// Reset volume transformation except for offset // 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) { if (keep_upper && upper_mesh.facets_count() > 0) {
ModelVolume* vol = upper->add_volume(upper_mesh); ModelVolume* vol = upper->add_volume(upper_mesh);
vol->name = volume->name; vol->name = volume->name;
vol->config = volume->config; vol->config = volume->config;
vol->set_material(volume->material_id(), *volume->material()); vol->set_material(volume->material_id(), *volume->material());
} }
if (keep_lower && lower_mesh.facets_count() > 0) { if (keep_lower && lower_mesh.facets_count() > 0) {
ModelVolume* vol = lower->add_volume(lower_mesh); ModelVolume* vol = lower->add_volume(lower_mesh);
vol->name = volume->name; vol->name = volume->name;
vol->config = volume->config; vol->config = volume->config;
vol->set_material(volume->material_id(), *volume->material()); vol->set_material(volume->material_id(), *volume->material());
// Compute the lower part instances' bounding boxes to figure out where to place // 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(); ModelVolume* volume = this->volumes.front();
TriangleMeshPtrs meshptrs = volume->mesh.split(); TriangleMeshPtrs meshptrs = volume->mesh().split();
for (TriangleMesh *mesh : meshptrs) { for (TriangleMesh *mesh : meshptrs) {
mesh->repair(); mesh->repair();
@ -1260,12 +1256,6 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
return; 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, // 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. // 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. // 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. // Adjust the meshes.
// Transformation to be applied to 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); 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(); Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
for (ModelVolume *model_volume : this->volumes) { for (ModelVolume *model_volume : this->volumes) {
const Geometry::Transformation volume_trafo = model_volume->get_transformation(); const Geometry::Transformation volume_trafo = model_volume->get_transformation();
bool volume_left_handed = volume_trafo.is_left_handed(); 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.; double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.;
// Transform the mesh. // 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); 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. // Reset the rotation, scaling and mirroring.
model_volume->set_rotation(Vec3d(0., 0., 0.)); 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)); 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(); Transform3d mv = mi * v->get_matrix();
const TriangleMesh& hull = v->get_convex_hull(); const TriangleMesh& hull = v->get_convex_hull();
for (uint32_t f = 0; f < hull.stl.stats.number_of_facets; ++f) for (const stl_facet &facet : hull.stl.facet_start)
{ for (int i = 0; i < 3; ++ i)
const stl_facet* facet = hull.stl.facet_start + f; min_z = std::min(min_z, (mv * facet.vertex[i].cast<double>()).z());
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>()));
}
} }
return min_z + inst->get_offset(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 stl_stats ModelObject::get_object_stl_stats() const
{ {
if (this->volumes.size() == 1) if (this->volumes.size() == 1)
return this->volumes[0]->mesh.stl.stats; return this->volumes[0]->mesh().stl.stats;
stl_stats full_stats; stl_stats full_stats;
memset(&full_stats, 0, sizeof(stl_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()) if (volume->id() == this->volumes[0]->id())
continue; continue;
const stl_stats& stats = volume->mesh.stl.stats; const stl_stats& stats = volume->mesh().stl.stats;
// initialize full_stats (for repaired errors) // initialize full_stats (for repaired errors)
full_stats.degenerate_facets += stats.degenerate_facets; 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 // the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
if (m_is_splittable == -1) 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; 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())) if (!shift.isApprox(Vec3d::Zero()))
{ {
mesh.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)); m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
translate(shift); translate(shift);
} }
} }
void ModelVolume::calculate_convex_hull() 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 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 + return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
stats.facets_added + stats.facets_reversed + stats.backwards_edges; 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 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) 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. // This is useful to assign different materials to different volumes of an object.
size_t ModelVolume::split(unsigned int max_extruders) size_t ModelVolume::split(unsigned int max_extruders)
{ {
TriangleMeshPtrs meshptrs = this->mesh.split(); TriangleMeshPtrs meshptrs = this->mesh().split();
if (meshptrs.size() <= 1) { if (meshptrs.size() <= 1) {
delete meshptrs.front(); delete meshptrs.front();
return 1; return 1;
@ -1619,7 +1606,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
mesh->repair(); mesh->repair();
if (idx == 0) if (idx == 0)
{ {
this->mesh = std::move(*mesh); this->set_mesh(std::move(*mesh));
this->calculate_convex_hull(); this->calculate_convex_hull();
// Assign a new unique ID, so that a new GLVolume will be generated. // Assign a new unique ID, so that a new GLVolume will be generated.
this->set_new_unique_id(); 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.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]->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]->translate(offset);
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); 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)); 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); 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) void ModelVolume::scale_geometry(const Vec3d& versor)
{ {
mesh.scale(versor); m_mesh->scale(versor);
m_convex_hull.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); TriangleMesh mesh = this->mesh();
this->m_convex_hull.transform(mesh_trafo, fix_left_handed); 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. // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
this->set_new_unique_id(); 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); TriangleMesh mesh = this->mesh();
this->m_convex_hull.transform(matrix, fix_left_handed); 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. // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
this->set_new_unique_id(); this->set_new_unique_id();
} }

View file

@ -7,7 +7,9 @@
#include "Point.hpp" #include "Point.hpp"
#include "TriangleMesh.hpp" #include "TriangleMesh.hpp"
#include "Slicing.hpp" #include "Slicing.hpp"
#include <map> #include <map>
#include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -261,6 +263,7 @@ public:
void rotate(double angle, const Vec3d& axis); void rotate(double angle, const Vec3d& axis);
void mirror(Axis 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); void scale_mesh(const Vec3d& versor);
size_t materials_count() const; size_t materials_count() const;
@ -268,7 +271,6 @@ public:
bool needed_repair() const; 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 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 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, // 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. // 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. // This situation is solved by baking in the instance transformation into the mesh vertices.
@ -340,7 +342,12 @@ class ModelVolume : public ModelBase
public: public:
std::string name; std::string name;
// The triangular model. // 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, // Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings. // overriding the global Slic3r settings and the ModelObject settings.
DynamicPrintConfig config; DynamicPrintConfig config;
@ -377,13 +384,16 @@ public:
void rotate(double angle, const Vec3d& axis); void rotate(double angle, const Vec3d& axis);
void mirror(Axis 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); 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 // 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(); // 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(); void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const; 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 // Get count of errors in the mesh
int get_mesh_errors_count() const; int get_mesh_errors_count() const;
@ -430,18 +440,20 @@ protected:
explicit ModelVolume(const ModelVolume &rhs) = default; explicit ModelVolume(const ModelVolume &rhs) = default;
void set_model_object(ModelObject *model_object) { object = model_object; } void set_model_object(ModelObject *model_object) { object = model_object; }
void transform_mesh(const Transform3d& t, bool fix_left_handed); void transform_this_mesh(const Transform3d& t, bool fix_left_handed);
void transform_mesh(const Matrix3d& m, bool fix_left_handed); void transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
private: private:
// Parent object owning this ModelVolume. // 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? // Is it an object to be printed, or a modifier volume?
ModelVolumeType m_type; ModelVolumeType m_type;
t_model_material_id m_material_id; t_model_material_id m_material_id;
// The convex hull of this model's mesh. // The convex hull of this model's mesh.
TriangleMesh m_convex_hull; std::shared_ptr<TriangleMesh> m_convex_hull;
Geometry::Transformation m_transformation; Geometry::Transformation m_transformation;
// flag to optimize the checking if the volume is splittable // flag to optimize the checking if the volume is splittable
// -1 -> is unknown value (before first cheking) // -1 -> is unknown value (before first cheking)
@ -449,24 +461,24 @@ private:
// 1 -> is splittable // 1 -> is splittable
mutable int m_is_splittable{ -1 }; 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) if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull(); calculate_convex_hull();
} }
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&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. // Copying an existing volume, therefore this volume will get a copy of the ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other) : ModelVolume(ModelObject *object, const ModelVolume &other) :
ModelBase(other), // copy the ID 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()); this->set_material_id(other.material_id());
} }
// Providing a new mesh, therefore this volume will get a new unique ID assigned. // Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : 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()); this->set_material_id(other.material_id());
if (mesh.stl.stats.number_of_facets > 1) 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_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); 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. // Add a new ModelObject to this Model, generate a new ID for this ModelObject.
ModelObject* add_object(); ModelObject* add_object();
ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);

View file

@ -9,6 +9,31 @@
#include <ClipperUtils.hpp> #include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp> #include <boost/geometry/index/rtree.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/rational.hpp>
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
#endif
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
namespace nfp {
template<class S>
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
}
}
namespace Slic3r { namespace Slic3r {
@ -130,7 +155,7 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) {
// at the same time, it has to provide reasonable results. // at the same time, it has to provide reasonable results.
std::tuple<double /*score*/, Box /*farthest point from bin center*/> std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter, objfunc(const PointImpl& bincenter,
const shapelike::Shapes<PolygonImpl>& merged_pile, const TMultiShape<PolygonImpl>& merged_pile,
const Box& pilebb, const Box& pilebb,
const ItemGroup& items, const ItemGroup& items,
const Item &item, const Item &item,
@ -293,7 +318,7 @@ class AutoArranger {};
// management and spatial index structures for acceleration. // management and spatial index structures for acceleration.
template<class TBin> template<class TBin>
class _ArrBase { class _ArrBase {
protected: public:
// Useful type shortcuts... // Useful type shortcuts...
using Placer = TPacker<TBin>; using Placer = TPacker<TBin>;
@ -301,7 +326,9 @@ protected:
using Packer = Nester<Placer, Selector>; using Packer = Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig; using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>; using Distance = TCoord<PointImpl>;
using Pile = sl::Shapes<PolygonImpl>; using Pile = TMultiShape<PolygonImpl>;
protected:
Packer m_pck; Packer m_pck;
PConfig m_pconf; // Placement configuration PConfig m_pconf; // Placement configuration
@ -539,7 +566,10 @@ public:
// 2D shape from top view. // 2D shape from top view.
using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>; using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) { ShapeData2D projectModelFromTop(const Slic3r::Model &model,
const WipeTowerInfo &wti,
double tolerance)
{
ShapeData2D ret; ShapeData2D ret;
// Count all the items on the bin (all the object's instances) // Count all the items on the bin (all the object's instances)
@ -561,21 +591,32 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo&
// Object instances should carry the same scaling and // Object instances should carry the same scaling and
// x, y rotation that is why we use the first instance. // x, y rotation that is why we use the first instance.
{ {
ModelInstance *finst = objptr->instances.front(); ModelInstance *finst = objptr->instances.front();
Vec3d rotation = finst->get_rotation(); Vec3d rotation = finst->get_rotation();
rotation.z() = 0.; rotation.z() = 0.;
Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror()); Transform3d trafo_instance = Geometry::assemble_transform(
Vec3d::Zero(),
rotation,
finst->get_scaling_factor(),
finst->get_mirror());
Polygon p = objptr->convex_hull_2d(trafo_instance); Polygon p = objptr->convex_hull_2d(trafo_instance);
assert(! p.points.empty());
assert(!p.points.empty());
// this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty())
continue;
// this may happen for malformed models, see:
// https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty()) continue;
if(tolerance > EPSILON) {
Polygons pp { p };
pp = p.simplify(double(scaled(tolerance)));
if (!pp.empty()) p = pp.front();
}
p.reverse(); p.reverse();
assert(!p.is_counter_clockwise()); assert(!p.is_counter_clockwise());
p.append(p.first_point());
clpath = Slic3rMultiPoint_to_ClipperPath(p); clpath = Slic3rMultiPoint_to_ClipperPath(p);
auto firstp = clpath.front(); clpath.emplace_back(firstp);
} }
Vec3d rotation0 = objptr->instances.front()->get_rotation(); Vec3d rotation0 = objptr->instances.front()->get_rotation();
@ -589,7 +630,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo&
// Invalid geometries would throw exceptions when arranging // Invalid geometries would throw exceptions when arranging
if(item.vertexCount() > 3) { if(item.vertexCount() > 3) {
item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))), item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()));
item.translation({ item.translation({
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)
@ -741,6 +782,8 @@ BedShapeHint bedShape(const Polyline &bed) {
return ret; return ret;
} }
static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1;
// The final client function to arrange the Model. A progress indicator and // The final client function to arrange the Model. A progress indicator and
// a stop predicate can be also be passed to control the process. // a stop predicate can be also be passed to control the process.
bool arrange(Model &model, // The model with the geometries bool arrange(Model &model, // The model with the geometries
@ -755,9 +798,9 @@ bool arrange(Model &model, // The model with the geometries
std::function<bool ()> stopcondition) std::function<bool ()> stopcondition)
{ {
bool ret = true; bool ret = true;
// Get the 2D projected shapes with their 3D model instance pointers // Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model, wti); auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM);
// Copy the references for the shapes only as the arranger expects a // Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon // sequence of objects convertible to Item or ClipperPolygon
@ -782,7 +825,7 @@ bool arrange(Model &model, // The model with the geometries
static_cast<libnest2d::Coord>(bbb.min(0)), static_cast<libnest2d::Coord>(bbb.min(0)),
static_cast<libnest2d::Coord>(bbb.min(1)) static_cast<libnest2d::Coord>(bbb.min(1))
}, },
{ {
static_cast<libnest2d::Coord>(bbb.max(0)), static_cast<libnest2d::Coord>(bbb.max(0)),
static_cast<libnest2d::Coord>(bbb.max(1)) static_cast<libnest2d::Coord>(bbb.max(1))
}); });
@ -856,9 +899,9 @@ void find_new_position(const Model &model,
coord_t min_obj_distance, coord_t min_obj_distance,
const Polyline &bed, const Polyline &bed,
WipeTowerInfo& wti) WipeTowerInfo& wti)
{ {
// Get the 2D projected shapes with their 3D model instance pointers // Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model, wti); auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM);
// Copy the references for the shapes only as the arranger expects a // Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon // sequence of objects convertible to Item or ClipperPolygon

View file

@ -2258,6 +2258,20 @@ void PrintConfigDef::init_sla_params()
def->min = 100; def->min = 100;
def->set_default_value(new ConfigOptionInt(1440)); 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 = this->add("display_orientation", coEnum);
def->label = L("Display orientation"); def->label = L("Display orientation");
def->tooltip = L("Set the actual LCD display orientation inside the SLA printer." def->tooltip = L("Set the actual LCD display orientation inside the SLA printer."

View file

@ -1083,6 +1083,8 @@ public:
ConfigOptionInt display_pixels_x; ConfigOptionInt display_pixels_x;
ConfigOptionInt display_pixels_y; ConfigOptionInt display_pixels_y;
ConfigOptionEnum<SLADisplayOrientation> display_orientation; ConfigOptionEnum<SLADisplayOrientation> display_orientation;
ConfigOptionBool display_mirror_x;
ConfigOptionBool display_mirror_y;
ConfigOptionFloats relative_correction; ConfigOptionFloats relative_correction;
ConfigOptionFloat absolute_correction; ConfigOptionFloat absolute_correction;
ConfigOptionFloat gamma_correction; ConfigOptionFloat gamma_correction;
@ -1099,6 +1101,8 @@ protected:
OPT_PTR(display_height); OPT_PTR(display_height);
OPT_PTR(display_pixels_x); OPT_PTR(display_pixels_x);
OPT_PTR(display_pixels_y); OPT_PTR(display_pixels_y);
OPT_PTR(display_mirror_x);
OPT_PTR(display_mirror_y);
OPT_PTR(display_orientation); OPT_PTR(display_orientation);
OPT_PTR(relative_correction); OPT_PTR(relative_correction);
OPT_PTR(absolute_correction); OPT_PTR(absolute_correction);

View file

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

View file

@ -1797,7 +1797,7 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
if (! volumes.empty()) { if (! volumes.empty()) {
// Compose mesh. // Compose mesh.
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. //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); mesh.transform(volumes.front()->get_matrix(), true);
assert(mesh.repaired); assert(mesh.repaired);
if (volumes.size() == 1 && 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) { for (size_t idx_volume = 1; idx_volume < volumes.size(); ++ idx_volume) {
const ModelVolume &model_volume = *volumes[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); vol_mesh.transform(model_volume.get_matrix(), true);
mesh.merge(vol_mesh); mesh.merge(vol_mesh);
} }
@ -1815,10 +1815,11 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
// apply XY shift // apply XY shift
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0); mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
// perform actual slicing // perform actual slicing
TriangleMeshSlicer mslicer;
const Print *print = this->print(); const Print *print = this->print();
auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); 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.init(&mesh, callback);
mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
m_print->throw_if_canceled(); m_print->throw_if_canceled();
@ -1832,7 +1833,7 @@ std::vector<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &z,
std::vector<ExPolygons> layers; std::vector<ExPolygons> layers;
// Compose mesh. // Compose mesh.
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. //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); mesh.transform(volume.get_matrix(), true);
if (mesh.repaired) { if (mesh.repaired) {
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. //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; TriangleMeshSlicer mslicer;
const Print *print = this->print(); const Print *print = this->print();
auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); 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.init(&mesh, callback);
mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
m_print->throw_if_canceled(); m_print->throw_if_canceled();

View file

@ -1,5 +1,10 @@
#include "Rasterizer.hpp" #ifndef SLARASTER_CPP
#include <ExPolygon.hpp> #define SLARASTER_CPP
#include <functional>
#include "SLARaster.hpp"
#include "libslic3r/ExPolygon.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp> #include <libnest2d/backends/clipper/clipper_polygon.hpp>
// For rasterizing // For rasterizing
@ -19,11 +24,13 @@
namespace Slic3r { namespace Slic3r {
const Polygon& contour(const ExPolygon& p) { return p.contour; } inline const Polygon& contour(const ExPolygon& p) { return p.contour; }
const ClipperLib::Path& contour(const ClipperLib::Polygon& 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; } inline const Polygons& holes(const ExPolygon& p) { return p.holes; }
const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; }
namespace sla {
class Raster::Impl { class Raster::Impl {
public: public:
@ -39,7 +46,7 @@ public:
static const TPixel ColorWhite; static const TPixel ColorWhite;
static const TPixel ColorBlack; static const TPixel ColorBlack;
using Origin = Raster::Origin; using Format = Raster::Format;
private: private:
Raster::Resolution m_resolution; Raster::Resolution m_resolution;
@ -52,16 +59,21 @@ private:
TRendererAA m_renderer; TRendererAA m_renderer;
std::function<double(double)> m_gammafn; 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 { inline void flipy(agg::path_storage& path) const {
path.flip_y(0, m_resolution.height_px); 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: public:
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, 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_resolution(res),
// m_pxdim(pd), // m_pxdim(pd),
m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm), m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm),
@ -72,7 +84,7 @@ public:
m_pixfmt(m_rbuf), m_pixfmt(m_rbuf),
m_raw_renderer(m_pixfmt), m_raw_renderer(m_pixfmt),
m_renderer(m_raw_renderer), m_renderer(m_raw_renderer),
m_o(o) m_mirror(mirror)
{ {
m_renderer.color(ColorWhite); m_renderer.color(ColorWhite);
@ -81,6 +93,19 @@ public:
clear(); 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) { template<class P> void draw(const P &poly) {
agg::rasterizer_scanline_aa<> ras; agg::rasterizer_scanline_aa<> ras;
@ -89,14 +114,16 @@ public:
ras.gamma(m_gammafn); ras.gamma(m_gammafn);
auto&& path = to_path(contour(poly)); 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); ras.add_path(path);
for(auto& h : holes(poly)) { for(auto& h : holes(poly)) {
auto&& holepath = to_path(h); 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); ras.add_path(holepath);
} }
@ -108,11 +135,11 @@ public:
} }
inline TBuffer& buffer() { return m_buf; } inline TBuffer& buffer() { return m_buf; }
inline Format format() const { return m_fmt; }
inline const Raster::Resolution resolution() { return m_resolution; } inline const Raster::Resolution resolution() { return m_resolution; }
inline Origin origin() const /*noexcept*/ { return m_o; }
private: private:
inline double getPx(const Point& p) { inline double getPx(const Point& p) {
return p(0) * m_pxdim_scaled.w_mm; 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::ColorWhite = Raster::Impl::TPixel(255);
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o, double g): template<> Raster::Raster() { reset(); };
m_impl(new Impl(r, pd, o, g)) {} Raster::~Raster() = default;
Raster::Raster() {} // Raster::Raster(Raster &&m) = default;
// Raster& Raster::operator=(Raster&&) = default;
Raster::~Raster() {} // FIXME: remove after migrating to higher version of windows compiler
Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {}
Raster::Raster(Raster &&m): Raster& Raster::operator=(Raster &&m) {
m_impl(std::move(m.m_impl)) {} m_impl = std::move(m.m_impl); return *this;
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);
} }
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, 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();
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() void Raster::reset()
@ -208,13 +235,13 @@ void Raster::draw(const ClipperLib::Polygon &poly)
m_impl->draw(poly); m_impl->draw(poly);
} }
void Raster::save(std::ostream& stream, Compression comp) void Raster::save(std::ostream& stream, Format fmt)
{ {
assert(m_impl); assert(m_impl);
if(!stream.good()) return; if(!stream.good()) return;
switch(comp) { switch(fmt) {
case Compression::PNG: { case Format::PNG: {
auto& b = m_impl->buffer(); auto& b = m_impl->buffer();
size_t out_len = 0; size_t out_len = 0;
void * rawdata = tdefl_write_image_to_png_file_in_memory( void * rawdata = tdefl_write_image_to_png_file_in_memory(
@ -231,7 +258,7 @@ void Raster::save(std::ostream& stream, Compression comp)
break; break;
} }
case Compression::RAW: { case Format::RAW: {
stream << "P5 " stream << "P5 "
<< m_impl->resolution().width_px << " " << m_impl->resolution().width_px << " "
<< m_impl->resolution().height_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); assert(m_impl);
std::vector<std::uint8_t> data; size_t s = 0; std::vector<std::uint8_t> data; size_t s = 0;
switch(comp) { switch(fmt) {
case Compression::PNG: { case Format::PNG: {
void *rawdata = tdefl_write_image_to_png_file_in_memory( void *rawdata = tdefl_write_image_to_png_file_in_memory(
m_impl->buffer().data(), m_impl->buffer().data(),
int(resolution().width_px), int(resolution().width_px),
@ -265,7 +297,7 @@ RawBytes Raster::save(Raster::Compression comp)
MZ_FREE(rawdata); MZ_FREE(rawdata);
break; break;
} }
case Compression::RAW: { case Format::RAW: {
auto header = std::string("P5 ") + auto header = std::string("P5 ") +
std::to_string(m_impl->resolution().width_px) + " " + std::to_string(m_impl->resolution().width_px) + " " +
std::to_string(m_impl->resolution().height_px) + " " + "255 "; std::to_string(m_impl->resolution().height_px) + " " + "255 ";
@ -286,4 +318,12 @@ RawBytes Raster::save(Raster::Compression comp)
return {std::move(data)}; return {std::move(data)};
} }
RawBytes Raster::save()
{
return save(m_impl->format());
} }
}
}
#endif // SLARASTER_CPP

View file

@ -1,17 +1,21 @@
#ifndef RASTERIZER_HPP #ifndef SLARASTER_HPP
#define RASTERIZER_HPP #define SLARASTER_HPP
#include <ostream> #include <ostream>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <array>
#include <utility>
#include <cstdint> #include <cstdint>
namespace ClipperLib { struct Polygon; } namespace ClipperLib { struct Polygon; }
namespace Slic3r { namespace Slic3r {
class ExPolygon; class ExPolygon;
namespace sla {
// Raw byte buffer paired with its size. Suitable for compressed PNG data. // Raw byte buffer paired with its size. Suitable for compressed PNG data.
class RawBytes { class RawBytes {
@ -23,15 +27,18 @@ public:
size_t size() const { return m_buffer.size(); } size_t size() const { return m_buffer.size(); }
const uint8_t * data() { return m_buffer.data(); } 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 // FIXME: the following is needed for MSVC2013 compatibility
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
RawBytes(const RawBytes&) = delete; // RawBytes(RawBytes&&) = default;
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} // RawBytes& operator=(RawBytes&&) = default;
RawBytes& operator=(const RawBytes&) = delete; RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
RawBytes& operator=(RawBytes&& mv) { RawBytes& operator=(RawBytes&& mv) {
m_buffer = std::move(mv.m_buffer); m_buffer = std::move(mv.m_buffer);
return *this; return *this;
@ -54,28 +61,19 @@ class Raster {
public: public:
/// Supported compression types /// Supported compression types
enum class Compression { enum class Format {
RAW, //!> Uncompressed pixel data RAW, //!> Uncompressed pixel data
PNG //!> PNG compression 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. /// Type that represents a resolution in pixels.
struct Resolution { struct Resolution {
unsigned width_px; unsigned width_px;
unsigned height_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*/ { inline unsigned pixels() const /*noexcept*/ {
return width_px * height_px; return width_px * height_px;
} }
@ -85,24 +83,34 @@ public:
struct PixelDim { struct PixelDim {
double w_mm; double w_mm;
double h_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) {} w_mm(px_width_mm), h_mm(px_height_mm) {}
}; };
/// Constructor taking the resolution and the pixel dimension. /// Constructor taking the resolution and the pixel dimension.
Raster(const Resolution& r, const PixelDim& pd, template <class...Args> Raster(Args...args) {
Origin o = Origin::BOTTOM_LEFT, double gamma = 1.0); reset(std::forward<Args>(args)...);
}
Raster();
Raster(const Raster& cpy) = delete; Raster(const Raster& cpy) = delete;
Raster& operator=(const Raster& cpy) = delete; Raster& operator=(const Raster& cpy) = delete;
Raster(Raster&& m); Raster(Raster&& m);
Raster& operator=(Raster&&);
~Raster(); ~Raster();
/// Reallocated everything for the given resolution and pixel dimension. /// Reallocated everything for the given resolution and pixel dimension.
void reset(const Resolution& r, const PixelDim& pd, double gamma = 1.0); /// The third parameter is either the X, Y mirroring or a supported format
void reset(const Resolution& r, const PixelDim& pd, Origin o, double gamma); /// 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 * Release the allocated resources. Drawing in this state ends in
* unspecified behavior. * unspecified behavior.
@ -119,11 +127,24 @@ public:
void draw(const ExPolygon& poly); void draw(const ExPolygon& poly);
void draw(const ClipperLib::Polygon& 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. /// 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();
}; };
} // This prevents the duplicate default constructor warning on MSVC2013
#endif // RASTERIZER_HPP template<> Raster::Raster();
} // sla
} // Slic3r
#endif // SLARASTER_HPP

View 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

View 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

View file

@ -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 // 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 // the same for subsequent iterations (status goes from 0 to 100 but
// iterations can be many more) // 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) (double rx, double ry, double rz)
{ {
EigenMesh3D& m = emesh; EigenMesh3D& m = emesh;
@ -91,7 +91,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
} }
// report status // report status
statuscb( unsigned(++status * 100.0/max_tries) ); if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) );
return score; return score;
}; };

View file

@ -121,19 +121,10 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
V.resize(3*stl.stats.number_of_facets, 3); V.resize(3*stl.stats.number_of_facets, 3);
F.resize(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) { for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
const stl_facet* facet = stl.facet_start+i; const stl_facet &facet = stl.facet_start[i];
V(3*i+0, 0) = double(facet->vertex[0](0)); V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast<double>();
V(3*i+0, 1) = double(facet->vertex[0](1)); V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast<double>();
V(3*i+0, 2) = double(facet->vertex[0](2)); V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast<double>();
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));
F(i, 0) = int(3*i+0); F(i, 0) = int(3*i+0);
F(i, 1) = int(3*i+1); F(i, 1) = int(3*i+1);
F(i, 2) = int(3*i+2); F(i, 2) = int(3*i+2);

View file

@ -747,8 +747,8 @@ void SLAPrint::process()
{ {
// We apply the printer correction offset here. // We apply the printer correction offset here.
if(clpr_offs != 0) if(clpr_offs != 0)
po.m_model_slices[id] = po.m_model_slices[id] =
offset_ex(po.m_model_slices[id], float(clpr_offs)); offset_ex(po.m_model_slices[id], float(clpr_offs));
mit->set_model_slice_idx(po, id); ++mit; mit->set_model_slice_idx(po, id); ++mit;
} }
@ -1014,7 +1014,7 @@ void SLAPrint::process()
namespace sl = libnest2d::shapelike; // For algorithms namespace sl = libnest2d::shapelike; // For algorithms
// If the raster has vertical orientation, we will flip the coordinates // 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 // Set up custom union and diff functions for clipper polygons
auto polyunion = [] (const ClipperPolygons& subjects) auto polyunion = [] (const ClipperPolygons& subjects)
@ -1072,9 +1072,9 @@ void SLAPrint::process()
// get polygons for all instances in the object // get polygons for all instances in the object
auto get_all_polygons = auto get_all_polygons =
[flpXY](const ExPolygons& input_polygons, [](const ExPolygons& input_polygons,
const std::vector<SLAPrintObject::Instance>& instances, const std::vector<SLAPrintObject::Instance>& instances,
bool is_lefthanded) bool is_lefthanded)
{ {
ClipperPolygons polygons; ClipperPolygons polygons;
polygons.reserve(input_polygons.size() * instances.size()); polygons.reserve(input_polygons.size() * instances.size());
@ -1088,7 +1088,7 @@ void SLAPrint::process()
// We need to reverse if flpXY OR is_lefthanded is true but // We need to reverse if flpXY OR is_lefthanded is true but
// not if both are true which is a logical inequality (XOR) // 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 // should be a move
poly.Contour.reserve(polygon.contour.size() + 1); poly.Contour.reserve(polygon.contour.size() + 1);
@ -1123,10 +1123,10 @@ void SLAPrint::process()
sl::translate(poly, ClipperPoint{instances[i].shift(X), sl::translate(poly, ClipperPoint{instances[i].shift(X),
instances[i].shift(Y)}); instances[i].shift(Y)});
if (flpXY) { // if (flpXY) {
for(auto& p : poly.Contour) std::swap(p.X, p.Y); // 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); // for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y);
} // }
polygons.emplace_back(std::move(poly)); polygons.emplace_back(std::move(poly));
} }
@ -1295,35 +1295,11 @@ void SLAPrint::process()
auto rasterize = [this]() { auto rasterize = [this]() {
if(canceled()) return; 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 { // create a raster printer for the current print parameters
// I don't know any better double layerh = m_default_object_config.layer_height.getFloat();
auto& ocfg = m_objects.front()->m_config; m_printer.reset(new SLAPrinter(m_printer_config,
auto& matcfg = m_material_config; m_material_config,
auto& printcfg = m_printer_config; layerh));
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));
} }
// Allocate space for all the layers // Allocate space for all the layers
@ -1511,6 +1487,8 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
"display_height", "display_height",
"display_pixels_x", "display_pixels_x",
"display_pixels_y", "display_pixels_y",
"display_mirror_x",
"display_mirror_y",
"display_orientation" "display_orientation"
}; };

View file

@ -3,11 +3,11 @@
#include <mutex> #include <mutex>
#include "PrintBase.hpp" #include "PrintBase.hpp"
#include "PrintExport.hpp" //#include "PrintExport.hpp"
#include "SLA/SLARasterWriter.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "MTUtils.hpp" #include "MTUtils.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp> #include <libnest2d/backends/clipper/clipper_polygon.hpp>
#include "Zipper.hpp"
namespace Slic3r { namespace Slic3r {
@ -326,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. * @brief This class is the high level FSM for the SLA printing process.
* *
@ -389,11 +358,10 @@ public:
// Returns true if the last step was finished with success. // 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); } 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, 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; } const PrintObjects& objects() const { return m_objects; }
@ -454,7 +422,7 @@ public:
const std::vector<PrintLayer>& print_layers() const { return m_printer_input; } const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
private: private:
using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>; using SLAPrinter = sla::SLARasterWriter;
using SLAPrinterPtr = std::unique_ptr<SLAPrinter>; using SLAPrinterPtr = std::unique_ptr<SLAPrinter>;
// Implement same logic as in SLAPrintObject // Implement same logic as in SLAPrintObject

View file

@ -227,7 +227,7 @@ std::vector<coordf_t> layer_height_profile_adaptive(
as.set_slicing_parameters(slicing_params); as.set_slicing_parameters(slicing_params);
for (const ModelVolume *volume : volumes) for (const ModelVolume *volume : volumes)
if (volume->is_model_part()) if (volume->is_model_part())
as.add_mesh(&volume->mesh); as.add_mesh(&volume->mesh());
as.prepare(); as.prepare();
// 2) Generate layers using the algorithm of @platsch // 2) Generate layers using the algorithm of @platsch

View file

@ -27,8 +27,8 @@ void SlicingAdaptive::prepare()
nfaces_total += (*it_mesh)->stl.stats.number_of_facets; nfaces_total += (*it_mesh)->stl.stats.number_of_facets;
m_faces.reserve(nfaces_total); 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 (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) for (const stl_facet &face : (*it_mesh)->stl.facet_start)
m_faces.push_back((*it_mesh)->stl.facet_start + i); m_faces.emplace_back(&face);
// 2) Sort faces lexicographically by their Z span. // 2) Sort faces lexicographically by their Z span.
std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) {

View file

@ -42,20 +42,17 @@
namespace Slic3r { namespace Slic3r {
TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets) TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets) : repaired(false)
: repaired(false)
{ {
stl_initialize(&this->stl);
stl_file &stl = this->stl; stl_file &stl = this->stl;
stl.error = 0;
stl.stats.type = inmemory; stl.stats.type = inmemory;
// count facets and allocate memory // 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.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl); 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; stl_facet facet;
facet.vertex[0] = points[facets[i](0)].cast<float>(); facet.vertex[0] = points[facets[i](0)].cast<float>();
facet.vertex[1] = points[facets[i](1)].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); 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 // #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 // 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"; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started";
// checking exact // checking exact
#ifdef SLIC3R_TRACE_REPAIR #ifdef SLIC3R_TRACE_REPAIR
BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact";
#endif /* SLIC3R_TRACE_REPAIR */ #endif /* SLIC3R_TRACE_REPAIR */
assert(stl_validate(&this->stl));
stl_check_facets_exact(&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_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_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); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge);
// checking nearby // checking nearby
//int last_edges_fixed = 0; //int last_edges_fixed = 0;
float tolerance = stl.stats.shortest_edge; float tolerance = (float)stl.stats.shortest_edge;
float increment = stl.stats.bounding_diameter / 10000.0; float increment = (float)stl.stats.bounding_diameter / 10000.0f;
int iterations = 2; int iterations = 2;
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
for (int i = 0; i < iterations; i++) { for (int i = 0; i < iterations; i++) {
@ -141,6 +118,7 @@ void TriangleMesh::repair()
} }
} }
} }
assert(stl_validate(&this->stl));
// remove_unconnected // remove_unconnected
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { 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"; BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets";
#endif /* SLIC3R_TRACE_REPAIR */ #endif /* SLIC3R_TRACE_REPAIR */
stl_remove_unconnected_facets(&stl); stl_remove_unconnected_facets(&stl);
assert(stl_validate(&this->stl));
} }
// fill_holes // fill_holes
@ -168,28 +147,38 @@ void TriangleMesh::repair()
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions";
#endif /* SLIC3R_TRACE_REPAIR */ #endif /* SLIC3R_TRACE_REPAIR */
stl_fix_normal_directions(&stl); stl_fix_normal_directions(&stl);
assert(stl_validate(&this->stl));
// normal_values // normal_values
#ifdef SLIC3R_TRACE_REPAIR #ifdef SLIC3R_TRACE_REPAIR
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values";
#endif /* SLIC3R_TRACE_REPAIR */ #endif /* SLIC3R_TRACE_REPAIR */
stl_fix_normal_values(&stl); stl_fix_normal_values(&stl);
assert(stl_validate(&this->stl));
// always calculate the volume and reverse all normals if volume is negative // always calculate the volume and reverse all normals if volume is negative
#ifdef SLIC3R_TRACE_REPAIR #ifdef SLIC3R_TRACE_REPAIR
BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume";
#endif /* SLIC3R_TRACE_REPAIR */ #endif /* SLIC3R_TRACE_REPAIR */
stl_calculate_volume(&stl); stl_calculate_volume(&stl);
assert(stl_validate(&this->stl));
// neighbors // neighbors
#ifdef SLIC3R_TRACE_REPAIR #ifdef SLIC3R_TRACE_REPAIR
BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors";
#endif /* SLIC3R_TRACE_REPAIR */ #endif /* SLIC3R_TRACE_REPAIR */
stl_verify_neighbors(&stl); stl_verify_neighbors(&stl);
assert(stl_validate(&this->stl));
this->repaired = true; this->repaired = true;
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; 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() float TriangleMesh::volume()
@ -249,20 +238,24 @@ bool TriangleMesh::needed_repair() const
void TriangleMesh::WriteOBJFile(const char* output_file) void TriangleMesh::WriteOBJFile(const char* output_file)
{ {
stl_generate_shared_vertices(&stl); its_write_obj(this->its, output_file);
stl_write_obj(&stl, output_file);
} }
void TriangleMesh::scale(float factor) void TriangleMesh::scale(float factor)
{ {
stl_scale(&(this->stl), 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) void TriangleMesh::scale(const Vec3d &versor)
{ {
stl_scale_versor(&this->stl, versor.cast<float>()); 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) 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) if (x == 0.f && y == 0.f && z == 0.f)
return; return;
stl_translate_relative(&(this->stl), x, y, z); 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) void TriangleMesh::translate(const Vec3f &displacement)
@ -287,13 +282,15 @@ void TriangleMesh::rotate(float angle, const Axis &axis)
angle = Slic3r::Geometry::rad2deg(angle); angle = Slic3r::Geometry::rad2deg(angle);
if (axis == X) { 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) { } 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) { } 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) void TriangleMesh::rotate(float angle, const Vec3d& axis)
@ -305,39 +302,49 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis)
Transform3d m = Transform3d::Identity(); Transform3d m = Transform3d::Identity();
m.rotate(Eigen::AngleAxisd(angle, axis_norm)); m.rotate(Eigen::AngleAxisd(angle, axis_norm));
stl_transform(&stl, m); stl_transform(&stl, m);
its_transform(its, m);
} }
void TriangleMesh::mirror(const Axis &axis) void TriangleMesh::mirror(const Axis &axis)
{ {
if (axis == X) { if (axis == X) {
stl_mirror_yz(&this->stl); stl_mirror_yz(&this->stl);
for (stl_vertex &v : this->its.vertices)
v(0) *= -1.0;
} else if (axis == Y) { } else if (axis == Y) {
stl_mirror_xz(&this->stl); stl_mirror_xz(&this->stl);
for (stl_vertex &v : this->its.vertices)
v(1) *= -1.0;
} else if (axis == Z) { } else if (axis == Z) {
stl_mirror_xy(&this->stl); 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) void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed)
{ {
stl_transform(&stl, t); 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.) { 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. // 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); stl_reverse_all_facets(&stl);
this->its.clear();
this->require_shared_vertices();
} }
} }
void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed)
{ {
stl_transform(&stl, m); stl_transform(&stl, m);
stl_invalidate_shared_vertices(&stl); its_transform(its, m);
if (fix_left_handed && m.determinant() < 0.) { 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. // 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); stl_reverse_all_facets(&stl);
this->its.clear();
this->require_shared_vertices();
} }
} }
@ -355,7 +362,8 @@ void TriangleMesh::rotate(double angle, Point* center)
return; return;
Vec2f c = center->cast<float>(); Vec2f c = center->cast<float>();
this->translate(-c(0), -c(1), 0); 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); this->translate(c(0), c(1), 0);
} }
@ -435,9 +443,8 @@ TriangleMeshPtrs TriangleMesh::split() const
TriangleMesh* mesh = new TriangleMesh; TriangleMesh* mesh = new TriangleMesh;
meshes.emplace_back(mesh); meshes.emplace_back(mesh);
mesh->stl.stats.type = inmemory; 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; mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets;
stl_clear_error(&mesh->stl);
stl_allocate(&mesh->stl); stl_allocate(&mesh->stl);
// Assign the facets to the new mesh. // Assign the facets to the new mesh.
@ -455,7 +462,7 @@ void TriangleMesh::merge(const TriangleMesh &mesh)
{ {
// reset stats and metadata // reset stats and metadata
int number_of_facets = this->stl.stats.number_of_facets; int number_of_facets = this->stl.stats.number_of_facets;
stl_invalidate_shared_vertices(&this->stl); this->its.clear();
this->repaired = false; this->repaired = false;
// update facet count and allocate more memory // update facet count and allocate more memory
@ -477,13 +484,12 @@ ExPolygons TriangleMesh::horizontal_projection() const
{ {
Polygons pp; Polygons pp;
pp.reserve(this->stl.stats.number_of_facets); pp.reserve(this->stl.stats.number_of_facets);
for (uint32_t i = 0; i < this->stl.stats.number_of_facets; ++ i) { for (const stl_facet &facet : this->stl.facet_start) {
stl_facet* facet = &this->stl.facet_start[i];
Polygon p; Polygon p;
p.points.resize(3); p.points.resize(3);
p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](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[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[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 p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that
pp.emplace_back(p); 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. // 2D convex hull of a 3D mesh projected into the Z=0 plane.
Polygon TriangleMesh::convex_hull() Polygon TriangleMesh::convex_hull()
{ {
this->require_shared_vertices();
Points pp; Points pp;
pp.reserve(this->stl.stats.shared_vertices); pp.reserve(this->its.vertices.size());
for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) { for (size_t i = 0; i < this->its.vertices.size(); ++ i) {
const stl_vertex &v = this->stl.v_shared[i]; const stl_vertex &v = this->its.vertices[i];
pp.emplace_back(Point::new_scale(v(0), v(1))); pp.emplace_back(Point::new_scale(v(0), v(1)));
} }
return Slic3r::Geometry::convex_hull(pp); 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 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const
{ {
BoundingBoxf3 bbox; BoundingBoxf3 bbox;
if (stl.v_shared == nullptr) { if (this->its.vertices.empty()) {
// Using the STL faces. // Using the STL faces.
for (size_t i = 0; i < this->facets_count(); ++ i) { for (const stl_facet &facet : this->stl.facet_start)
const stl_facet &facet = this->stl.facet_start[i];
for (size_t j = 0; j < 3; ++ j) for (size_t j = 0; j < 3; ++ j)
bbox.merge(trafo * facet.vertex[j].cast<double>()); bbox.merge(trafo * facet.vertex[j].cast<double>());
}
} else { } else {
// Using the shared vertices should be a bit quicker than using the STL faces. // Using the shared vertices should be a bit quicker than using the STL faces.
for (int i = 0; i < stl.stats.shared_vertices; ++ i) for (const stl_vertex &v : this->its.vertices)
bbox.merge(trafo * this->stl.v_shared[i].cast<double>()); bbox.merge(trafo * v.cast<double>());
} }
return bbox; return bbox;
} }
TriangleMesh TriangleMesh::convex_hull_3d() const 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: // The qhull call:
orgQhull::Qhull qhull; orgQhull::Qhull qhull;
qhull.disableOutputStream(); // we want qhull to be quiet 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 (...) catch (...)
{ {
@ -587,34 +590,20 @@ TriangleMesh TriangleMesh::convex_hull_3d() const
TriangleMesh output_mesh(dst_vertices, facets); TriangleMesh output_mesh(dst_vertices, facets);
output_mesh.repair(); output_mesh.repair();
output_mesh.require_shared_vertices();
return output_mesh; return output_mesh;
} }
void TriangleMesh::require_shared_vertices() void TriangleMesh::require_shared_vertices()
{ {
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start";
if (!this->repaired) assert(stl_validate(&this->stl));
if (! this->repaired)
this->repair(); 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"; 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 assert(stl_validate(&this->stl, this->its));
// 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 */
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; 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(); throw_on_cancel();
facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); 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); v_scaled_shared.assign(_mesh->its.vertices.size(), stl_vertex());
// Scale the copied vertices. for (size_t i = 0; i < v_scaled_shared.size(); ++ i)
for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) this->v_scaled_shared[i] = _mesh->its.vertices[i] / float(SCALING_FACTOR);
this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR);
// Create a mapping from triangle edge into face. // Create a mapping from triangle edge into face.
struct EdgeToFace { 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 (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx)
for (int i = 0; i < 3; ++ i) { for (int i = 0; i < 3; ++ i) {
EdgeToFace &e2f = edges_map[facet_idx*3+i]; EdgeToFace &e2f = edges_map[facet_idx*3+i];
e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i]; e2f.vertex_low = this->mesh->its.indices[facet_idx][i];
e2f.vertex_high = this->mesh->stl.v_indices[facet_idx].vertex[(i + 1) % 3]; e2f.vertex_high = this->mesh->its.indices[facet_idx][(i + 1) % 3];
e2f.face = facet_idx; e2f.face = facet_idx;
// 1 based indexing, to be always strictly positive. // 1 based indexing, to be always strictly positive.
e2f.face_edge = i + 1; 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, void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex,
const std::vector<float> &z) const 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 // find facet extents
const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); 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. // 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 // This is needed to get all intersection lines in a consistent order
// (external on the right of the line) // (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); 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: // 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"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object";
float scaled_z = scale_(z); float scaled_z = scale_(z);
for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { 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 // find facet extents
float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2))); 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)) { if (min_z > z || (min_z == z && max_z > z)) {
// facet is above the cut plane and does not belong to it // 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)) { } else if (max_z < z || (max_z == z && min_z < z)) {
// facet is below the cut plane and does not belong to it // 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) { } else if (min_z < z && max_z > z) {
// Facet is cut by the slicing plane. // 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; quadrilateral[1].vertex[2] = v0v1;
if (v0(2) > z) { if (v0(2) > z) {
if (upper != NULL) stl_add_facet(&upper->stl, &triangle); if (upper != nullptr)
if (lower != NULL) { stl_add_facet(&upper->stl, &triangle);
if (lower != nullptr) {
stl_add_facet(&lower->stl, &quadrilateral[0]); stl_add_facet(&lower->stl, &quadrilateral[0]);
stl_add_facet(&lower->stl, &quadrilateral[1]); stl_add_facet(&lower->stl, &quadrilateral[1]);
} }
} else { } else {
if (upper != NULL) { if (upper != nullptr) {
stl_add_facet(&upper->stl, &quadrilateral[0]); stl_add_facet(&upper->stl, &quadrilateral[0]);
stl_add_facet(&upper->stl, &quadrilateral[1]); 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"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part";
ExPolygons section; ExPolygons section;
this->make_expolygons_simple(upper_lines, &section); this->make_expolygons_simple(upper_lines, &section);
@ -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"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part";
ExPolygons section; ExPolygons section;
this->make_expolygons_simple(lower_lines, &section); this->make_expolygons_simple(lower_lines, &section);
@ -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 //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html
TriangleMesh make_sphere(double radius, double fa) TriangleMesh make_sphere(double radius, double fa)
{ {
int sectorCount = ceil(2. * M_PI / fa); int sectorCount = int(ceil(2. * M_PI / fa));
int stackCount = ceil(M_PI / fa); int stackCount = int(ceil(M_PI / fa));
float sectorStep = 2. * M_PI / sectorCount; float sectorStep = float(2. * M_PI / sectorCount);
float stackStep = M_PI / stackCount; float stackStep = float(M_PI / stackCount);
Pointf3s vertices; Pointf3s vertices;
vertices.reserve((stackCount - 1) * sectorCount + 2); vertices.reserve((stackCount - 1) * sectorCount + 2);

View file

@ -21,19 +21,13 @@ typedef std::vector<TriangleMesh*> TriangleMeshPtrs;
class TriangleMesh class TriangleMesh
{ {
public: public:
TriangleMesh() : repaired(false) { stl_initialize(&this->stl); } TriangleMesh() : repaired(false) {}
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets); TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); }
~TriangleMesh() { clear(); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }
TriangleMesh& operator=(const TriangleMesh &other); bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); }
TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } void repair(bool update_shared_vertices = true);
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();
float volume(); float volume();
void check_topology(); void check_topology();
bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } 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; TriangleMeshPtrs split() const;
void merge(const TriangleMesh &mesh); void merge(const TriangleMesh &mesh);
ExPolygons horizontal_projection() const; 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. // 2D convex hull of a 3D mesh projected into the Z=0 plane.
Polygon convex_hull(); Polygon convex_hull();
BoundingBoxf3 bounding_box() const; BoundingBoxf3 bounding_box() const;
@ -69,12 +63,13 @@ public:
void reset_repair_stats(); void reset_repair_stats();
bool needed_repair() const; bool needed_repair() const;
void require_shared_vertices(); 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; } size_t facets_count() const { return this->stl.stats.number_of_facets; }
bool empty() const { return this->facets_count() == 0; } bool empty() const { return this->facets_count() == 0; }
bool is_splittable() const; bool is_splittable() const;
stl_file stl; stl_file stl;
indexed_triangle_set its;
bool repaired; bool repaired;
private: private:

View file

@ -198,6 +198,11 @@ size_t Index::load(const boost::filesystem::path &path)
size_t idx_line = 0; size_t idx_line = 0;
Version ver; Version ver;
while (std::getline(ifs, line)) { while (std::getline(ifs, line)) {
#ifndef _MSVCVER
// On a Unix system, getline does not remove the trailing carriage returns, if the index is shared over a Windows filesystem. Remove them manually.
while (! line.empty() && line.back() == '\r')
line.pop_back();
#endif
++ idx_line; ++ idx_line;
// Skip the initial white spaces. // Skip the initial white spaces.
char *key = left_trim(const_cast<char*>(line.data())); char *key = left_trim(const_cast<char*>(line.data()));

View file

@ -241,8 +241,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
: m_transformed_bounding_box_dirty(true) : m_transformed_bounding_box_dirty(true)
, m_sla_shift_z(0.0) , m_sla_shift_z(0.0)
, m_transformed_convex_hull_bounding_box_dirty(true) , m_transformed_convex_hull_bounding_box_dirty(true)
, m_convex_hull(nullptr)
, m_convex_hull_owned(false)
// geometry_id == 0 -> invalid // geometry_id == 0 -> invalid
, geometry_id(std::pair<size_t, size_t>(0, 0)) , geometry_id(std::pair<size_t, size_t>(0, 0))
, extruder_id(0) , extruder_id(0)
@ -268,12 +266,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
set_render_color(r, g, b, a); set_render_color(r, g, b, a);
} }
GLVolume::~GLVolume()
{
if (m_convex_hull_owned)
delete m_convex_hull;
}
void GLVolume::set_render_color(float r, float g, float b, float a) void GLVolume::set_render_color(float r, float g, float b, float a)
{ {
render_color[0] = r; render_color[0] = r;
@ -335,12 +327,6 @@ void GLVolume::set_color_from_model_volume(const ModelVolume *model_volume)
color[3] = model_volume->is_model_part() ? 1.f : 0.5f; color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
} }
void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned)
{
m_convex_hull = convex_hull;
m_convex_hull_owned = owned;
}
Transform3d GLVolume::world_matrix() const Transform3d GLVolume::world_matrix() const
{ {
Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix();
@ -377,7 +363,7 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const
BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const
{ {
return (m_convex_hull != nullptr && m_convex_hull->stl.stats.number_of_facets > 0) ? return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ?
m_convex_hull->transformed_bounding_box(trafo) : m_convex_hull->transformed_bounding_box(trafo) :
bounding_box.transformed(trafo); bounding_box.transformed(trafo);
} }
@ -587,7 +573,7 @@ int GLVolumeCollection::load_object_volume(
const ModelVolume *model_volume = model_object->volumes[volume_idx]; const ModelVolume *model_volume = model_object->volumes[volume_idx];
const int extruder_id = model_volume->extruder_id(); const int extruder_id = model_volume->extruder_id();
const ModelInstance *instance = model_object->instances[instance_idx]; const ModelInstance *instance = model_object->instances[instance_idx];
const TriangleMesh& mesh = model_volume->mesh; const TriangleMesh& mesh = model_volume->mesh();
float color[4]; float color[4];
memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
/* if (model_volume->is_support_blocker()) { /* if (model_volume->is_support_blocker()) {
@ -613,7 +599,7 @@ int GLVolumeCollection::load_object_volume(
if (model_volume->is_model_part()) if (model_volume->is_model_part())
{ {
// GLVolume will reference a convex hull from model_volume! // GLVolume will reference a convex hull from model_volume!
v.set_convex_hull(&model_volume->get_convex_hull(), false); v.set_convex_hull(model_volume->get_convex_hull_shared_ptr());
if (extruder_id != -1) if (extruder_id != -1)
v.extruder_id = extruder_id; v.extruder_id = extruder_id;
} }
@ -656,7 +642,10 @@ void GLVolumeCollection::load_object_auxiliary(
v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first); v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first);
v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id); v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true); if (&instance_idx == &instances.back())
v.set_convex_hull(std::move(convex_hull));
else
v.set_convex_hull(convex_hull);
v.is_modifier = false; v.is_modifier = false;
v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree);
v.set_instance_transformation(model_instance.get_transformation()); v.set_instance_transformation(model_instance.get_transformation());

View file

@ -10,6 +10,7 @@
#include "slic3r/GUI/GLCanvas3DManager.hpp" #include "slic3r/GUI/GLCanvas3DManager.hpp"
#include <functional> #include <functional>
#include <memory>
#ifndef NDEBUG #ifndef NDEBUG
#define HAS_GLSAFE #define HAS_GLSAFE
@ -243,7 +244,6 @@ public:
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
~GLVolume();
private: private:
Geometry::Transformation m_instance_transformation; Geometry::Transformation m_instance_transformation;
@ -255,10 +255,8 @@ private:
mutable BoundingBoxf3 m_transformed_bounding_box; mutable BoundingBoxf3 m_transformed_bounding_box;
// Whether or not is needed to recalculate the transformed bounding box. // Whether or not is needed to recalculate the transformed bounding box.
mutable bool m_transformed_bounding_box_dirty; mutable bool m_transformed_bounding_box_dirty;
// Pointer to convex hull of the original mesh, if any. // Convex hull of the volume, if any.
// This object may or may not own the convex hull instance based on m_convex_hull_owned std::shared_ptr<const TriangleMesh> m_convex_hull;
const TriangleMesh* m_convex_hull;
bool m_convex_hull_owned;
// Bounding box of this volume, in unscaled coordinates. // Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box; mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
// Whether or not is needed to recalculate the transformed convex hull bounding box. // Whether or not is needed to recalculate the transformed convex hull bounding box.
@ -395,7 +393,9 @@ public:
double get_sla_shift_z() const { return m_sla_shift_z; } double get_sla_shift_z() const { return m_sla_shift_z; }
void set_sla_shift_z(double z) { m_sla_shift_z = z; } void set_sla_shift_z(double z) { m_sla_shift_z = z; }
void set_convex_hull(const TriangleMesh *convex_hull, bool owned); void set_convex_hull(std::shared_ptr<const TriangleMesh> convex_hull) { m_convex_hull = std::move(convex_hull); }
void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(convex_hull); }
void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(std::move(convex_hull)); }
int object_idx() const { return this->composite_id.object_id; } int object_idx() const { return this->composite_id.object_id; }
int volume_idx() const { return this->composite_id.volume_id; } int volume_idx() const { return this->composite_id.volume_id; }

View file

@ -89,7 +89,7 @@ void BackgroundSlicingProcess::process_fff()
// Perform the final post-processing of the export path by applying the print statistics over the file name. // Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
if (copy_file(m_temp_output_path, export_path) != 0) if (copy_file(m_temp_output_path, export_path) != 0)
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?")));
m_print->set_status(95, _utf8(L("Running post-processing scripts"))); m_print->set_status(95, _utf8(L("Running post-processing scripts")));
run_post_process_scripts(export_path, m_fff_print->config()); run_post_process_scripts(export_path, m_fff_print->config());
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());

View file

@ -5524,7 +5524,7 @@ void GLCanvas3D::_load_sla_shells()
v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0));
v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation));
v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.);
v.set_convex_hull(new TriangleMesh(std::move(mesh.convex_hull_3d())), true); v.set_convex_hull(mesh.convex_hull_3d());
}; };
// adds objects' volumes // adds objects' volumes

View file

@ -261,7 +261,7 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /
const stl_stats& stats = vol_idx == -1 ? const stl_stats& stats = vol_idx == -1 ?
(*m_objects)[obj_idx]->get_object_stl_stats() : (*m_objects)[obj_idx]->get_object_stl_stats() :
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats; (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stl.stats;
std::map<std::string, int> error_msg = { std::map<std::string, int> error_msg = {
{ L("degenerate facets"), stats.degenerate_facets }, { L("degenerate facets"), stats.degenerate_facets },
@ -1597,7 +1597,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
// First (any) GLVolume of the selected instance. They all share the same instance matrix. // First (any) GLVolume of the selected instance. They all share the same instance matrix.
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
// Transform the new modifier to be aligned with the print bed. // Transform the new modifier to be aligned with the print bed.
const BoundingBoxf3 mesh_bb = new_volume->mesh.bounding_box(); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
// Set the modifier position. // Set the modifier position.
auto offset = (type_name == "Slab") ? auto offset = (type_name == "Slab") ?

View file

@ -92,6 +92,7 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo)
combo->SetValue(selection); combo->SetValue(selection);
} }
ObjectManipulation::ObjectManipulation(wxWindow* parent) : ObjectManipulation::ObjectManipulation(wxWindow* parent) :
OG_Settings(parent, true) OG_Settings(parent, true)
#ifndef __APPLE__ #ifndef __APPLE__
@ -162,16 +163,71 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
const int field_width = 5; const int field_width = 5;
// Mirror button size:
const int mirror_btn_width = 3;
// Legend for object modification // Legend for object modification
line = Line{ "", "" }; line = Line{ "", "" };
def.label = ""; def.label = "";
def.type = coString; def.type = coString;
def.width = field_width/*50*/; def.width = field_width - mirror_btn_width;//field_width/*50*/;
// Load bitmaps to be used for the mirroring buttons:
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on.png");
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png");
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
for (const std::string axis : { "x", "y", "z" }) { for (const std::string axis : { "x", "y", "z" }) {
const std::string label = boost::algorithm::to_upper_copy(axis); const std::string label = boost::algorithm::to_upper_copy(axis);
def.set_default_value(new ConfigOptionString{ " " + label }); def.set_default_value(new ConfigOptionString{ " " + label });
Option option = Option(def, axis + "_axis_legend"); Option option = Option(def, axis + "_axis_legend");
unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2
// We will add a button to toggle mirroring to each axis:
auto mirror_button = [=](wxWindow* parent) {
wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width);
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label));
m_mirror_buttons[axis_idx].first = btn;
m_mirror_buttons[axis_idx].second = mbShown;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
Axis axis = (Axis)(axis_idx + X);
if (m_mirror_buttons[axis_idx].second == mbHidden)
return;
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()){
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis));
}
}
else
return;
// Update mirroring at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror();
canvas->set_as_dirty();
UpdateAndShow(true);
});
return sizer;
};
option.side_widget = mirror_button;
line.append_option(option); line.append_option(option);
} }
line.near_label_widget = [this](wxWindow* parent) { line.near_label_widget = [this](wxWindow* parent) {
@ -190,8 +246,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
def.set_default_value(new ConfigOptionFloat(0.0)); def.set_default_value(new ConfigOptionFloat(0.0));
def.width = field_width/*50*/; def.width = field_width/*50*/;
// Add "uniform scaling" button in front of "Scale" option
if (option_name == "Scale") { if (option_name == "Scale") {
// Add "uniform scaling" button in front of "Scale" option
line.near_label_widget = [this](wxWindow* parent) { line.near_label_widget = [this](wxWindow* parent) {
auto btn = new LockButton(parent, wxID_ANY); auto btn = new LockButton(parent, wxID_ANY);
btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){ btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){
@ -201,8 +257,59 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
m_lock_bnt = btn; m_lock_bnt = btn;
return btn; return btn;
}; };
// Add reset scale button
auto reset_scale_button = [=](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
btn->SetToolTip(_(L("Reset scale")));
m_reset_scale_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
change_scale_value(0, 100.);
change_scale_value(1, 100.);
change_scale_value(2, 100.);
});
return sizer;
};
line.append_widget(reset_scale_button);
} }
else if (option_name == "Rotation") {
// Add reset rotation button
auto reset_rotation_button = [=](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
btn->SetToolTip(_(L("Reset rotation")));
m_reset_rotation_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_rotation(Vec3d::Zero());
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()){
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_rotation(Vec3d::Zero());
}
}
else
return;
// Update rotation at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate();
UpdateAndShow(true);
});
return sizer;
};
line.append_widget(reset_rotation_button);
}
// Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment
else if (option_name == "Size") { else if (option_name == "Size") {
line.near_label_widget = [this](wxWindow* parent) { line.near_label_widget = [this](wxWindow* parent) {
@ -224,8 +331,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
return line; return line;
}; };
// Settings table // Settings table
m_og->sidetext_width = 3;
m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label); m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label);
m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label); m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label);
m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label); m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label);
@ -239,6 +346,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
ctrl->msw_rescale(); ctrl->msw_rescale();
}; };
} }
void ObjectManipulation::Show(const bool show) void ObjectManipulation::Show(const bool show)
{ {
@ -408,9 +517,95 @@ void ObjectManipulation::update_if_dirty()
else else
m_og->disable(); m_og->disable();
update_reset_buttons_visibility();
update_mirror_buttons_visibility();
m_dirty = false; m_dirty = false;
} }
void ObjectManipulation::update_reset_buttons_visibility()
{
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
if (!canvas)
return;
const Selection& selection = canvas->get_selection();
bool show_rotation = false;
bool show_scale = false;
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Vec3d rotation;
Vec3d scale;
if (selection.is_single_full_instance()) {
rotation = volume->get_instance_rotation();
scale = volume->get_instance_scaling_factor();
}
else {
rotation = volume->get_volume_rotation();
scale = volume->get_volume_scaling_factor();
}
show_rotation = !rotation.isApprox(Vec3d::Zero());
show_scale = !scale.isApprox(Vec3d::Ones());
}
wxGetApp().CallAfter([this, show_rotation, show_scale]{
m_reset_rotation_button->Show(show_rotation);
m_reset_scale_button->Show(show_scale);
});
}
void ObjectManipulation::update_mirror_buttons_visibility()
{
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
std::array<MirrorButtonState, 3> new_states = {mbHidden, mbHidden, mbHidden};
if (!m_world_coordinates) {
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Vec3d mirror;
if (selection.is_single_full_instance())
mirror = volume->get_instance_mirror();
else
mirror = volume->get_volume_mirror();
for (unsigned char i=0; i<3; ++i)
new_states[i] = (mirror[i] < 0. ? mbActive : mbShown);
}
}
else {
// the mirroring buttons should be hidden in world coordinates,
// unless we make it actually mirror in world coords.
}
// Hiding the buttons through Hide() always messed up the sizers. As a workaround, the button
// is assigned a transparent bitmap. We must of course remember the actual state.
wxGetApp().CallAfter([this, new_states]{
for (int i=0; i<3; ++i) {
if (new_states[i] != m_mirror_buttons[i].second) {
const wxBitmap* bmp;
switch (new_states[i]) {
case mbHidden : bmp = &m_mirror_bitmap_hidden.bmp(); m_mirror_buttons[i].first->Enable(false); break;
case mbShown : bmp = &m_mirror_bitmap_off.bmp(); m_mirror_buttons[i].first->Enable(true); break;
case mbActive : bmp = &m_mirror_bitmap_on.bmp(); m_mirror_buttons[i].first->Enable(true); break;
}
m_mirror_buttons[i].first->SetBitmap(*bmp);
m_mirror_buttons[i].second = new_states[i];
}
}
});
}
#ifndef __APPLE__ #ifndef __APPLE__
void ObjectManipulation::emulate_kill_focus() void ObjectManipulation::emulate_kill_focus()
{ {
@ -493,7 +688,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
m_cache.rotation = rotation; m_cache.rotation = rotation;
m_cache.rotation_rounded(axis) = DBL_MAX; m_cache.rotation_rounded(axis) = DBL_MAX;
this->UpdateAndShow(true); this->UpdateAndShow(true);
} }
void ObjectManipulation::change_scale_value(int axis, double value) void ObjectManipulation::change_scale_value(int axis, double value)
@ -511,6 +706,7 @@ void ObjectManipulation::change_scale_value(int axis, double value)
this->UpdateAndShow(true); this->UpdateAndShow(true);
} }
void ObjectManipulation::change_size_value(int axis, double value) void ObjectManipulation::change_size_value(int axis, double value)
{ {
if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON) if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON)
@ -666,6 +862,12 @@ void ObjectManipulation::msw_rescale()
m_manifold_warning_bmp.msw_rescale(); m_manifold_warning_bmp.msw_rescale();
m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp()); m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp());
m_mirror_bitmap_on.msw_rescale();
m_mirror_bitmap_off.msw_rescale();
m_mirror_bitmap_hidden.msw_rescale();
m_reset_scale_button->msw_rescale();
m_reset_rotation_button->msw_rescale();
get_og()->msw_rescale(); get_og()->msw_rescale();
} }

View file

@ -53,6 +53,23 @@ class ObjectManipulation : public OG_Settings
wxStaticText* m_scale_Label = nullptr; wxStaticText* m_scale_Label = nullptr;
wxStaticText* m_rotate_Label = nullptr; wxStaticText* m_rotate_Label = nullptr;
// Non-owning pointers to the reset buttons, so we can hide and show them.
ScalableButton* m_reset_scale_button = nullptr;
ScalableButton* m_reset_rotation_button = nullptr;
// Mirroring buttons and their current state
enum MirrorButtonState {
mbHidden,
mbShown,
mbActive
};
std::array<std::pair<ScalableButton*, MirrorButtonState>, 3> m_mirror_buttons;
// Bitmaps for the mirroring buttons.
ScalableBitmap m_mirror_bitmap_on;
ScalableBitmap m_mirror_bitmap_off;
ScalableBitmap m_mirror_bitmap_hidden;
// Needs to be updated from OnIdle? // Needs to be updated from OnIdle?
bool m_dirty = false; bool m_dirty = false;
// Cached labels for the delayed update, not localized! // Cached labels for the delayed update, not localized!
@ -111,10 +128,10 @@ private:
void reset_settings_value(); void reset_settings_value();
void update_settings_value(const Selection& selection); void update_settings_value(const Selection& selection);
// update size values after scale unit changing or "gizmos" // Show or hide scale/rotation reset buttons if needed
void update_size_value(const Vec3d& size); void update_reset_buttons_visibility();
// update rotation value after "gizmos" //Show or hide mirror buttons
void update_rotation_value(const Vec3d& rotation); void update_mirror_buttons_visibility();
// change values // change values
void change_position_value(int axis, double value); void change_position_value(int axis, double value);

View file

@ -27,6 +27,7 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_i
: GLGizmoBase(parent, sprite_id) : GLGizmoBase(parent, sprite_id)
#endif // ENABLE_SVG_ICONS #endif // ENABLE_SVG_ICONS
, m_quadric(nullptr) , m_quadric(nullptr)
, m_its(nullptr)
{ {
m_quadric = ::gluNewQuadric(); m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr) if (m_quadric != nullptr)
@ -379,36 +380,23 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const
bool GLGizmoSlaSupports::is_mesh_update_necessary() const bool GLGizmoSlaSupports::is_mesh_update_necessary() const
{ {
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
&& ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0); && ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr);
} }
void GLGizmoSlaSupports::update_mesh() void GLGizmoSlaSupports::update_mesh()
{ {
wxBusyCursor wait; wxBusyCursor wait;
Eigen::MatrixXf& V = m_V;
Eigen::MatrixXi& F = m_F;
// We rely on SLA model object having a single volume,
// this way we can use that mesh directly. // this way we can use that mesh directly.
// This mesh does not account for the possible Z up SLA offset. // This mesh does not account for the possible Z up SLA offset.
m_mesh = &m_model_object->volumes.front()->mesh; m_mesh = &m_model_object->volumes.front()->mesh();
const_cast<TriangleMesh*>(m_mesh)->require_shared_vertices(); // TriangleMeshSlicer needs this m_its = &m_mesh->its;
const stl_file& stl = m_mesh->stl;
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) = facet->vertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2);
V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2);
V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2);
F(i, 0) = 3*i+0;
F(i, 1) = 3*i+1;
F(i, 2) = 3*i+2;
}
m_current_mesh_model_id = m_model_object->id(); m_current_mesh_model_id = m_model_object->id();
m_editing_mode = false; m_editing_mode = false;
m_AABB = igl::AABB<Eigen::MatrixXf,3>(); m_AABB.deinit();
m_AABB.init(m_V, m_F); m_AABB.init(
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3));
} }
// Unprojects the mouse position on the mesh and return the hit point and normal of the facet. // Unprojects the mouse position on the mesh and return the hit point and normal of the facet.
@ -416,7 +404,7 @@ void GLGizmoSlaSupports::update_mesh()
std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
{ {
// if the gizmo doesn't have the V, F structures for igl, calculate them first: // if the gizmo doesn't have the V, F structures for igl, calculate them first:
if (m_V.size() == 0) if (m_its == nullptr)
update_mesh(); update_mesh();
const Camera& camera = m_parent.get_camera(); const Camera& camera = m_parent.get_camera();
@ -442,7 +430,10 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse
point1 = inv * point1; point1 = inv * point1;
point2 = inv * point2; point2 = inv * point2;
if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hits)) if (!m_AABB.intersect_ray(
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
point1.cast<float>(), (point2-point1).cast<float>(), hits))
throw std::invalid_argument("unproject_on_mesh(): No intersection found."); throw std::invalid_argument("unproject_on_mesh(): No intersection found.");
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
@ -457,9 +448,9 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse
igl::Hit& hit = hits[i]; igl::Hit& hit = hits[i];
int fid = hit.id; // facet id int fid = hit.id; // facet id
bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]);
b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]);
result = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); result = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)];
if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast<double>())) if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast<double>()))
break; break;
} }
@ -564,15 +555,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
// Cast a ray in the direction of the camera and look for intersection with the mesh: // Cast a ray in the direction of the camera and look for intersection with the mesh:
std::vector<igl::Hit> hits; std::vector<igl::Hit> hits;
// Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies.
if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { if (m_AABB.intersect_ray(
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) {
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
if (m_clipping_plane_distance != 0.f) { if (m_clipping_plane_distance != 0.f) {
// If the closest hit facet normal points in the same direction as the ray, // If the closest hit facet normal points in the same direction as the ray,
// we are looking through the mesh and should therefore discard the point: // we are looking through the mesh and should therefore discard the point:
int fid = hits.front().id; // facet id int fid = hits.front().id; // facet id
Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); Vec3f a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]);
Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); Vec3f b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]);
if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f) if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f)
is_obscured = true; is_obscured = true;
@ -582,7 +576,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
int fid = hit.id; // facet id int fid = hit.id; // facet id
Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
Vec3f hit_pos = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); Vec3f hit_pos = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)];
if (is_point_clipped(hit_pos.cast<double>())) { if (is_point_clipped(hit_pos.cast<double>())) {
hits.erase(hits.begin()+j); hits.erase(hits.begin()+j);
--j; --j;
@ -759,9 +753,12 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const
int idx = 0; int idx = 0;
Eigen::Matrix<float, 1, 3> pp = m_editing_mode_cache[i].support_point.pos; Eigen::Matrix<float, 1, 3> pp = m_editing_mode_cache[i].support_point.pos;
Eigen::Matrix<float, 1, 3> cc; Eigen::Matrix<float, 1, 3> cc;
m_AABB.squared_distance(m_V, m_F, pp, idx, cc); m_AABB.squared_distance(
Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
pp, idx, cc);
Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]);
Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]);
m_editing_mode_cache[i].normal = a.cross(b); m_editing_mode_cache[i].normal = a.cross(b);
} }
@ -1067,8 +1064,7 @@ void GLGizmoSlaSupports::on_set_state()
m_clipping_plane_distance = 0.f; m_clipping_plane_distance = 0.f;
// Release triangle mesh slicer and the AABB spatial search structure. // Release triangle mesh slicer and the AABB spatial search structure.
m_AABB.deinit(); m_AABB.deinit();
m_V = Eigen::MatrixXf(); m_its = nullptr;
m_F = Eigen::MatrixXi();
m_tms.reset(); m_tms.reset();
m_supports_tms.reset(); m_supports_tms.reset();
}); });

View file

@ -35,10 +35,11 @@ private:
const float RenderPointScale = 1.f; const float RenderPointScale = 1.f;
GLUquadricObj* m_quadric; GLUquadricObj* m_quadric;
Eigen::MatrixXf m_V; // vertices typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
Eigen::MatrixXi m_F; // facets indices typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
igl::AABB<Eigen::MatrixXf,3> m_AABB; igl::AABB<MapMatrixXfUnaligned, 3> m_AABB;
const TriangleMesh* m_mesh; const TriangleMesh* m_mesh;
const indexed_triangle_set* m_its;
mutable const TriangleMesh* m_supports_mesh; mutable const TriangleMesh* m_supports_mesh;
mutable std::vector<Vec2f> m_triangles; mutable std::vector<Vec2f> m_triangles;
mutable std::vector<Vec2f> m_supports_triangles; mutable std::vector<Vec2f> m_supports_triangles;
@ -131,6 +132,11 @@ private:
protected: protected:
void on_set_state() override; void on_set_state() override;
virtual void on_set_hover_id()
{
if ((int)m_editing_mode_cache.size() <= m_hover_id)
m_hover_id = -1;
}
void on_start_dragging(const Selection& selection) override; void on_start_dragging(const Selection& selection) override;
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override; virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override;

View file

@ -54,7 +54,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
#endif // _WIN32 #endif // _WIN32
// initialize status bar // initialize status bar
m_statusbar = new ProgressStatusBar(this); m_statusbar.reset(new ProgressStatusBar(this));
m_statusbar->embed(this); m_statusbar->embed(this);
m_statusbar->set_status_text(_(L("Version")) + " " + m_statusbar->set_status_text(_(L("Version")) + " " +
SLIC3R_VERSION + SLIC3R_VERSION +
@ -103,6 +103,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
event.Veto(); event.Veto();
return; return;
} }
if(m_plater) m_plater->stop_jobs();
// Weird things happen as the Paint messages are floating around the windows being destructed. // Weird things happen as the Paint messages are floating around the windows being destructed.
// Avoid the Paint messages by hiding the main window. // Avoid the Paint messages by hiding the main window.
@ -138,6 +140,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
update_ui_from_settings(); // FIXME (?) update_ui_from_settings(); // FIXME (?)
} }
MainFrame::~MainFrame() = default;
void MainFrame::update_title() void MainFrame::update_title()
{ {
wxString title = wxEmptyString; wxString title = wxEmptyString;

View file

@ -89,7 +89,7 @@ protected:
public: public:
MainFrame(); MainFrame();
~MainFrame() {} ~MainFrame();
Plater* plater() { return m_plater; } Plater* plater() { return m_plater; }
@ -126,7 +126,7 @@ public:
Plater* m_plater { nullptr }; Plater* m_plater { nullptr };
wxNotebook* m_tabpanel { nullptr }; wxNotebook* m_tabpanel { nullptr };
wxProgressDialog* m_progress_dialog { nullptr }; wxProgressDialog* m_progress_dialog { nullptr };
ProgressStatusBar* m_statusbar { nullptr }; std::unique_ptr<ProgressStatusBar> m_statusbar;
}; };
} // GUI } // GUI

View file

@ -276,7 +276,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n
// add sidetext if any // add sidetext if any
if (option.sidetext != "") { if (option.sidetext != "") {
auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition,
/*wxSize(sidetext_width*wxGetApp().em_unit(), -1)*/wxDefaultSize, wxALIGN_LEFT); wxSize(sidetext_width != -1 ? sidetext_width*wxGetApp().em_unit() : -1, -1) /*wxDefaultSize*/, wxALIGN_LEFT);
sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT); sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT);
sidetext->SetFont(wxGetApp().normal_font()); sidetext->SetFont(wxGetApp().normal_font());
sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);

View file

@ -5,9 +5,11 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <regex> #include <regex>
#include <future>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h> #include <wx/stattext.h>
@ -37,7 +39,12 @@
#include "libslic3r/SLA/SLARotfinder.hpp" #include "libslic3r/SLA/SLARotfinder.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libnest2d/optimizers/nlopt/genetic.hpp" //#include "libslic3r/ClipperUtils.hpp"
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
// #include "libnest2d/backends/clipper/geometries.hpp"
// #include "libnest2d/utils/rotcalipers.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
#include "GUI.hpp" #include "GUI.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
@ -1253,8 +1260,243 @@ struct Plater::priv
Preview *preview; Preview *preview;
BackgroundSlicingProcess background_process; BackgroundSlicingProcess background_process;
bool arranging;
bool rotoptimizing; // A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
// state in the status progress indicator. On the other hand they are
// separated from the background slicing process. Ideally, these jobs should
// run when the background process is not running.
//
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class Job: public wxEvtHandler {
int m_range = 100;
std::future<void> m_ftr;
priv *m_plater = nullptr;
std::atomic<bool> m_running {false}, m_canceled {false};
bool m_finalized = false;
void run() {
m_running.store(true); process(); m_running.store(false);
// ensure to call the last status to finalize the job
update_status(status_range(), "");
}
protected:
// status range for a particular job
virtual int status_range() const { return 100; }
// status update, to be used from the work thread (process() method)
void update_status(int st, const wxString& msg = "") {
auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg);
wxQueueEvent(this, evt);
}
priv& plater() { return *m_plater; }
bool was_canceled() const { return m_canceled.load(); }
// Launched just before start(), a job can use it to prepare internals
virtual void prepare() {}
// Launched when the job is finished. It refreshes the 3dscene by def.
virtual void finalize() {
// Do a full refresh of scene tree, including regenerating
// all the GLVolumes. FIXME The update function shall just
// reload the modified matrices.
if(! was_canceled())
plater().update(true);
}
public:
Job(priv *_plater): m_plater(_plater)
{
Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){
auto msg = evt.GetString();
if(! msg.empty()) plater().statusbar()->set_status_text(msg);
if(m_finalized) return;
plater().statusbar()->set_progress(evt.GetInt());
if(evt.GetInt() == status_range()) {
// set back the original range and cancel callback
plater().statusbar()->set_range(m_range);
plater().statusbar()->set_cancel_callback();
wxEndBusyCursor();
finalize();
// dont do finalization again for the same process
m_finalized = true;
}
});
}
// TODO: use this when we all migrated to VS2019
// Job(const Job&) = delete;
// Job(Job&&) = default;
// Job& operator=(const Job&) = delete;
// Job& operator=(Job&&) = default;
Job(const Job&) = delete;
Job& operator=(const Job&) = delete;
Job(Job &&o) :
m_range(o.m_range),
m_ftr(std::move(o.m_ftr)),
m_plater(o.m_plater),
m_finalized(o.m_finalized)
{
m_running.store(o.m_running.load());
m_canceled.store(o.m_canceled.load());
}
virtual void process() = 0;
void start() { // Start the job. No effect if the job is already running
if(! m_running.load()) {
prepare();
// Save the current status indicatior range and push the new one
m_range = plater().statusbar()->get_range();
plater().statusbar()->set_range(status_range());
// init cancellation flag and set the cancel callback
m_canceled.store(false);
plater().statusbar()->set_cancel_callback( [this](){
m_canceled.store(true);
});
m_finalized = false;
// Changing cursor to busy
wxBeginBusyCursor();
try { // Execute the job
m_ftr = std::async(std::launch::async, &Job::run, this);
} catch(std::exception& ) {
update_status(status_range(),
_(L("ERROR: not enough resources to execute a new job.")));
}
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
}
}
// To wait for the running job and join the threads. False is returned
// if the timeout has been reached and the job is still running. Call
// cancel() before this fn if you want to explicitly end the job.
bool join(int timeout_ms = 0) const {
if(!m_ftr.valid()) return true;
if(timeout_ms <= 0)
m_ftr.wait();
else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==
std::future_status::timeout)
return false;
return true;
}
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
};
enum class Jobs : size_t {
Arrange,
Rotoptimize
};
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started.
class ExclusiveJobGroup {
static const int ABORT_WAIT_MAX_MS = 10000;
priv * m_plater;
class ArrangeJob : public Job
{
int count = 0;
protected:
void prepare() override
{
count = 0;
for (auto obj : plater().model.objects)
count += int(obj->instances.size());
}
public:
//using Job::Job;
ArrangeJob(priv * pltr): Job(pltr) {}
int status_range() const override { return count; }
void set_count(int c) { count = c; }
void process() override;
} arrange_job/*{m_plater}*/;
class RotoptimizeJob : public Job
{
public:
//using Job::Job;
RotoptimizeJob(priv * pltr): Job(pltr) {}
void process() override;
} rotoptimize_job/*{m_plater}*/;
// To create a new job, just define a new subclass of Job, implement
// the process and the optional prepare() and finalize() methods
// Register the instance of the class in the m_jobs container
// if it cannot run concurrently with other jobs in this group
std::vector<std::reference_wrapper<Job>> m_jobs/*{arrange_job,
rotoptimize_job}*/;
public:
ExclusiveJobGroup(priv *_plater)
: m_plater(_plater)
, arrange_job(m_plater)
, rotoptimize_job(m_plater)
, m_jobs({arrange_job, rotoptimize_job})
{}
void start(Jobs jid) {
m_plater->background_process.stop();
stop_all();
m_jobs[size_t(jid)].get().start();
}
void cancel_all() { for (Job& j : m_jobs) j.cancel(); }
void join_all(int wait_ms = 0)
{
std::vector<bool> aborted(m_jobs.size(), false);
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
aborted[jid] = m_jobs[jid].get().join(wait_ms);
if (!all_of(aborted))
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
}
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; }
bool is_any_running() const
{
return std::any_of(m_jobs.begin(),
m_jobs.end(),
[](const Job &j) { return j.is_running(); });
}
} m_ui_jobs{this};
bool delayed_scene_refresh; bool delayed_scene_refresh;
std::string delayed_error_message; std::string delayed_error_message;
@ -1429,8 +1671,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
{ {
this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
arranging = false;
rotoptimizing = false;
background_process.set_fff_print(&fff_print); background_process.set_fff_print(&fff_print);
background_process.set_sla_print(&sla_print); background_process.set_sla_print(&sla_print);
background_process.set_gcode_preview_data(&gcode_preview_data); background_process.set_gcode_preview_data(&gcode_preview_data);
@ -1536,7 +1776,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
void Plater::priv::update(bool force_full_scene_refresh) void Plater::priv::update(bool force_full_scene_refresh)
{ {
wxWindowUpdateLocker freeze_guard(q); // the following line, when enabled, causes flickering on NVIDIA graphics cards
// wxWindowUpdateLocker freeze_guard(q);
if (get_config("autocenter") == "1") { if (get_config("autocenter") == "1") {
// auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); // auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
// const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); // const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values);
@ -1606,7 +1847,7 @@ void Plater::priv::update_ui_from_settings()
ProgressStatusBar* Plater::priv::statusbar() ProgressStatusBar* Plater::priv::statusbar()
{ {
return main_frame->m_statusbar; return main_frame->m_statusbar.get();
} }
std::string Plater::priv::get_config(const std::string &key) const std::string Plater::priv::get_config(const std::string &key) const
@ -2143,59 +2384,45 @@ void Plater::priv::mirror(Axis axis)
void Plater::priv::arrange() void Plater::priv::arrange()
{ {
if (arranging) { return; } m_ui_jobs.start(Jobs::Arrange);
arranging = true; }
Slic3r::ScopeGuard arranging_guard([this]() { arranging = false; });
wxBusyCursor wait; // This method will find an optimal orientation for the currently selected item
// Very similar in nature to the arrange method above...
void Plater::priv::sla_optimize_rotation() {
m_ui_jobs.start(Jobs::Rotoptimize);
}
this->background_process.stop(); void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
// TODO: we should decide whether to allow arrange when the search is
// running we should probably disable explicit slicing and background
// processing
unsigned count = 0; static const auto arrangestr = _(L("Arranging"));
for(auto obj : model.objects) count += obj->instances.size();
auto prev_range = statusbar()->get_range(); auto &config = plater().config;
statusbar()->set_range(count); auto &view3D = plater().view3D;
auto &model = plater().model;
auto statusfn = [this, count] (unsigned st, const std::string& msg) {
/* // In case we would run the arrange asynchronously
wxCommandEvent event(EVT_PROGRESS_BAR);
event.SetInt(st);
event.SetString(msg);
wxQueueEvent(this->q, event.Clone()); */
statusbar()->set_progress(count - st);
statusbar()->set_status_text(_(msg));
// ok, this is dangerous, but we are protected by the flag
// 'arranging' and the arrange button is also disabled.
// This call is needed for the cancel button to work.
wxYieldIfNeeded();
};
statusbar()->set_cancel_callback([this, statusfn](){
arranging = false;
statusfn(0, L("Arranging canceled"));
});
static const std::string arrangestr = L("Arranging");
// FIXME: I don't know how to obtain the minimum distance, it depends // FIXME: I don't know how to obtain the minimum distance, it depends
// on printer technology. I guess the following should work but it crashes. // on printer technology. I guess the following should work but it crashes.
double dist = 6; //PrintConfig::min_object_distance(config); double dist = 6; // PrintConfig::min_object_distance(config);
if(printer_technology == ptFFF) { if (plater().printer_technology == ptFFF) {
dist = PrintConfig::min_object_distance(config); dist = PrintConfig::min_object_distance(config);
} }
auto min_obj_distance = coord_t(dist/SCALING_FACTOR); auto min_obj_distance = coord_t(dist / SCALING_FACTOR);
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); const auto *bed_shape_opt = config->opt<ConfigOptionPoints>(
"bed_shape");
assert(bed_shape_opt); assert(bed_shape_opt);
auto& bedpoints = bed_shape_opt->values; auto & bedpoints = bed_shape_opt->values;
Polyline bed; bed.points.reserve(bedpoints.size()); Polyline bed;
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); bed.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
statusfn(0, arrangestr); update_status(0, arrangestr);
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
@ -2211,129 +2438,87 @@ void Plater::priv::arrange()
bed, bed,
hint, hint,
false, // create many piles not just one pile false, // create many piles not just one pile
[statusfn](unsigned st) { statusfn(st, arrangestr); }, [this](unsigned st) {
[this] () { return !arranging; }); if (st > 0)
} catch(std::exception& /*e*/) { update_status(count - int(st), arrangestr);
GUI::show_error(this->q, L("Could not arrange model objects! " },
"Some geometries may be invalid.")); [this]() { return was_canceled(); });
} catch (std::exception & /*e*/) {
GUI::show_error(plater().q,
L("Could not arrange model objects! "
"Some geometries may be invalid."));
} }
update_status(count,
was_canceled() ? _(L("Arranging canceled."))
: _(L("Arranging done.")));
// it remains to move the wipe tower: // it remains to move the wipe tower:
view3D->get_canvas3d()->arrange_wipe_tower(wti); view3D->get_canvas3d()->arrange_wipe_tower(wti);
statusfn(0, L("Arranging done."));
statusbar()->set_range(prev_range);
statusbar()->set_cancel_callback(); // remove cancel button
// Do a full refresh of scene tree, including regenerating all the GLVolumes.
//FIXME The update function shall just reload the modified matrices.
update(true);
} }
// This method will find an optimal orientation for the currently selected item void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
// Very similar in nature to the arrange method above... {
void Plater::priv::sla_optimize_rotation() { int obj_idx = plater().get_selected_object_idx();
// TODO: we should decide whether to allow arrange when the search is
// running we should probably disable explicit slicing and background
// processing
if (rotoptimizing) { return; }
rotoptimizing = true;
Slic3r::ScopeGuard rotoptimizing_guard([this]() { rotoptimizing = false; });
int obj_idx = get_selected_object_idx();
if (obj_idx < 0) { return; } if (obj_idx < 0) { return; }
ModelObject * o = model.objects[size_t(obj_idx)]; ModelObject *o = plater().model.objects[size_t(obj_idx)];
background_process.stop();
auto prev_range = statusbar()->get_range();
statusbar()->set_range(100);
auto stfn = [this] (unsigned st, const std::string& msg) {
statusbar()->set_progress(int(st));
statusbar()->set_status_text(msg);
// could be problematic, but we need the cancel button.
wxYieldIfNeeded();
};
statusbar()->set_cancel_callback([this, stfn](){
rotoptimizing = false;
stfn(0, L("Orientation search canceled"));
});
auto r = sla::find_best_rotation( auto r = sla::find_best_rotation(
*o, .005f, *o,
[stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); }, .005f,
[this](){ return !rotoptimizing; } [this](unsigned s) {
); if (s < 100)
update_status(int(s),
_(L("Searching for optimal orientation")));
},
[this]() { return was_canceled(); });
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); const auto *bed_shape_opt =
plater().config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt); assert(bed_shape_opt);
auto& bedpoints = bed_shape_opt->values; auto & bedpoints = bed_shape_opt->values;
Polyline bed; bed.points.reserve(bedpoints.size()); Polyline bed;
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); bed.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
double mindist = 6.0; // FIXME double mindist = 6.0; // FIXME
double offs = mindist / 2.0 - EPSILON;
if (!was_canceled()) {
if(rotoptimizing) // wasn't canceled for(ModelInstance * oi : o->instances) {
for(ModelInstance * oi : o->instances) { oi->set_rotation({r[X], r[Y], r[Z]});
oi->set_rotation({r[X], r[Y], r[Z]});
auto trmatrix = oi->get_transformation().get_matrix();
auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); Polygon trchull = o->convex_hull_2d(trmatrix);
namespace opt = libnest2d::opt; MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
opt::StopCriteria stopcr; double r = rotbb.angle_to_X();
stopcr.relative_score_difference = 0.01;
stopcr.max_iterations = 10000; // The box should be landscape
stopcr.stop_score = 0.0; if(rotbb.width() < rotbb.height()) r += PI / 2;
opt::GeneticOptimizer solver(stopcr);
Polygon pbed(bed); Vec3d rt = oi->get_rotation(); rt(Z) += r;
auto bin = pbed.bounding_box(); oi->set_rotation(rt);
double binw = bin.size()(X) * SCALING_FACTOR - offs; }
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
arr::WipeTowerInfo wti; // useless in SLA context
auto result = solver.optimize_min([&trchull, binw, binh](double rot){ arr::find_new_position(plater().model,
auto chull = trchull; o->instances,
chull.rotate(rot); coord_t(mindist / SCALING_FACTOR),
bed,
auto bb = chull.bounding_box(); wti);
double bbw = bb.size()(X) * SCALING_FACTOR;
double bbh = bb.size()(Y) * SCALING_FACTOR; // Correct the z offset of the object which was corrupted be
// the rotation
auto wdiff = bbw - binw; o->ensure_on_bed();
auto hdiff = bbh - binh;
double diff = 0;
if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
if(wdiff > 0) diff += wdiff;
if(hdiff > 0) diff += hdiff;
return diff;
}, opt::initvals(0.0), opt::bound(-PI/2, PI/2));
double r = std::get<0>(result.optimum);
Vec3d rt = oi->get_rotation(); rt(Z) += r;
oi->set_rotation(rt);
} }
arr::WipeTowerInfo wti; // useless in SLA context update_status(100,
arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti); was_canceled() ? _(L("Orientation search canceled."))
: _(L("Orientation found.")));
// Correct the z offset of the object which was corrupted be the rotation
o->ensure_on_bed();
stfn(0, L("Orientation found."));
statusbar()->set_range(prev_range);
statusbar()->set_cancel_callback();
update(true);
} }
void Plater::priv::split_object() void Plater::priv::split_object()
@ -2514,7 +2699,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
bool Plater::priv::restart_background_process(unsigned int state) bool Plater::priv::restart_background_process(unsigned int state)
{ {
if (arranging || rotoptimizing) { if (m_ui_jobs.is_any_running()) {
// Avoid a race condition // Avoid a race condition
return false; return false;
} }
@ -2745,7 +2930,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
{ {
if (evt.status.percent >= -1) { if (evt.status.percent >= -1) {
if (arranging || rotoptimizing) { if (m_ui_jobs.is_any_running()) {
// Avoid a race condition // Avoid a race condition
return; return;
} }
@ -3226,7 +3411,7 @@ bool Plater::priv::can_fix_through_netfabb() const
bool Plater::priv::can_increase_instances() const bool Plater::priv::can_increase_instances() const
{ {
if (arranging || rotoptimizing) { if (m_ui_jobs.is_any_running()) {
return false; return false;
} }
@ -3236,7 +3421,7 @@ bool Plater::priv::can_increase_instances() const
bool Plater::priv::can_decrease_instances() const bool Plater::priv::can_decrease_instances() const
{ {
if (arranging || rotoptimizing) { if (m_ui_jobs.is_any_running()) {
return false; return false;
} }
@ -3256,7 +3441,7 @@ bool Plater::priv::can_split_to_volumes() const
bool Plater::priv::can_arrange() const bool Plater::priv::can_arrange() const
{ {
return !model.objects.empty() && !arranging; return !model.objects.empty() && !m_ui_jobs.is_any_running();
} }
bool Plater::priv::can_layers_editing() const bool Plater::priv::can_layers_editing() const
@ -3323,6 +3508,7 @@ SLAPrint& Plater::sla_print() { return p->sla_print; }
void Plater::new_project() void Plater::new_project()
{ {
p->select_view_3D("3D");
wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
} }
@ -3383,6 +3569,8 @@ void Plater::load_files(const std::vector<std::string>& input_files, bool load_m
void Plater::update() { p->update(); } void Plater::update() { p->update(); }
void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); }
void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } void Plater::update_ui_from_settings() { p->update_ui_from_settings(); }
void Plater::select_view(const std::string& direction) { p->select_view(direction); } void Plater::select_view(const std::string& direction) { p->select_view(direction); }
@ -3583,7 +3771,7 @@ void Plater::export_stl(bool extended, bool selection_only)
else else
{ {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
mesh = model_object->volumes[volume->volume_idx()]->mesh; mesh = model_object->volumes[volume->volume_idx()]->mesh();
mesh.transform(volume->get_volume_transformation().get_matrix()); mesh.transform(volume->get_volume_transformation().get_matrix());
mesh.translate(-model_object->origin_translation.cast<float>()); mesh.translate(-model_object->origin_translation.cast<float>());
} }
@ -3689,7 +3877,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
if (!path.Lower().EndsWith(".3mf")) if (!path.Lower().EndsWith(".3mf"))
return; return;
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
const std::string path_u8 = into_u8(path); const std::string path_u8 = into_u8(path);
wxBusyCursor wait; wxBusyCursor wait;
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
@ -3705,6 +3893,9 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
void Plater::reslice() void Plater::reslice()
{ {
// Stop arrange and (or) optimize rotation tasks.
this->stop_jobs();
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running. //FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
// bitmask of UpdateBackgroundProcessReturnState // bitmask of UpdateBackgroundProcessReturnState
unsigned int state = this->p->update_background_process(true); unsigned int state = this->p->update_background_process(true);
@ -3740,7 +3931,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object)
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
this->p->view3D->reload_scene(false); this->p->view3D->reload_scene(false);
if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID))
// Nothing to do on empty input or invalid configuration. // Nothing to do on empty input or invalid configuration.
return; return;

View file

@ -144,6 +144,7 @@ public:
void load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true); void load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true);
void update(); void update();
void stop_jobs();
void select_view(const std::string& direction); void select_view(const std::string& direction);
void select_view_3D(const std::string& name); void select_view_3D(const std::string& name);

View file

@ -509,6 +509,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
"printer_technology", "printer_technology",
"bed_shape", "max_print_height", "bed_shape", "max_print_height",
"display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_width", "display_height", "display_pixels_x", "display_pixels_y",
"display_mirror_x", "display_mirror_y",
"display_orientation", "display_orientation",
"fast_tilt_time", "slow_tilt_time", "area_fill", "fast_tilt_time", "slow_tilt_time", "area_fill",
"relative_correction", "relative_correction",

View file

@ -781,7 +781,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
if (i == 0) if (i == 0)
suffix[0] = 0; suffix[0] = 0;
else else
sprintf(suffix, "%d", i); sprintf(suffix, "%d", (int)i);
std::string new_name = name + suffix; std::string new_name = name + suffix;
loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name),
new_name, std::move(cfg), i == 0); new_name, std::move(cfg), i == 0);
@ -837,7 +837,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
return preset_name_dst; return preset_name_dst;
// Try to generate another name. // Try to generate another name.
char buf[64]; char buf[64];
sprintf(buf, " (%d)", i); sprintf(buf, " (%d)", (int)i);
preset_name_dst = preset_name_src + buf + bundle_name; preset_name_dst = preset_name_src + buf + bundle_name;
} }
} }
@ -1379,7 +1379,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
for (size_t i = 0; i < this->filament_presets.size(); ++ i) { for (size_t i = 0; i < this->filament_presets.size(); ++ i) {
char suffix[64]; char suffix[64];
if (i > 0) if (i > 0)
sprintf(suffix, "_%d", i); sprintf(suffix, "_%d", (int)i);
else else
suffix[0] = 0; suffix[0] = 0;
c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl;

View file

@ -168,6 +168,11 @@ void ProgressStatusBar::set_status_text(const char *txt)
this->set_status_text(wxString::FromUTF8(txt)); this->set_status_text(wxString::FromUTF8(txt));
} }
wxString ProgressStatusBar::get_status_text() const
{
return self->GetStatusText();
}
void ProgressStatusBar::show_cancel_button() void ProgressStatusBar::show_cancel_button()
{ {
if(m_cancelbutton) m_cancelbutton->Show(); if(m_cancelbutton) m_cancelbutton->Show();

View file

@ -52,6 +52,7 @@ public:
void set_status_text(const wxString& txt); void set_status_text(const wxString& txt);
void set_status_text(const std::string& txt); void set_status_text(const std::string& txt);
void set_status_text(const char *txt); void set_status_text(const char *txt);
wxString get_status_text() const;
// Temporary methods to satisfy Perl side // Temporary methods to satisfy Perl side
void show_cancel_button(); void show_cancel_button();

View file

@ -333,6 +333,8 @@ private:
void render_sidebar_rotation_hint(Axis axis) const; void render_sidebar_rotation_hint(Axis axis) const;
void render_sidebar_scale_hint(Axis axis) const; void render_sidebar_scale_hint(Axis axis) const;
void render_sidebar_size_hint(Axis axis, double length) const; void render_sidebar_size_hint(Axis axis, double length) const;
public:
enum SyncRotationType { enum SyncRotationType {
// Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis.
SYNC_ROTATION_NONE = 0, SYNC_ROTATION_NONE = 0,
@ -343,6 +345,8 @@ private:
}; };
void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_instances(SyncRotationType sync_rotation_type);
void synchronize_unselected_volumes(); void synchronize_unselected_volumes();
private:
void ensure_on_bed(); void ensure_on_bed();
bool is_from_fully_selected_instance(unsigned int volume_idx) const; bool is_from_fully_selected_instance(unsigned int volume_idx) const;

View file

@ -2087,6 +2087,10 @@ void TabPrinter::build_sla()
line.append_option(optgroup->get_option("display_pixels_y")); line.append_option(optgroup->get_option("display_pixels_y"));
optgroup->append_line(line); optgroup->append_line(line);
optgroup->append_single_option_line("display_orientation"); optgroup->append_single_option_line("display_orientation");
// FIXME: This should be on one line in the UI
optgroup->append_single_option_line("display_mirror_x");
optgroup->append_single_option_line("display_mirror_y");
optgroup = page->new_optgroup(_(L("Tilt"))); optgroup = page->new_optgroup(_(L("Tilt")));
line = { _(L("Tilt time")), "" }; line = { _(L("Tilt time")), "" };

View file

@ -389,10 +389,10 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); throw std::runtime_error(L("Repaired 3MF file does not contain any volume"));
if (model.objects.front()->volumes.size() > 1) if (model.objects.front()->volumes.size() > 1)
throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); throw std::runtime_error(L("Repaired 3MF file contains more than one volume"));
meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh)); meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh()));
} }
for (size_t i = 0; i < volumes.size(); ++ i) { for (size_t i = 0; i < volumes.size(); ++ i) {
volumes[i]->mesh = std::move(meshes_repaired[i]); volumes[i]->set_mesh(std::move(meshes_repaired[i]));
volumes[i]->set_new_unique_id(); volumes[i]->set_new_unique_id();
} }
model_object.invalidate_bounding_box(); model_object.invalidate_bounding_box();

View file

@ -253,7 +253,7 @@ ModelMaterial::attributes()
Ref<DynamicPrintConfig> config() Ref<DynamicPrintConfig> config()
%code%{ RETVAL = &THIS->config; %}; %code%{ RETVAL = &THIS->config; %};
Ref<TriangleMesh> mesh() Ref<TriangleMesh> mesh()
%code%{ RETVAL = &THIS->mesh; %}; %code%{ RETVAL = &THIS->mesh(); %};
bool modifier() bool modifier()
%code%{ RETVAL = THIS->is_modifier(); %}; %code%{ RETVAL = THIS->is_modifier(); %};

View file

@ -46,7 +46,6 @@ TriangleMesh::ReadFromPerl(vertices, facets)
SV* facets SV* facets
CODE: CODE:
stl_file &stl = THIS->stl; stl_file &stl = THIS->stl;
stl.error = 0;
stl.stats.type = inmemory; stl.stats.type = inmemory;
// count facets and allocate memory // count facets and allocate memory
@ -99,20 +98,18 @@ SV*
TriangleMesh::vertices() TriangleMesh::vertices()
CODE: CODE:
if (!THIS->repaired) CONFESS("vertices() requires repair()"); if (!THIS->repaired) CONFESS("vertices() requires repair()");
THIS->require_shared_vertices();
if (THIS->stl.v_shared == NULL)
stl_generate_shared_vertices(&(THIS->stl));
// vertices // vertices
AV* vertices = newAV(); AV* vertices = newAV();
av_extend(vertices, THIS->stl.stats.shared_vertices); av_extend(vertices, THIS->its.vertices.size());
for (int i = 0; i < THIS->stl.stats.shared_vertices; i++) { for (size_t i = 0; i < THIS->its.vertices.size(); i++) {
AV* vertex = newAV(); AV* vertex = newAV();
av_store(vertices, i, newRV_noinc((SV*)vertex)); av_store(vertices, i, newRV_noinc((SV*)vertex));
av_extend(vertex, 2); av_extend(vertex, 2);
av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i](0))); av_store(vertex, 0, newSVnv(THIS->its.vertices[i](0)));
av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i](1))); av_store(vertex, 1, newSVnv(THIS->its.vertices[i](1)));
av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i](2))); av_store(vertex, 2, newSVnv(THIS->its.vertices[i](2)));
} }
RETVAL = newRV_noinc((SV*)vertices); RETVAL = newRV_noinc((SV*)vertices);
@ -123,9 +120,7 @@ SV*
TriangleMesh::facets() TriangleMesh::facets()
CODE: CODE:
if (!THIS->repaired) CONFESS("facets() requires repair()"); if (!THIS->repaired) CONFESS("facets() requires repair()");
THIS->require_shared_vertices();
if (THIS->stl.v_shared == NULL)
stl_generate_shared_vertices(&(THIS->stl));
// facets // facets
AV* facets = newAV(); AV* facets = newAV();
@ -134,9 +129,9 @@ TriangleMesh::facets()
AV* facet = newAV(); AV* facet = newAV();
av_store(facets, i, newRV_noinc((SV*)facet)); av_store(facets, i, newRV_noinc((SV*)facet));
av_extend(facet, 2); av_extend(facet, 2);
av_store(facet, 0, newSVnv(THIS->stl.v_indices[i].vertex[0])); av_store(facet, 0, newSVnv(THIS->its.indices[i][0]));
av_store(facet, 1, newSVnv(THIS->stl.v_indices[i].vertex[1])); av_store(facet, 1, newSVnv(THIS->its.indices[i][1]));
av_store(facet, 2, newSVnv(THIS->stl.v_indices[i].vertex[2])); av_store(facet, 2, newSVnv(THIS->its.indices[i][2]));
} }
RETVAL = newRV_noinc((SV*)facets); RETVAL = newRV_noinc((SV*)facets);