mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-12 09:17:52 -06:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_perspective_camera
This commit is contained in:
commit
28dd52d88b
70 changed files with 3490 additions and 3623 deletions
|
@ -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
|
||||||
|
|
BIN
resources/icons/mirroring_off.png
Normal file
BIN
resources/icons/mirroring_off.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 589 B |
BIN
resources/icons/mirroring_on.png
Normal file
BIN
resources/icons/mirroring_on.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 600 B |
BIN
resources/icons/mirroring_transparent.png
Normal file
BIN
resources/icons/mirroring_transparent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 93 B |
|
@ -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,7 +244,6 @@ 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;
|
||||||
|
|
|
@ -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
|
@ -25,209 +25,50 @@
|
||||||
#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];
|
||||||
neighbor[0] = stl->neighbors_start[facet_num].neighbor[0];
|
stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1];
|
||||||
neighbor[1] = stl->neighbors_start[facet_num].neighbor[1];
|
|
||||||
neighbor[2] = stl->neighbors_start[facet_num].neighbor[2];
|
|
||||||
vnot[0] = stl->neighbors_start[facet_num].which_vertex_not[0];
|
|
||||||
vnot[1] = stl->neighbors_start[facet_num].which_vertex_not[1];
|
|
||||||
vnot[2] = stl->neighbors_start[facet_num].which_vertex_not[2];
|
|
||||||
|
|
||||||
/* reverse the facet */
|
|
||||||
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;
|
stl->facet_start[facet_num].vertex[1] = tmp_vertex;
|
||||||
|
|
||||||
/* fix the vnots of the neighboring facets */
|
// fix the vnots of the neighboring facets
|
||||||
if (neighbor[0] != -1)
|
if (neighbor[0] != -1)
|
||||||
stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] =
|
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;
|
||||||
(stl->neighbors_start[neighbor[0]].
|
|
||||||
which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6;
|
|
||||||
if (neighbor[1] != -1)
|
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] = (stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6;
|
||||||
(stl->neighbors_start[neighbor[1]].
|
|
||||||
which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6;
|
|
||||||
if (neighbor[2] != -1)
|
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] = (stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6;
|
||||||
(stl->neighbors_start[neighbor[2]].
|
|
||||||
which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6;
|
|
||||||
|
|
||||||
/* swap the neighbors of the facet that is being reversed */
|
// swap the neighbors of the facet that is being reversed
|
||||||
stl->neighbors_start[facet_num].neighbor[1] = neighbor[2];
|
stl->neighbors_start[facet_num].neighbor[1] = neighbor[2];
|
||||||
stl->neighbors_start[facet_num].neighbor[2] = neighbor[1];
|
stl->neighbors_start[facet_num].neighbor[2] = neighbor[1];
|
||||||
|
|
||||||
/* swap the vnots of the facet that is being reversed */
|
// 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[1] = vnot[2];
|
||||||
stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1];
|
stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1];
|
||||||
|
|
||||||
/* reverse the values of the vnots 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].which_vertex_not[0] =
|
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[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[1] =
|
stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6;
|
||||||
(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;
|
|
||||||
int reversed_count = 0;
|
|
||||||
int id;
|
|
||||||
int force_exit = 0;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
// this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
|
|
||||||
if (stl->stats.number_of_facets == 0) return;
|
|
||||||
|
|
||||||
/* Initialize linked list. */
|
|
||||||
head = (struct stl_normal*)malloc(sizeof(struct stl_normal));
|
|
||||||
if(head == NULL) perror("stl_fix_normal_directions");
|
|
||||||
tail = (struct stl_normal*)malloc(sizeof(struct stl_normal));
|
|
||||||
if(tail == NULL) perror("stl_fix_normal_directions");
|
|
||||||
head->next = tail;
|
|
||||||
tail->next = tail;
|
|
||||||
|
|
||||||
/* Initialize list that keeps track of already fixed facets. */
|
|
||||||
norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char));
|
|
||||||
if(norm_sw == NULL) perror("stl_fix_normal_directions");
|
|
||||||
|
|
||||||
/* Initialize list that keeps track of reversed facets. */
|
|
||||||
reversed_ids = (int*)calloc(stl->stats.number_of_facets, sizeof(int));
|
|
||||||
if (reversed_ids == NULL) perror("stl_fix_normal_directions reversed_ids");
|
|
||||||
|
|
||||||
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) {
|
|
||||||
/* Returns 0 if the normal is within tolerance */
|
|
||||||
/* Returns 1 if the normal is not within tolerance, but direction is OK */
|
|
||||||
/* Returns 2 if the normal is not within tolerance and backwards */
|
|
||||||
/* Returns 4 if the status is unknown. */
|
|
||||||
|
|
||||||
stl_facet *facet;
|
|
||||||
|
|
||||||
facet = &stl->facet_start[facet_num];
|
|
||||||
|
|
||||||
stl_normal normal;
|
stl_normal normal;
|
||||||
stl_calculate_normal(normal, facet);
|
stl_calculate_normal(normal, facet);
|
||||||
|
@ -236,58 +77,160 @@ static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_
|
||||||
|
|
||||||
const float eps = 0.001f;
|
const float eps = 0.001f;
|
||||||
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
|
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
|
||||||
/* It is not really necessary to change the values here */
|
// Normal is within tolerance. It is not really necessary to change the values here, but just for consistency, I will.
|
||||||
/* but just for consistency, I will. */
|
|
||||||
facet->normal = normal;
|
facet->normal = normal;
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stl_normal test_norm = facet->normal;
|
stl_normal test_norm = facet->normal;
|
||||||
stl_normalize_vector(test_norm);
|
stl_normalize_vector(test_norm);
|
||||||
normal_dif = (normal - test_norm).cwiseAbs();
|
normal_dif = (normal - test_norm).cwiseAbs();
|
||||||
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
|
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) {
|
if (normal_fix_flag) {
|
||||||
facet->normal = normal;
|
facet->normal = normal;
|
||||||
stl->stats.normals_fixed += 1;
|
++ stl->stats.normals_fixed;
|
||||||
}
|
}
|
||||||
return 1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
test_norm *= -1.f;
|
test_norm *= -1.f;
|
||||||
normal_dif = (normal - test_norm).cwiseAbs();
|
normal_dif = (normal - test_norm).cwiseAbs();
|
||||||
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
|
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
|
||||||
// Facet is backwards.
|
// The normal is not within tolerance and backwards.
|
||||||
if (normal_fix_flag) {
|
if (normal_fix_flag) {
|
||||||
facet->normal = normal;
|
facet->normal = normal;
|
||||||
stl->stats.normals_fixed += 1;
|
++ stl->stats.normals_fixed;
|
||||||
}
|
}
|
||||||
return 2;
|
return true;
|
||||||
}
|
}
|
||||||
if (normal_fix_flag) {
|
if (normal_fix_flag) {
|
||||||
facet->normal = normal;
|
facet->normal = normal;
|
||||||
stl->stats.normals_fixed += 1;
|
++ stl->stats.normals_fixed;
|
||||||
}
|
}
|
||||||
return 4;
|
// Status is unknown.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stl_fix_normal_values(stl_file *stl) {
|
void stl_fix_normal_directions(stl_file *stl)
|
||||||
int i;
|
{
|
||||||
|
// This may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
|
||||||
|
if (stl->stats.number_of_facets == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (stl->error) return;
|
struct stl_normal {
|
||||||
|
int facet_num;
|
||||||
|
stl_normal *next;
|
||||||
|
};
|
||||||
|
|
||||||
for(i = 0; i < stl->stats.number_of_facets; i++) {
|
// Initialize linked list.
|
||||||
stl_check_normal_vector(stl, i, 1);
|
boost::object_pool<stl_normal> pool;
|
||||||
|
stl_normal *head = pool.construct();
|
||||||
|
stl_normal *tail = pool.construct();
|
||||||
|
head->next = tail;
|
||||||
|
tail->next = tail;
|
||||||
|
|
||||||
|
// Initialize list that keeps track of already fixed facets.
|
||||||
|
std::vector<char> norm_sw(stl->stats.number_of_facets, 0);
|
||||||
|
// Initialize list that keeps track of reversed facets.
|
||||||
|
std::vector<int> reversed_ids(stl->stats.number_of_facets, 0);
|
||||||
|
|
||||||
|
int facet_num = 0;
|
||||||
|
int reversed_count = 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 (check_normal_vector(stl, 0, 0)) {
|
||||||
|
reverse_facet(stl, 0);
|
||||||
|
reversed_ids[reversed_count ++] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Say that we've fixed this facet:
|
||||||
|
norm_sw[facet_num] = 1;
|
||||||
|
int checked = 1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// Add neighbors_to_list. Add unconnected neighbors to the list.
|
||||||
|
bool force_exit = false;
|
||||||
|
for (int 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 (int id = reversed_count - 1; id >= 0; -- id)
|
||||||
|
reverse_facet(stl, reversed_ids[id]);
|
||||||
|
force_exit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
|
||||||
|
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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
stl_normal normal;
|
stl_normal normal;
|
||||||
for(int i = 0; i < stl->stats.number_of_facets; i++) {
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||||
stl_reverse_facet(stl, i);
|
reverse_facet(stl, i);
|
||||||
stl_calculate_normal(normal, &stl->facet_start[i]);
|
stl_calculate_normal(normal, &stl->facet_start[i]);
|
||||||
stl_normalize_vector(normal);
|
stl_normalize_vector(normal);
|
||||||
stl->facet_start[i].normal = normal;
|
stl->facet_start[i].normal = normal;
|
||||||
|
|
|
@ -23,92 +23,60 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) {
|
||||||
stl_generate_shared_vertices(stl_file *stl) {
|
for (int j = 0; j < 3; ++ j) {
|
||||||
int i;
|
if (its.indices[facet_idx][j] != -1)
|
||||||
int j;
|
// Shared vertex was already assigned.
|
||||||
int first_facet;
|
|
||||||
int direction;
|
|
||||||
int facet_num;
|
|
||||||
int vnot;
|
|
||||||
int next_edge;
|
|
||||||
int pivot_vertex;
|
|
||||||
int next_facet;
|
|
||||||
int reversed;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
/* make sure this function is idempotent and does not leak memory */
|
|
||||||
stl_invalidate_shared_vertices(stl);
|
|
||||||
|
|
||||||
stl->v_indices = (v_indices_struct*)
|
|
||||||
calloc(stl->stats.number_of_facets, sizeof(v_indices_struct));
|
|
||||||
if(stl->v_indices == NULL) perror("stl_generate_shared_vertices");
|
|
||||||
stl->v_shared = (stl_vertex*)
|
|
||||||
calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex));
|
|
||||||
if(stl->v_shared == NULL) perror("stl_generate_shared_vertices");
|
|
||||||
stl->stats.shared_malloced = stl->stats.number_of_facets / 2;
|
|
||||||
stl->stats.shared_vertices = 0;
|
|
||||||
|
|
||||||
for(i = 0; i < stl->stats.number_of_facets; i++) {
|
|
||||||
stl->v_indices[i].vertex[0] = -1;
|
|
||||||
stl->v_indices[i].vertex[1] = -1;
|
|
||||||
stl->v_indices[i].vertex[2] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for(i = 0; i < stl->stats.number_of_facets; i++) {
|
|
||||||
first_facet = i;
|
|
||||||
for(j = 0; j < 3; j++) {
|
|
||||||
if(stl->v_indices[i].vertex[j] != -1) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
// Create a new shared vertex.
|
||||||
if(stl->stats.shared_vertices == stl->stats.shared_malloced) {
|
its.vertices.emplace_back(stl->facet_start[facet_idx].vertex[j]);
|
||||||
stl->stats.shared_malloced += 1024;
|
// 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.
|
||||||
stl->v_shared = (stl_vertex*)realloc(stl->v_shared,
|
int facet_in_fan_idx = facet_idx;
|
||||||
stl->stats.shared_malloced * sizeof(stl_vertex));
|
bool edge_direction = false;
|
||||||
if(stl->v_shared == NULL) perror("stl_generate_shared_vertices");
|
bool traversal_reversed = false;
|
||||||
}
|
int vnot = (j + 2) % 3;
|
||||||
|
// Increase the
|
||||||
stl->v_shared[stl->stats.shared_vertices] =
|
++ fan_traversal_stamp;
|
||||||
stl->facet_start[i].vertex[j];
|
|
||||||
|
|
||||||
direction = 0;
|
|
||||||
reversed = 0;
|
|
||||||
facet_num = i;
|
|
||||||
vnot = (j + 2) % 3;
|
|
||||||
|
|
||||||
for (;;) {
|
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) {
|
if (vnot > 2) {
|
||||||
if(direction == 0) {
|
// 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;
|
pivot_vertex = (vnot + 2) % 3;
|
||||||
next_edge = pivot_vertex;
|
next_edge = pivot_vertex;
|
||||||
direction = 1;
|
|
||||||
} else {
|
} else {
|
||||||
pivot_vertex = (vnot + 1) % 3;
|
pivot_vertex = (vnot + 1) % 3;
|
||||||
next_edge = vnot % 3;
|
next_edge = vnot % 3;
|
||||||
direction = 0;
|
|
||||||
}
|
}
|
||||||
|
edge_direction = ! edge_direction;
|
||||||
} else {
|
} else {
|
||||||
if(direction == 0) {
|
// The neighboring facet is correctly oriented.
|
||||||
|
if (! edge_direction) {
|
||||||
pivot_vertex = (vnot + 1) % 3;
|
pivot_vertex = (vnot + 1) % 3;
|
||||||
next_edge = vnot;
|
next_edge = vnot;
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,87 +84,73 @@ stl_generate_shared_vertices(stl_file *stl) {
|
||||||
next_edge = pivot_vertex;
|
next_edge = pivot_vertex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stl->v_indices[facet_num].vertex[pivot_vertex] =
|
its.indices[facet_in_fan_idx][pivot_vertex] = its.vertices.size() - 1;
|
||||||
stl->stats.shared_vertices;
|
fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp;
|
||||||
|
|
||||||
next_facet = stl->neighbors_start[facet_num].neighbor[next_edge];
|
// 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) {
|
if (next_facet == -1) {
|
||||||
if(reversed) {
|
// 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;
|
break;
|
||||||
} else {
|
} else {
|
||||||
direction = 1;
|
// Reached the first limit. Now try to reverse and traverse up to the other limit.
|
||||||
|
edge_direction = true;
|
||||||
vnot = (j + 1) % 3;
|
vnot = (j + 1) % 3;
|
||||||
reversed = 1;
|
traversal_reversed = true;
|
||||||
facet_num = first_facet;
|
facet_in_fan_idx = facet_idx;
|
||||||
}
|
}
|
||||||
} else if(next_facet != first_facet) {
|
} else if (next_facet == facet_idx) {
|
||||||
vnot = stl->neighbors_start[facet_num].
|
// Traversed a closed fan all around.
|
||||||
which_vertex_not[next_edge];
|
// assert(! traversal_reversed);
|
||||||
facet_num = next_facet;
|
|
||||||
} else {
|
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stl->stats.shared_vertices += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool its_write_off(const indexed_triangle_set &its, const char *file)
|
||||||
stl_write_off(stl_file *stl, const char *file) {
|
{
|
||||||
int i;
|
|
||||||
FILE *fp;
|
|
||||||
char *error_msg;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
/* Open the file */
|
/* Open the file */
|
||||||
fp = boost::nowide::fopen(file, "w");
|
FILE *fp = boost::nowide::fopen(file, "w");
|
||||||
if(fp == NULL) {
|
if (fp == nullptr) {
|
||||||
error_msg = (char*)
|
BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing";
|
||||||
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
|
return false;
|
||||||
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, "OFF\n");
|
||||||
fprintf(fp, "%d %d 0\n",
|
fprintf(fp, "%d %d 0\n", (int)its.vertices.size(), (int)its.indices.size());
|
||||||
stl->stats.shared_vertices, stl->stats.number_of_facets);
|
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(i = 0; i < stl->stats.shared_vertices; i++) {
|
for (uint32_t i = 0; i < its.indices.size(); ++ i)
|
||||||
fprintf(fp, "\t%f %f %f\n",
|
fprintf(fp, "\t3 %d %d %d\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
|
||||||
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);
|
fclose(fp);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool its_write_vrml(const indexed_triangle_set &its, const char *file)
|
||||||
stl_write_vrml(stl_file *stl, const char *file) {
|
{
|
||||||
int i;
|
|
||||||
FILE *fp;
|
|
||||||
char *error_msg;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
/* Open the file */
|
/* Open the file */
|
||||||
fp = boost::nowide::fopen(file, "w");
|
FILE *fp = boost::nowide::fopen(file, "w");
|
||||||
if(fp == NULL) {
|
if (fp == nullptr) {
|
||||||
error_msg = (char*)
|
BOOST_LOG_TRIVIAL(error) << "stl_write_vrml: Couldn't open " << file << " for writing";
|
||||||
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
|
return false;
|
||||||
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, "#VRML V1.0 ascii\n\n");
|
||||||
|
@ -214,51 +168,92 @@ stl_write_vrml(stl_file *stl, const char *file) {
|
||||||
fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n");
|
fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n");
|
||||||
fprintf(fp, "\t\t\tpoint [\n");
|
fprintf(fp, "\t\t\tpoint [\n");
|
||||||
|
|
||||||
for(i = 0; i < (stl->stats.shared_vertices - 1); i++) {
|
int i = 0;
|
||||||
fprintf(fp, "\t\t\t\t%f %f %f,\n",
|
for (; i + 1 < its.vertices.size(); ++ i)
|
||||||
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", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2));
|
||||||
}
|
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\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\t}\n");
|
||||||
fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n");
|
fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n");
|
||||||
fprintf(fp, "\t\t\tcoordIndex [\n");
|
fprintf(fp, "\t\t\tcoordIndex [\n");
|
||||||
|
|
||||||
for(i = 0; i < (stl->stats.number_of_facets - 1); i++) {
|
for (size_t i = 0; i + 1 < its.indices.size(); ++ i)
|
||||||
fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0],
|
fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
|
||||||
stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[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\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\t}\n");
|
||||||
fprintf(fp, "\t}\n");
|
fprintf(fp, "\t}\n");
|
||||||
fprintf(fp, "}\n");
|
fprintf(fp, "}\n");
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stl_write_obj (stl_file *stl, const char *file) {
|
bool its_write_obj(const indexed_triangle_set &its, const char *file)
|
||||||
int i;
|
{
|
||||||
FILE* fp;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
FILE *fp = boost::nowide::fopen(file, "w");
|
||||||
|
if (fp == nullptr) {
|
||||||
/* Open the file */
|
BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing";
|
||||||
fp = boost::nowide::fopen(file, "w");
|
return false;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
fclose(fp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check validity of the mesh, assert on error.
|
||||||
|
bool stl_validate(const stl_file *stl, const indexed_triangle_set &its)
|
||||||
|
{
|
||||||
|
assert(! stl->facet_start.empty());
|
||||||
|
assert(stl->facet_start.size() == stl->stats.number_of_facets);
|
||||||
|
assert(stl->neighbors_start.size() == stl->stats.number_of_facets);
|
||||||
|
assert(stl->facet_start.size() == stl->neighbors_start.size());
|
||||||
|
assert(! stl->neighbors_start.empty());
|
||||||
|
assert((its.indices.empty()) == (its.vertices.empty()));
|
||||||
|
assert(stl->stats.number_of_facets > 0);
|
||||||
|
assert(its.vertices.empty() || its.indices.size() == stl->stats.number_of_facets);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
// Verify validity of neighborship data.
|
||||||
|
for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) {
|
||||||
|
const stl_neighbors &nbr = stl->neighbors_start[facet_idx];
|
||||||
|
const int *vertices = its.indices.empty() ? nullptr : its.indices[facet_idx].data();
|
||||||
|
for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) {
|
||||||
|
int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx];
|
||||||
|
assert(nbr_face < (int)stl->stats.number_of_facets);
|
||||||
|
if (nbr_face != -1) {
|
||||||
|
int nbr_vnot = nbr.which_vertex_not[nbr_idx];
|
||||||
|
assert(nbr_vnot >= 0 && nbr_vnot < 6);
|
||||||
|
// Neighbor of the neighbor is the original face.
|
||||||
|
assert(stl->neighbors_start[nbr_face].neighbor[(nbr_vnot + 1) % 3] == facet_idx);
|
||||||
|
int vnot_back = stl->neighbors_start[nbr_face].which_vertex_not[(nbr_vnot + 1) % 3];
|
||||||
|
assert(vnot_back >= 0 && vnot_back < 6);
|
||||||
|
assert((nbr_vnot < 3) == (vnot_back < 3));
|
||||||
|
assert(vnot_back % 3 == (nbr_idx + 2) % 3);
|
||||||
|
if (vertices != nullptr) {
|
||||||
|
// Has shared vertices.
|
||||||
|
if (nbr_vnot < 3) {
|
||||||
|
// Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented.
|
||||||
|
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]));
|
||||||
|
} else {
|
||||||
|
// Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped.
|
||||||
|
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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* _DEBUG */
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validity of the mesh, assert on error.
|
||||||
|
bool stl_validate(const stl_file *stl)
|
||||||
|
{
|
||||||
|
indexed_triangle_set its;
|
||||||
|
return stl_validate(stl, its);
|
||||||
}
|
}
|
||||||
|
|
175
src/admesh/stl.h
175
src/admesh/stl.h
|
@ -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,6 +41,7 @@
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ struct stl_facet {
|
||||||
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];
|
||||||
|
@ -67,39 +69,28 @@ 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 {
|
|
||||||
// Key of a hash edge: sorted vertices of the edge.
|
|
||||||
uint32_t key[6];
|
|
||||||
// Compare two keys.
|
|
||||||
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 {
|
|
||||||
// Index of a neighbor facet.
|
// Index of a neighbor facet.
|
||||||
int neighbor[3];
|
int neighbor[3];
|
||||||
// Index of an opposite vertex at the neighbor face.
|
// Index of an opposite vertex at the neighbor face.
|
||||||
char which_vertex_not[3];
|
char which_vertex_not[3];
|
||||||
} stl_neighbors;
|
};
|
||||||
|
|
||||||
typedef struct {
|
struct stl_stats {
|
||||||
int vertex[3];
|
stl_stats() { this->reset(); }
|
||||||
} v_indices_struct;
|
void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; }
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char header[81];
|
char header[81];
|
||||||
stl_type type;
|
stl_type type;
|
||||||
uint32_t number_of_facets;
|
uint32_t number_of_facets;
|
||||||
|
@ -109,7 +100,6 @@ typedef struct {
|
||||||
float bounding_diameter;
|
float bounding_diameter;
|
||||||
float shortest_edge;
|
float shortest_edge;
|
||||||
float volume;
|
float volume;
|
||||||
unsigned number_of_blocks;
|
|
||||||
int connected_edges;
|
int connected_edges;
|
||||||
int connected_facets_1_edge;
|
int connected_facets_1_edge;
|
||||||
int connected_facets_2_edge;
|
int connected_facets_2_edge;
|
||||||
|
@ -126,45 +116,47 @@ typedef struct {
|
||||||
int backwards_edges;
|
int backwards_edges;
|
||||||
int normals_fixed;
|
int normals_fixed;
|
||||||
int number_of_parts;
|
int number_of_parts;
|
||||||
int malloced;
|
};
|
||||||
int freed;
|
|
||||||
int facets_malloced;
|
|
||||||
int collisions;
|
|
||||||
int shared_vertices;
|
|
||||||
int shared_malloced;
|
|
||||||
} stl_stats;
|
|
||||||
|
|
||||||
typedef struct {
|
struct stl_file {
|
||||||
FILE *fp;
|
stl_file() {}
|
||||||
stl_facet *facet_start;
|
|
||||||
stl_hash_edge **heads;
|
void clear() {
|
||||||
stl_hash_edge *tail;
|
this->facet_start.clear();
|
||||||
int M;
|
this->neighbors_start.clear();
|
||||||
stl_neighbors *neighbors_start;
|
this->stats.reset();
|
||||||
v_indices_struct *v_indices;
|
}
|
||||||
stl_vertex *v_shared;
|
|
||||||
|
std::vector<stl_facet> facet_start;
|
||||||
|
std::vector<stl_neighbors> neighbors_start;
|
||||||
|
// Statistics
|
||||||
stl_stats stats;
|
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,9 +178,6 @@ 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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) {
|
for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) {
|
||||||
stl_facet &face = stl->facet_start[i_face];
|
stl_facet &face = stl->facet_start[i_face];
|
||||||
for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) {
|
for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) {
|
||||||
|
@ -211,9 +200,6 @@ extern void stl_transform(stl_file *stl, T *trafo3x4)
|
||||||
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];
|
||||||
|
@ -228,9 +214,6 @@ 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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -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
|
||||||
|
|
|
@ -22,158 +22,85 @@
|
||||||
|
|
||||||
#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, "\
|
|
||||||
File type : ASCII STL file\n");
|
|
||||||
}
|
|
||||||
fprintf(file, "\
|
|
||||||
Header : %s\n", stl->stats.header);
|
|
||||||
fprintf(file, "============== Size ==============\n");
|
fprintf(file, "============== Size ==============\n");
|
||||||
fprintf(file, "Min X = % f, Max X = % f\n",
|
fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.min(0), stl->stats.max(0));
|
||||||
stl->stats.min(0), stl->stats.max(0));
|
fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.min(1), stl->stats.max(1));
|
||||||
fprintf(file, "Min Y = % f, Max Y = % f\n",
|
fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.min(2), stl->stats.max(2));
|
||||||
stl->stats.min(1), stl->stats.max(1));
|
fprintf(file, "========= Facet Status ========== Original ============ Final ====\n");
|
||||||
fprintf(file, "Min Z = % f, Max Z = % f\n",
|
fprintf(file, "Number of facets : %5d %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets);
|
||||||
stl->stats.min(2), stl->stats.max(2));
|
fprintf(file, "Facets with 1 disconnected edge : %5d %5d\n",
|
||||||
|
stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge);
|
||||||
fprintf(file, "\
|
fprintf(file, "Facets with 2 disconnected edges : %5d %5d\n",
|
||||||
========= Facet Status ========== Original ============ Final ====\n");
|
stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge);
|
||||||
fprintf(file, "\
|
fprintf(file, "Facets with 3 disconnected edges : %5d %5d\n",
|
||||||
Number of facets : %5d %5d\n",
|
stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge);
|
||||||
stl->stats.original_num_facets, stl->stats.number_of_facets);
|
fprintf(file, "Total disconnected facets : %5d %5d\n",
|
||||||
fprintf(file, "\
|
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);
|
||||||
Facets with 1 disconnected edge : %5d %5d\n",
|
fprintf(file, "=== Processing Statistics === ===== Other Statistics =====\n");
|
||||||
stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge -
|
fprintf(file, "Number of parts : %5d Volume : %f\n", stl->stats.number_of_parts, stl->stats.volume);
|
||||||
stl->stats.connected_facets_3_edge);
|
fprintf(file, "Degenerate facets : %5d\n", stl->stats.degenerate_facets);
|
||||||
fprintf(file, "\
|
fprintf(file, "Edges fixed : %5d\n", stl->stats.edges_fixed);
|
||||||
Facets with 2 disconnected edges : %5d %5d\n",
|
fprintf(file, "Facets removed : %5d\n", stl->stats.facets_removed);
|
||||||
stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge -
|
fprintf(file, "Facets added : %5d\n", stl->stats.facets_added);
|
||||||
stl->stats.connected_facets_2_edge);
|
fprintf(file, "Facets reversed : %5d\n", stl->stats.facets_reversed);
|
||||||
fprintf(file, "\
|
fprintf(file, "Backwards edges : %5d\n", stl->stats.backwards_edges);
|
||||||
Facets with 3 disconnected edges : %5d %5d\n",
|
fprintf(file, "Normals fixed : %5d\n", stl->stats.normals_fixed);
|
||||||
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;
|
|
||||||
char *error_msg;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
/* Open the file */
|
|
||||||
FILE *fp = boost::nowide::fopen(file, "w");
|
FILE *fp = boost::nowide::fopen(file, "w");
|
||||||
if(fp == NULL) {
|
if (fp == nullptr) {
|
||||||
error_msg = (char*)
|
BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing";
|
||||||
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
|
return false;
|
||||||
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, "solid %s\n", label);
|
fprintf(fp, "solid %s\n", label);
|
||||||
|
|
||||||
for(i = 0; i < stl->stats.number_of_facets; i++) {
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||||
fprintf(fp, " facet normal % .8E % .8E % .8E\n",
|
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));
|
||||||
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, " outer loop\n");
|
||||||
fprintf(fp, " vertex % .8E % .8E % .8E\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));
|
||||||
stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1),
|
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));
|
||||||
stl->facet_start[i].vertex[0](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, " 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, " endloop\n");
|
||||||
fprintf(fp, " endfacet\n");
|
fprintf(fp, " endfacet\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(fp, "endsolid %s\n", label);
|
fprintf(fp, "endsolid %s\n", label);
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* 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++) {
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||||
fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n",
|
fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n",
|
||||||
i,
|
i,
|
||||||
stl->neighbors_start[i].neighbor[0],
|
stl->neighbors_start[i].neighbor[0],
|
||||||
|
@ -184,9 +111,10 @@ stl_print_neighbors(stl_file *stl, char *file) {
|
||||||
(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)
|
||||||
{
|
{
|
||||||
|
@ -197,36 +125,27 @@ void stl_internal_reverse_quads(char *buf, size_t cnt)
|
||||||
}
|
}
|
||||||
#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;
|
|
||||||
|
|
||||||
/* Open the file */
|
|
||||||
fp = boost::nowide::fopen(file, "wb");
|
|
||||||
if(fp == NULL) {
|
|
||||||
error_msg = (char*)
|
|
||||||
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
|
|
||||||
sprintf(error_msg, "stl_write_binary: Couldn't open %s for writing",
|
|
||||||
file);
|
|
||||||
perror(error_msg);
|
|
||||||
free(error_msg);
|
|
||||||
stl->error = 1;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(fp, "%s", label);
|
fprintf(fp, "%s", label);
|
||||||
for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp);
|
for (size_t i = strlen(label); i < LABEL_SIZE; ++ i)
|
||||||
|
putc(0, fp);
|
||||||
|
|
||||||
|
#if !defined(SEEK_SET)
|
||||||
|
#define SEEK_SET 0
|
||||||
|
#endif
|
||||||
fseek(fp, LABEL_SIZE, SEEK_SET);
|
fseek(fp, LABEL_SIZE, SEEK_SET);
|
||||||
#ifdef BOOST_LITTLE_ENDIAN
|
#if BOOST_ENDIAN_LITTLE_BYTE
|
||||||
fwrite(&stl->stats.number_of_facets, 4, 1, fp);
|
fwrite(&stl->stats.number_of_facets, 4, 1, fp);
|
||||||
for (i = 0; i < stl->stats.number_of_facets; ++ i)
|
for (const stl_facet &facet : stl->facet_start)
|
||||||
fwrite(stl->facet_start + i, SIZEOF_STL_FACET, 1, fp);
|
fwrite(&facet, SIZEOF_STL_FACET, 1, fp);
|
||||||
#else /* BOOST_LITTLE_ENDIAN */
|
#else /* BOOST_ENDIAN_LITTLE_BYTE */
|
||||||
char buffer[50];
|
char buffer[50];
|
||||||
// Convert the number of facets to little endian.
|
// Convert the number of facets to little endian.
|
||||||
memcpy(buffer, &stl->stats.number_of_facets, 4);
|
memcpy(buffer, &stl->stats.number_of_facets, 4);
|
||||||
|
@ -238,44 +157,29 @@ stl_write_binary(stl_file *stl, const char *file, const char *label) {
|
||||||
stl_internal_reverse_quads(buffer, 48);
|
stl_internal_reverse_quads(buffer, 48);
|
||||||
fwrite(buffer, SIZEOF_STL_FACET, 1, fp);
|
fwrite(buffer, SIZEOF_STL_FACET, 1, fp);
|
||||||
}
|
}
|
||||||
#endif /* BOOST_LITTLE_ENDIAN */
|
#endif /* BOOST_ENDIAN_LITTLE_BYTE */
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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("edge (%d)/(%d) %s\n", edge.facet_number, edge.which_edge, label);
|
|
||||||
if(edge.which_edge < 3) {
|
|
||||||
stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3);
|
|
||||||
stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3);
|
|
||||||
} else {
|
|
||||||
stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3);
|
|
||||||
stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
stl_write_neighbor(stl_file *stl, int facet) {
|
|
||||||
if (stl->error) return;
|
|
||||||
printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet,
|
printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet,
|
||||||
stl->neighbors_start[facet].neighbor[0],
|
stl->neighbors_start[facet].neighbor[0],
|
||||||
stl->neighbors_start[facet].neighbor[1],
|
stl->neighbors_start[facet].neighbor[1],
|
||||||
|
@ -285,86 +189,43 @@ stl_write_neighbor(stl_file *stl, int facet) {
|
||||||
stl->neighbors_start[facet].which_vertex_not[2]);
|
stl->neighbors_start[facet].which_vertex_not[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool stl_write_quad_object(stl_file *stl, char *file)
|
||||||
stl_write_quad_object(stl_file *stl, char *file) {
|
{
|
||||||
FILE *fp;
|
|
||||||
int i;
|
|
||||||
int j;
|
|
||||||
char *error_msg;
|
|
||||||
stl_vertex connect_color = stl_vertex::Zero();
|
stl_vertex connect_color = stl_vertex::Zero();
|
||||||
stl_vertex uncon_1_color = stl_vertex::Zero();
|
stl_vertex uncon_1_color = stl_vertex::Zero();
|
||||||
stl_vertex uncon_2_color = stl_vertex::Zero();
|
stl_vertex uncon_2_color = stl_vertex::Zero();
|
||||||
stl_vertex uncon_3_color = stl_vertex::Zero();
|
stl_vertex uncon_3_color = stl_vertex::Zero();
|
||||||
stl_vertex color;
|
stl_vertex color;
|
||||||
|
|
||||||
if (stl->error) return;
|
FILE *fp = boost::nowide::fopen(file, "w");
|
||||||
|
if (fp == nullptr) {
|
||||||
/* Open the file */
|
BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing";
|
||||||
fp = boost::nowide::fopen(file, "w");
|
return false;
|
||||||
if(fp == NULL) {
|
|
||||||
error_msg = (char*)
|
|
||||||
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
|
|
||||||
sprintf(error_msg, "stl_write_quad_object: Couldn't open %s for writing",
|
|
||||||
file);
|
|
||||||
perror(error_msg);
|
|
||||||
free(error_msg);
|
|
||||||
stl->error = 1;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(fp, "CQUAD\n");
|
fprintf(fp, "CQUAD\n");
|
||||||
for(i = 0; i < stl->stats.number_of_facets; i++) {
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||||
j = ((stl->neighbors_start[i].neighbor[0] == -1) +
|
switch (stl->neighbors_start[i].num_neighbors_missing()) {
|
||||||
(stl->neighbors_start[i].neighbor[1] == -1) +
|
case 0: color = connect_color; break;
|
||||||
(stl->neighbors_start[i].neighbor[2] == -1));
|
case 1: color = uncon_1_color; break;
|
||||||
if(j == 0) {
|
case 2: color = uncon_2_color; break;
|
||||||
color = connect_color;
|
default: color = uncon_3_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",
|
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));
|
||||||
stl->facet_start[i].vertex[0](0),
|
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));
|
||||||
stl->facet_start[i].vertex[0](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));
|
||||||
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[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[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;
|
|
||||||
|
|
||||||
/* 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, "999\n%s\n", label);
|
fprintf(fp, "999\n%s\n", label);
|
||||||
|
@ -375,41 +236,15 @@ stl_write_dxf(stl_file *stl, const char *file, char *label) {
|
||||||
|
|
||||||
fprintf(fp, "0\nSECTION\n2\nENTITIES\n");
|
fprintf(fp, "0\nSECTION\n2\nENTITIES\n");
|
||||||
|
|
||||||
for(i = 0; i < stl->stats.number_of_facets; i++) {
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||||
fprintf(fp, "0\n3DFACE\n8\n0\n");
|
fprintf(fp, "0\n3DFACE\n8\n0\n");
|
||||||
fprintf(fp, "10\n%f\n20\n%f\n30\n%f\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));
|
||||||
stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1),
|
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));
|
||||||
stl->facet_start[i].vertex[0](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, "11\n%f\n21\n%f\n31\n%f\n",
|
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));
|
||||||
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");
|
fprintf(fp, "0\nENDSEC\n0\nEOF\n");
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
}
|
return true;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,279 +36,152 @@
|
||||||
#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);
|
||||||
|
|
||||||
void
|
// Check for binary or ASCII file.
|
||||||
stl_initialize(stl_file *stl) {
|
fseek(fp, HEADER_SIZE, SEEK_SET);
|
||||||
memset(stl, 0, sizeof(stl_file));
|
|
||||||
stl->stats.volume = -1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef BOOST_LITTLE_ENDIAN
|
|
||||||
extern void stl_internal_reverse_quads(char *buf, size_t cnt);
|
|
||||||
#endif /* BOOST_LITTLE_ENDIAN */
|
|
||||||
|
|
||||||
void
|
|
||||||
stl_count_facets(stl_file *stl, const char *file) {
|
|
||||||
long file_size;
|
|
||||||
uint32_t header_num_facets;
|
|
||||||
uint32_t num_facets;
|
|
||||||
int i;
|
|
||||||
size_t s;
|
|
||||||
unsigned char chtest[128];
|
unsigned char chtest[128];
|
||||||
int num_lines = 1;
|
if (! fread(chtest, sizeof(chtest), 1, fp)) {
|
||||||
char *error_msg;
|
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The input is an empty file: " << file;
|
||||||
|
fclose(fp);
|
||||||
if (stl->error) return;
|
return nullptr;
|
||||||
|
|
||||||
/* 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;
|
stl->stats.type = ascii;
|
||||||
for(s = 0; s < sizeof(chtest); s++) {
|
for (size_t s = 0; s < sizeof(chtest); s++) {
|
||||||
if (chtest[s] > 127) {
|
if (chtest[s] > 127) {
|
||||||
stl->stats.type = binary;
|
stl->stats.type = binary;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rewind(stl->fp);
|
rewind(fp);
|
||||||
|
|
||||||
/* Get the header and the number of facets in the .STL file */
|
uint32_t num_facets = 0;
|
||||||
/* If the .STL file is binary, then do the following */
|
|
||||||
|
// 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) {
|
if (stl->stats.type == binary) {
|
||||||
/* Test if the STL file has the right size */
|
// Test if the STL file has the right size.
|
||||||
if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0)
|
if (((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) {
|
||||||
|| (file_size < STL_MIN_FILE_SIZE)) {
|
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The file " << file << " has the wrong size.";
|
||||||
fprintf(stderr, "The file %s has the wrong size.\n", file);
|
fclose(fp);
|
||||||
stl->error = 1;
|
return nullptr;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET;
|
num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET;
|
||||||
|
|
||||||
/* Read the header */
|
// Read the header.
|
||||||
if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) {
|
if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79)
|
||||||
stl->stats.header[80] = '\0';
|
stl->stats.header[80] = '\0';
|
||||||
}
|
|
||||||
|
|
||||||
/* Read the int following the header. This should contain # of facets */
|
// 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;
|
uint32_t header_num_facets;
|
||||||
|
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0;
|
||||||
#ifndef BOOST_LITTLE_ENDIAN
|
#ifndef BOOST_LITTLE_ENDIAN
|
||||||
// Convert from little endian to big endian.
|
// Convert from little endian to big endian.
|
||||||
stl_internal_reverse_quads((char*)&header_num_facets, 4);
|
stl_internal_reverse_quads((char*)&header_num_facets, 4);
|
||||||
#endif /* BOOST_LITTLE_ENDIAN */
|
#endif /* BOOST_LITTLE_ENDIAN */
|
||||||
if (! header_num_faces_read || num_facets != header_num_facets) {
|
if (! header_num_faces_read || num_facets != header_num_facets)
|
||||||
fprintf(stderr,
|
BOOST_LOG_TRIVIAL(info) << "stl_open_count_facets: Warning: File size doesn't match number of facets in the header: " << file;
|
||||||
"Warning: File size doesn't match number of facets in the header\n");
|
|
||||||
}
|
}
|
||||||
}
|
// Otherwise, if the .STL file is ASCII, then do the following:
|
||||||
/* Otherwise, if the .STL file is ASCII, then do the following */
|
else
|
||||||
else {
|
{
|
||||||
/* Reopen the file in text mode (for getting correct newlines on Windows) */
|
// Reopen the file in text mode (for getting correct newlines on Windows)
|
||||||
// fix to silence a warning about unused return value.
|
// fix to silence a warning about unused return value.
|
||||||
// obviously if it fails we have problems....
|
// obviously if it fails we have problems....
|
||||||
stl->fp = boost::nowide::freopen(file, "r", stl->fp);
|
fp = boost::nowide::freopen(file, "r", fp);
|
||||||
|
|
||||||
// do another null check to be safe
|
// do another null check to be safe
|
||||||
if(stl->fp == NULL) {
|
if (fp == nullptr) {
|
||||||
error_msg = (char*)
|
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading";
|
||||||
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
|
fclose(fp);
|
||||||
sprintf(error_msg, "stl_initialize: Couldn't open %s for reading",
|
return nullptr;
|
||||||
file);
|
|
||||||
perror(error_msg);
|
|
||||||
free(error_msg);
|
|
||||||
stl->error = 1;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Find the number of facets */
|
// Find the number of facets.
|
||||||
char linebuf[100];
|
char linebuf[100];
|
||||||
while (fgets(linebuf, 100, stl->fp) != NULL) {
|
int num_lines = 1;
|
||||||
/* don't count short lines */
|
while (fgets(linebuf, 100, fp) != nullptr) {
|
||||||
if (strlen(linebuf) <= 4) continue;
|
// Don't count short lines.
|
||||||
|
if (strlen(linebuf) <= 4)
|
||||||
/* skip solid/endsolid lines as broken STL file generators may put several of them */
|
continue;
|
||||||
if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) 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;
|
++ num_lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
rewind(stl->fp);
|
rewind(fp);
|
||||||
|
|
||||||
/* Get the header */
|
// Get the header.
|
||||||
for(i = 0;
|
int i = 0;
|
||||||
(i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++);
|
for (; i < 80 && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ;
|
||||||
stl->stats.header[i] = '\0'; /* Lose the '\n' */
|
stl->stats.header[i] = '\0'; // Lose the '\n'
|
||||||
stl->stats.header[80] = '\0';
|
stl->stats.header[80] = '\0';
|
||||||
|
|
||||||
num_facets = num_lines / ASCII_LINES_PER_FACET;
|
num_facets = num_lines / ASCII_LINES_PER_FACET;
|
||||||
}
|
}
|
||||||
|
|
||||||
stl->stats.number_of_facets += num_facets;
|
stl->stats.number_of_facets += num_facets;
|
||||||
stl->stats.original_num_facets = stl->stats.number_of_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_allocate(stl_file *stl) {
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
/* 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
|
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. */
|
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) {
|
static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first)
|
||||||
stl_facet facet;
|
{
|
||||||
|
if (stl->stats.type == binary)
|
||||||
if (stl->error) return;
|
fseek(fp, HEADER_SIZE, SEEK_SET);
|
||||||
|
else
|
||||||
if(stl->stats.type == binary) {
|
rewind(fp);
|
||||||
fseek(stl->fp, HEADER_SIZE, SEEK_SET);
|
|
||||||
} else {
|
|
||||||
rewind(stl->fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
char normal_buf[3][32];
|
char normal_buf[3][32];
|
||||||
for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) {
|
for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++ i) {
|
||||||
if(stl->stats.type == binary)
|
stl_facet facet;
|
||||||
/* Read a single facet from a binary .STL file */
|
|
||||||
{
|
if (stl->stats.type == binary) {
|
||||||
/* we assume little-endian architecture! */
|
// 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) {
|
if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET)
|
||||||
stl->error = 1;
|
return false;
|
||||||
return;
|
|
||||||
}
|
|
||||||
#ifndef BOOST_LITTLE_ENDIAN
|
#ifndef BOOST_LITTLE_ENDIAN
|
||||||
// Convert the loaded little endian data to big endian.
|
// Convert the loaded little endian data to big endian.
|
||||||
stl_internal_reverse_quads((char*)&facet, 48);
|
stl_internal_reverse_quads((char*)&facet, 48);
|
||||||
#endif /* BOOST_LITTLE_ENDIAN */
|
#endif /* BOOST_LITTLE_ENDIAN */
|
||||||
} else
|
} else {
|
||||||
/* Read a single facet from an ASCII .STL file */
|
// Read a single facet from an ASCII .STL file
|
||||||
{
|
|
||||||
// skip solid/endsolid
|
// skip solid/endsolid
|
||||||
// (in this order, otherwise it won't work when they are paired in the middle of a file)
|
// (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(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")
|
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.
|
// 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]);
|
int res_normal = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
|
||||||
assert(res_normal == 3);
|
assert(res_normal == 3);
|
||||||
int res_outer_loop = fscanf(stl->fp, " outer loop");
|
int res_outer_loop = fscanf(fp, " outer loop");
|
||||||
assert(res_outer_loop == 0);
|
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));
|
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);
|
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));
|
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);
|
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));
|
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);
|
assert(res_vertex3 == 3);
|
||||||
int res_endloop = fscanf(stl->fp, " endloop");
|
int res_endloop = fscanf(fp, " endloop");
|
||||||
assert(res_endloop == 0);
|
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.
|
// 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 ");
|
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) {
|
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!");
|
BOOST_LOG_TRIVIAL(error) << "Something is syntactically very wrong with this ASCII STL! ";
|
||||||
stl->error = 1;
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
|
// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
|
||||||
|
@ -335,19 +209,48 @@ void stl_read(stl_file *stl, int first_facet, bool first) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Write the facet into memory. */
|
// Write the facet into memory.
|
||||||
stl->facet_start[i] = facet;
|
stl->facet_start[i] = facet;
|
||||||
stl_facet_stats(stl, facet, first);
|
stl_facet_stats(stl, facet, first);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
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
|
||||||
|
extern void stl_internal_reverse_quads(char *buf, size_t cnt);
|
||||||
|
#endif /* BOOST_LITTLE_ENDIAN */
|
||||||
|
|
||||||
|
void stl_allocate(stl_file *stl)
|
||||||
|
{
|
||||||
|
// Allocate memory for the entire .STL file.
|
||||||
|
stl->facet_start.assign(stl->stats.number_of_facets, stl_facet());
|
||||||
|
// Allocate memory for the neighbors list.
|
||||||
|
stl->neighbors_start.assign(stl->stats.number_of_facets, stl_neighbors());
|
||||||
|
}
|
||||||
|
|
||||||
|
void stl_reallocate(stl_file *stl)
|
||||||
|
{
|
||||||
|
stl->facet_start.resize(stl->stats.number_of_facets);
|
||||||
|
stl->neighbors_start.resize(stl->stats.number_of_facets);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// While we are going through all of the facets, let's find the
|
// While we are going through all of the facets, let's find the
|
||||||
// maximum and minimum values for x, y, and z
|
// maximum and minimum values for x, y, and z
|
||||||
|
|
||||||
|
@ -366,20 +269,3 @@ void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first)
|
||||||
stl->stats.max = stl->stats.max.cwiseMax(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));
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,35 +25,29 @@
|
||||||
#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);
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
stl_verify_neighbors(stl_file *stl) {
|
|
||||||
int i;
|
|
||||||
int j;
|
|
||||||
stl_edge edge_a;
|
|
||||||
stl_edge edge_b;
|
|
||||||
int neighbor;
|
|
||||||
int vnot;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
stl->stats.backwards_edges = 0;
|
stl->stats.backwards_edges = 0;
|
||||||
|
|
||||||
for(i = 0; i < stl->stats.number_of_facets; i++) {
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||||
for(j = 0; j < 3; j++) {
|
for (int j = 0; j < 3; ++ j) {
|
||||||
|
struct stl_edge {
|
||||||
|
stl_vertex p1;
|
||||||
|
stl_vertex p2;
|
||||||
|
int facet_number;
|
||||||
|
};
|
||||||
|
stl_edge edge_a;
|
||||||
edge_a.p1 = stl->facet_start[i].vertex[j];
|
edge_a.p1 = stl->facet_start[i].vertex[j];
|
||||||
edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3];
|
edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3];
|
||||||
neighbor = stl->neighbors_start[i].neighbor[j];
|
int neighbor = stl->neighbors_start[i].neighbor[j];
|
||||||
vnot = stl->neighbors_start[i].which_vertex_not[j];
|
|
||||||
|
|
||||||
if (neighbor == -1)
|
if (neighbor == -1)
|
||||||
continue; /* this edge has no neighbor... Continue. */
|
continue; // this edge has no neighbor... Continue.
|
||||||
|
int vnot = stl->neighbors_start[i].which_vertex_not[j];
|
||||||
|
stl_edge edge_b;
|
||||||
if (vnot < 3) {
|
if (vnot < 3) {
|
||||||
edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3];
|
edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3];
|
||||||
edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3];
|
edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3];
|
||||||
|
@ -63,9 +57,8 @@ stl_verify_neighbors(stl_file *stl) {
|
||||||
edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 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) {
|
if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) {
|
||||||
/* These edges should match but they don't. Print results. */
|
// 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",
|
BOOST_LOG_TRIVIAL(info) << "edge " << j << " of facet " << i << " doesn't match edge " << (vnot + 1) << " of facet " << neighbor;
|
||||||
j, i, vnot + 1, neighbor);
|
|
||||||
stl_write_facet(stl, (char*)"first facet", i);
|
stl_write_facet(stl, (char*)"first facet", i);
|
||||||
stl_write_facet(stl, (char*)"second facet", neighbor);
|
stl_write_facet(stl, (char*)"second facet", neighbor);
|
||||||
}
|
}
|
||||||
|
@ -75,9 +68,6 @@ stl_verify_neighbors(stl_file *stl) {
|
||||||
|
|
||||||
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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
stl_vertex new_min(x, y, z);
|
stl_vertex new_min(x, y, z);
|
||||||
stl_vertex shift = new_min - stl->stats.min;
|
stl_vertex shift = new_min - stl->stats.min;
|
||||||
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
|
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
|
||||||
|
@ -85,29 +75,21 @@ void stl_translate(stl_file *stl, float x, float y, float z)
|
||||||
stl->facet_start[i].vertex[j] += shift;
|
stl->facet_start[i].vertex[j] += shift;
|
||||||
stl->stats.min = new_min;
|
stl->stats.min = new_min;
|
||||||
stl->stats.max += shift;
|
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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
stl_vertex shift(x, y, z);
|
stl_vertex shift(x, y, z);
|
||||||
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
|
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
|
||||||
for (int j = 0; j < 3; ++ j)
|
for (int j = 0; j < 3; ++ j)
|
||||||
stl->facet_start[i].vertex[j] += shift;
|
stl->facet_start[i].vertex[j] += shift;
|
||||||
stl->stats.min += shift;
|
stl->stats.min += shift;
|
||||||
stl->stats.max += 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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Scale extents.
|
// Scale extents.
|
||||||
auto s = versor.array();
|
auto s = versor.array();
|
||||||
stl->stats.min.array() *= s;
|
stl->stats.min.array() *= s;
|
||||||
|
@ -121,99 +103,96 @@ void stl_scale_versor(stl_file *stl, const stl_vertex &versor)
|
||||||
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
|
for (int i = 0; i < stl->stats.number_of_facets; ++ i)
|
||||||
for (int j = 0; j < 3; ++ j)
|
for (int j = 0; j < 3; ++ j)
|
||||||
stl->facet_start[i].vertex[j].array() *= s;
|
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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
stl_normal normal;
|
stl_normal normal;
|
||||||
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_calculate_normal(normal, &stl->facet_start[i]);
|
stl_calculate_normal(normal, &stl->facet_start[i]);
|
||||||
stl_normalize_vector(normal);
|
stl_normalize_vector(normal);
|
||||||
stl->facet_start[i].normal = 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;
|
||||||
|
x = float(c * xold - s * yold);
|
||||||
|
y = float(s * xold + c * yold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stl_rotate_x(stl_file *stl, float angle)
|
||||||
|
{
|
||||||
double radian_angle = (angle / 180.0) * M_PI;
|
double radian_angle = (angle / 180.0) * M_PI;
|
||||||
double c = cos(radian_angle);
|
double c = cos(radian_angle);
|
||||||
double s = sin(radian_angle);
|
double s = sin(radian_angle);
|
||||||
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
|
||||||
if (stl->error) return;
|
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);
|
||||||
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);
|
stl_get_size(stl);
|
||||||
calculate_normals(stl);
|
calculate_normals(stl);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void stl_rotate_y(stl_file *stl, float angle)
|
||||||
stl_rotate_y(stl_file *stl, float angle) {
|
{
|
||||||
int i;
|
|
||||||
int j;
|
|
||||||
double radian_angle = (angle / 180.0) * M_PI;
|
double radian_angle = (angle / 180.0) * M_PI;
|
||||||
double c = cos(radian_angle);
|
double c = cos(radian_angle);
|
||||||
double s = sin(radian_angle);
|
double s = sin(radian_angle);
|
||||||
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
|
||||||
if (stl->error) return;
|
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);
|
||||||
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);
|
stl_get_size(stl);
|
||||||
calculate_normals(stl);
|
calculate_normals(stl);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void stl_rotate_z(stl_file *stl, float angle)
|
||||||
stl_rotate_z(stl_file *stl, float angle) {
|
{
|
||||||
int i;
|
|
||||||
int j;
|
|
||||||
double radian_angle = (angle / 180.0) * M_PI;
|
double radian_angle = (angle / 180.0) * M_PI;
|
||||||
double c = cos(radian_angle);
|
double c = cos(radian_angle);
|
||||||
double s = sin(radian_angle);
|
double s = sin(radian_angle);
|
||||||
|
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
|
||||||
if (stl->error) return;
|
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);
|
||||||
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);
|
stl_get_size(stl);
|
||||||
calculate_normals(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void its_rotate_y(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(2), v(0), c, s);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
void its_rotate_z(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(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]);
|
||||||
|
@ -226,14 +205,9 @@ void stl_get_size(stl_file *stl)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
for(int i = 0; i < stl->stats.number_of_facets; i++) {
|
|
||||||
for(int j = 0; j < 3; j++) {
|
|
||||||
stl->facet_start[i].vertex[j](2) *= -1.0;
|
stl->facet_start[i].vertex[j](2) *= -1.0;
|
||||||
}
|
|
||||||
}
|
|
||||||
float temp_size = stl->stats.min(2);
|
float temp_size = stl->stats.min(2);
|
||||||
stl->stats.min(2) = stl->stats.max(2);
|
stl->stats.min(2) = stl->stats.max(2);
|
||||||
stl->stats.max(2) = temp_size;
|
stl->stats.max(2) = temp_size;
|
||||||
|
@ -245,13 +219,9 @@ void stl_mirror_xy(stl_file *stl)
|
||||||
|
|
||||||
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++) {
|
|
||||||
for (int j = 0; j < 3; j++) {
|
|
||||||
stl->facet_start[i].vertex[j](0) *= -1.0;
|
stl->facet_start[i].vertex[j](0) *= -1.0;
|
||||||
}
|
|
||||||
}
|
|
||||||
float temp_size = stl->stats.min(0);
|
float temp_size = stl->stats.min(0);
|
||||||
stl->stats.min(0) = stl->stats.max(0);
|
stl->stats.min(0) = stl->stats.max(0);
|
||||||
stl->stats.max(0) = temp_size;
|
stl->stats.max(0) = temp_size;
|
||||||
|
@ -263,48 +233,16 @@ void stl_mirror_yz(stl_file *stl)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
for (int i = 0; i < stl->stats.number_of_facets; i++) {
|
|
||||||
for (int j = 0; j < 3; j++) {
|
|
||||||
stl->facet_start[i].vertex[j](1) *= -1.0;
|
stl->facet_start[i].vertex[j](1) *= -1.0;
|
||||||
}
|
|
||||||
}
|
|
||||||
float temp_size = stl->stats.min(1);
|
float temp_size = stl->stats.min(1);
|
||||||
stl->stats.min(1) = stl->stats.max(1);
|
stl->stats.min(1) = stl->stats.max(1);
|
||||||
stl->stats.max(1) = temp_size;
|
stl->stats.max(1) = temp_size;
|
||||||
stl->stats.min(1) *= -1.0;
|
stl->stats.min(1) *= -1.0;
|
||||||
stl->stats.max(1) *= -1.0;
|
stl->stats.max(1) *= -1.0;
|
||||||
stl_reverse_all_facets(stl);
|
stl_reverse_all_facets(stl);
|
||||||
stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
|
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)
|
||||||
|
@ -335,86 +273,89 @@ static float get_area(stl_facet *facet)
|
||||||
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 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)
|
||||||
|
{
|
||||||
|
stl->stats.volume = get_volume(stl);
|
||||||
|
if (stl->stats.volume < 0.0) {
|
||||||
|
stl_reverse_all_facets(stl);
|
||||||
|
stl->stats.volume = -stl->stats.volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stl_repair(
|
||||||
|
stl_file *stl,
|
||||||
|
bool fixall_flag,
|
||||||
|
bool exact_flag,
|
||||||
|
bool tolerance_flag,
|
||||||
float tolerance,
|
float tolerance,
|
||||||
int increment_flag,
|
bool increment_flag,
|
||||||
float increment,
|
float increment,
|
||||||
int nearby_flag,
|
bool nearby_flag,
|
||||||
int iterations,
|
int iterations,
|
||||||
int remove_unconnected_flag,
|
bool remove_unconnected_flag,
|
||||||
int fill_holes_flag,
|
bool fill_holes_flag,
|
||||||
int normal_directions_flag,
|
bool normal_directions_flag,
|
||||||
int normal_values_flag,
|
bool normal_values_flag,
|
||||||
int reverse_all_flag,
|
bool reverse_all_flag,
|
||||||
int verbose_flag) {
|
bool verbose_flag)
|
||||||
|
{
|
||||||
int i;
|
if (exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) {
|
||||||
int last_edges_fixed = 0;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
|
||||||
|
|
||||||
if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag
|
|
||||||
|| fill_holes_flag || normal_directions_flag) {
|
|
||||||
if (verbose_flag)
|
if (verbose_flag)
|
||||||
printf("Checking exact...\n");
|
printf("Checking exact...\n");
|
||||||
exact_flag = 1;
|
exact_flag = true;
|
||||||
stl_check_facets_exact(stl);
|
stl_check_facets_exact(stl);
|
||||||
stl->stats.facets_w_1_bad_edge =
|
stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_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.connected_facets_3_edge);
|
stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nearby_flag || fixall_flag) {
|
if (nearby_flag || fixall_flag) {
|
||||||
if(!tolerance_flag) {
|
if (! tolerance_flag)
|
||||||
tolerance = stl->stats.shortest_edge;
|
tolerance = stl->stats.shortest_edge;
|
||||||
}
|
if (! increment_flag)
|
||||||
if(!increment_flag) {
|
|
||||||
increment = stl->stats.bounding_diameter / 10000.0;
|
increment = stl->stats.bounding_diameter / 10000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
|
if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
|
||||||
for(i = 0; i < iterations; i++) {
|
int last_edges_fixed = 0;
|
||||||
if(stl->stats.connected_facets_3_edge <
|
for (int i = 0; i < iterations; ++ i) {
|
||||||
stl->stats.number_of_facets) {
|
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);
|
||||||
Checking nearby. Tolerance= %f Iteration=%d of %d...",
|
|
||||||
tolerance, i + 1, iterations);
|
|
||||||
stl_check_facets_nearby(stl, tolerance);
|
stl_check_facets_nearby(stl, tolerance);
|
||||||
if (verbose_flag)
|
if (verbose_flag)
|
||||||
printf(" Fixed %d edges.\n",
|
printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed);
|
||||||
stl->stats.edges_fixed - last_edges_fixed);
|
|
||||||
last_edges_fixed = stl->stats.edges_fixed;
|
last_edges_fixed = stl->stats.edges_fixed;
|
||||||
tolerance += increment;
|
tolerance += increment;
|
||||||
} else {
|
} else {
|
||||||
if (verbose_flag)
|
if (verbose_flag)
|
||||||
printf("\
|
printf("All facets connected. No further nearby check necessary.\n");
|
||||||
All facets connected. No further nearby check necessary.\n");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (verbose_flag)
|
||||||
if (verbose_flag)
|
|
||||||
printf("All facets connected. No nearby check necessary.\n");
|
printf("All facets connected. No nearby check necessary.\n");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remove_unconnected_flag || fixall_flag || fill_holes_flag) {
|
if (remove_unconnected_flag || fixall_flag || fill_holes_flag) {
|
||||||
if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
|
if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) {
|
||||||
if (verbose_flag)
|
if (verbose_flag)
|
||||||
printf("Removing unconnected facets...\n");
|
printf("Removing unconnected facets...\n");
|
||||||
stl_remove_unconnected_facets(stl);
|
stl_remove_unconnected_facets(stl);
|
||||||
} else
|
} else if (verbose_flag)
|
||||||
if (verbose_flag)
|
|
||||||
printf("No unconnected need to be removed.\n");
|
printf("No unconnected need to be removed.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,8 +364,7 @@ All facets connected. No further nearby check necessary.\n");
|
||||||
if (verbose_flag)
|
if (verbose_flag)
|
||||||
printf("Filling holes...\n");
|
printf("Filling holes...\n");
|
||||||
stl_fill_holes(stl);
|
stl_fill_holes(stl);
|
||||||
} else
|
} else if (verbose_flag)
|
||||||
if (verbose_flag)
|
|
||||||
printf("No holes need to be filled.\n");
|
printf("No holes need to be filled.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,7 +386,7 @@ All facets connected. No further nearby check necessary.\n");
|
||||||
stl_fix_normal_values(stl);
|
stl_fix_normal_values(stl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Always calculate the volume. It shouldn't take too long */
|
// Always calculate the volume. It shouldn't take too long.
|
||||||
if (verbose_flag)
|
if (verbose_flag)
|
||||||
printf("Calculating volume...\n");
|
printf("Calculating volume...\n");
|
||||||
stl_calculate_volume(stl);
|
stl_calculate_volume(stl);
|
||||||
|
|
|
@ -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
|
||||||
|
@ -175,6 +172,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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1489,9 +1489,9 @@ namespace Slic3r {
|
||||||
}
|
}
|
||||||
|
|
||||||
// splits volume out of imported geometry
|
// splits volume out of imported geometry
|
||||||
|
TriangleMesh triangle_mesh;
|
||||||
|
stl_file &stl = triangle_mesh.stl;
|
||||||
unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
|
unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
|
||||||
ModelVolume* volume = object.add_volume(TriangleMesh());
|
|
||||||
stl_file& stl = volume->mesh.stl;
|
|
||||||
stl.stats.type = inmemory;
|
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;
|
||||||
|
@ -1510,8 +1510,10 @@ 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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,17 +440,19 @@ 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
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,327 +0,0 @@
|
||||||
#ifndef PRINTEXPORT_HPP
|
|
||||||
#define PRINTEXPORT_HPP
|
|
||||||
|
|
||||||
// For png export of the sliced model
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
|
||||||
#include <boost/filesystem/path.hpp>
|
|
||||||
|
|
||||||
#include "Rasterizer/Rasterizer.hpp"
|
|
||||||
//#include <tbb/parallel_for.h>
|
|
||||||
//#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
// Used for addressing parameters of FilePrinter::set_statistics()
|
|
||||||
enum ePrintStatistics
|
|
||||||
{
|
|
||||||
psUsedMaterial = 0,
|
|
||||||
psNumFade,
|
|
||||||
psNumSlow,
|
|
||||||
psNumFast,
|
|
||||||
|
|
||||||
psCnt
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class FilePrinterFormat {
|
|
||||||
SLA_PNGZIP,
|
|
||||||
SVG
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Interface for a file printer of the slices. Implementation can be an SVG
|
|
||||||
* or PNG printer or any other format.
|
|
||||||
*
|
|
||||||
* The format argument specifies the output format of the printer and it enables
|
|
||||||
* different implementations of this class template for each supported format.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
template<FilePrinterFormat format>
|
|
||||||
class FilePrinter {
|
|
||||||
public:
|
|
||||||
|
|
||||||
// Draw a polygon which is a polygon inside a slice on the specified layer.
|
|
||||||
void draw_polygon(const ExPolygon& p, unsigned lyr);
|
|
||||||
void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr);
|
|
||||||
|
|
||||||
// Tell the printer how many layers should it consider.
|
|
||||||
void layers(unsigned layernum);
|
|
||||||
|
|
||||||
// Get the number of layers in the print.
|
|
||||||
unsigned layers() const;
|
|
||||||
|
|
||||||
/* Switch to a particular layer. If there where less layers then the
|
|
||||||
* specified layer number than an appropriate number of layers will be
|
|
||||||
* allocated in the printer.
|
|
||||||
*/
|
|
||||||
void begin_layer(unsigned layer);
|
|
||||||
|
|
||||||
// Allocate a new layer on top of the last and switch to it.
|
|
||||||
void begin_layer();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finish the selected layer. It means that no drawing is allowed on that
|
|
||||||
* layer anymore. This fact can be used to prepare the file system output
|
|
||||||
* data like png comprimation and so on.
|
|
||||||
*/
|
|
||||||
void finish_layer(unsigned layer);
|
|
||||||
|
|
||||||
// Finish the top layer.
|
|
||||||
void finish_layer();
|
|
||||||
|
|
||||||
// Save all the layers into the file (or dir) specified in the path argument
|
|
||||||
// An optional project name can be added to be used for the layer file names
|
|
||||||
void save(const std::string& path, const std::string& projectname = "");
|
|
||||||
|
|
||||||
// Save only the selected layer to the file specified in path argument.
|
|
||||||
void save_layer(unsigned lyr, const std::string& path);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Provokes static_assert in the right way.
|
|
||||||
template<class T = void> struct VeryFalse { static const bool value = false; };
|
|
||||||
|
|
||||||
// This can be explicitly implemented in the gui layer or the default Zipper
|
|
||||||
// API in libslic3r with minz.
|
|
||||||
template<class Fmt> class LayerWriter {
|
|
||||||
public:
|
|
||||||
|
|
||||||
LayerWriter(const std::string& /*zipfile_path*/)
|
|
||||||
{
|
|
||||||
static_assert(VeryFalse<Fmt>::value,
|
|
||||||
"No layer writer implementation provided!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should create a new file within the zip with the given filename. It
|
|
||||||
// should also finish any previous entry.
|
|
||||||
void next_entry(const std::string& /*fname*/) {}
|
|
||||||
|
|
||||||
// Should create a new file within the archive and write the provided data.
|
|
||||||
void binary_entry(const std::string& /*fname*/,
|
|
||||||
const std::uint8_t* buf, size_t len);
|
|
||||||
|
|
||||||
// Test whether the object can still be used for writing.
|
|
||||||
bool is_ok() { return false; }
|
|
||||||
|
|
||||||
// Write some data (text) into the current file (entry) within the archive.
|
|
||||||
template<class T> LayerWriter& operator<<(T&& /*arg*/) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the current entry into the archive.
|
|
||||||
void finalize() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Implementation for PNG raster output
|
|
||||||
// Be aware that if a large number of layers are allocated, it can very well
|
|
||||||
// exhaust the available memory especially on 32 bit platform.
|
|
||||||
template<> class FilePrinter<FilePrinterFormat::SLA_PNGZIP>
|
|
||||||
{
|
|
||||||
struct Layer {
|
|
||||||
Raster raster;
|
|
||||||
RawBytes rawbytes;
|
|
||||||
|
|
||||||
Layer() {}
|
|
||||||
|
|
||||||
Layer(const Layer&) = delete;
|
|
||||||
Layer(Layer&& m):
|
|
||||||
raster(std::move(m.raster)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// We will save the compressed PNG data into stringstreams which can be done
|
|
||||||
// in parallel. Later we can write every layer to the disk sequentially.
|
|
||||||
std::vector<Layer> m_layers_rst;
|
|
||||||
Raster::Resolution m_res;
|
|
||||||
Raster::PixelDim m_pxdim;
|
|
||||||
double m_exp_time_s = .0, m_exp_time_first_s = .0;
|
|
||||||
double m_layer_height = .0;
|
|
||||||
Raster::Origin m_o = Raster::Origin::TOP_LEFT;
|
|
||||||
double m_gamma;
|
|
||||||
|
|
||||||
double m_used_material = 0.0;
|
|
||||||
int m_cnt_fade_layers = 0;
|
|
||||||
int m_cnt_slow_layers = 0;
|
|
||||||
int m_cnt_fast_layers = 0;
|
|
||||||
|
|
||||||
std::string createIniContent(const std::string& projectname) {
|
|
||||||
using std::string;
|
|
||||||
using std::to_string;
|
|
||||||
|
|
||||||
auto expt_str = to_string(m_exp_time_s);
|
|
||||||
auto expt_first_str = to_string(m_exp_time_first_s);
|
|
||||||
auto layerh_str = to_string(m_layer_height);
|
|
||||||
|
|
||||||
const std::string cnt_fade_layers = to_string(m_cnt_fade_layers);
|
|
||||||
const std::string cnt_slow_layers = to_string(m_cnt_slow_layers);
|
|
||||||
const std::string cnt_fast_layers = to_string(m_cnt_fast_layers);
|
|
||||||
const std::string used_material = to_string(m_used_material);
|
|
||||||
|
|
||||||
return string(
|
|
||||||
"action = print\n"
|
|
||||||
"jobDir = ") + projectname + "\n" +
|
|
||||||
"expTime = " + expt_str + "\n"
|
|
||||||
"expTimeFirst = " + expt_first_str + "\n"
|
|
||||||
"numFade = " + cnt_fade_layers + "\n"
|
|
||||||
"layerHeight = " + layerh_str + "\n"
|
|
||||||
"usedMaterial = " + used_material + "\n"
|
|
||||||
"numSlow = " + cnt_slow_layers + "\n"
|
|
||||||
"numFast = " + cnt_fast_layers + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
enum RasterOrientation {
|
|
||||||
RO_LANDSCAPE,
|
|
||||||
RO_PORTRAIT
|
|
||||||
};
|
|
||||||
|
|
||||||
// We will play with the raster's coordinate origin parameter. When the
|
|
||||||
// printer should print in landscape mode it should have the Y axis flipped
|
|
||||||
// because the layers should be displayed upside down. PNG has its
|
|
||||||
// coordinate origin in the top-left corner so normally the Raster objects
|
|
||||||
// should be instantiated with the TOP_LEFT flag. However, in landscape mode
|
|
||||||
// we do want the pictures to be upside down so we will make BOTTOM_LEFT
|
|
||||||
// type rasters and the PNG format will do the flipping automatically.
|
|
||||||
|
|
||||||
// In case of portrait images, we have to rotate the image by a 90 degrees
|
|
||||||
// and flip the y axis. To get the correct upside-down orientation of the
|
|
||||||
// slice images, we can flip the x and y coordinates of the input polygons
|
|
||||||
// and do the Y flipping of the image. This will generate the correct
|
|
||||||
// orientation in portrait mode.
|
|
||||||
|
|
||||||
inline FilePrinter(double width_mm, double height_mm,
|
|
||||||
unsigned width_px, unsigned height_px,
|
|
||||||
double layer_height,
|
|
||||||
double exp_time, double exp_time_first,
|
|
||||||
RasterOrientation ro = RO_PORTRAIT,
|
|
||||||
double gamma = 1.0):
|
|
||||||
m_res(width_px, height_px),
|
|
||||||
m_pxdim(width_mm/width_px, height_mm/height_px),
|
|
||||||
m_exp_time_s(exp_time),
|
|
||||||
m_exp_time_first_s(exp_time_first),
|
|
||||||
m_layer_height(layer_height),
|
|
||||||
|
|
||||||
// Here is the trick with the orientation.
|
|
||||||
m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT :
|
|
||||||
Raster::Origin::TOP_LEFT ),
|
|
||||||
m_gamma(gamma)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
FilePrinter(const FilePrinter& ) = delete;
|
|
||||||
FilePrinter(FilePrinter&& m):
|
|
||||||
m_layers_rst(std::move(m.m_layers_rst)),
|
|
||||||
m_res(m.m_res),
|
|
||||||
m_pxdim(m.m_pxdim) {}
|
|
||||||
|
|
||||||
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
|
||||||
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
|
||||||
|
|
||||||
inline void draw_polygon(const ExPolygon& p, unsigned lyr) {
|
|
||||||
assert(lyr < m_layers_rst.size());
|
|
||||||
m_layers_rst[lyr].raster.draw(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) {
|
|
||||||
assert(lyr < m_layers_rst.size());
|
|
||||||
m_layers_rst[lyr].raster.draw(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void begin_layer(unsigned lyr) {
|
|
||||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
|
||||||
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o, m_gamma);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void begin_layer() {
|
|
||||||
m_layers_rst.emplace_back();
|
|
||||||
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_o, m_gamma);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void finish_layer(unsigned lyr_id) {
|
|
||||||
assert(lyr_id < m_layers_rst.size());
|
|
||||||
m_layers_rst[lyr_id].rawbytes =
|
|
||||||
m_layers_rst[lyr_id].raster.save(Raster::Compression::PNG);
|
|
||||||
m_layers_rst[lyr_id].raster.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void finish_layer() {
|
|
||||||
if(!m_layers_rst.empty()) {
|
|
||||||
m_layers_rst.back().rawbytes =
|
|
||||||
m_layers_rst.back().raster.save(Raster::Compression::PNG);
|
|
||||||
m_layers_rst.back().raster.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class LyrFmt>
|
|
||||||
inline void save(const std::string& fpath, const std::string& prjname = "")
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
LayerWriter<LyrFmt> writer(fpath);
|
|
||||||
if(!writer.is_ok()) return;
|
|
||||||
|
|
||||||
std::string project = prjname.empty()?
|
|
||||||
boost::filesystem::path(fpath).stem().string() : prjname;
|
|
||||||
|
|
||||||
writer.next_entry("config.ini");
|
|
||||||
if(!writer.is_ok()) return;
|
|
||||||
|
|
||||||
writer << createIniContent(project);
|
|
||||||
|
|
||||||
for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++)
|
|
||||||
{
|
|
||||||
if(m_layers_rst[i].rawbytes.size() > 0) {
|
|
||||||
char lyrnum[6];
|
|
||||||
std::sprintf(lyrnum, "%.5d", i);
|
|
||||||
auto zfilename = project + lyrnum + ".png";
|
|
||||||
if(!writer.is_ok()) break;
|
|
||||||
|
|
||||||
writer.binary_entry(zfilename,
|
|
||||||
m_layers_rst[i].rawbytes.data(),
|
|
||||||
m_layers_rst[i].rawbytes.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.finalize();
|
|
||||||
} catch(std::exception& e) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
|
||||||
// Rethrow the exception
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void save_layer(unsigned lyr, const std::string& path) {
|
|
||||||
unsigned i = lyr;
|
|
||||||
assert(i < m_layers_rst.size());
|
|
||||||
|
|
||||||
char lyrnum[6];
|
|
||||||
std::sprintf(lyrnum, "%.5d", lyr);
|
|
||||||
std::string loc = path + "layer" + lyrnum + ".png";
|
|
||||||
|
|
||||||
std::fstream out(loc, std::fstream::out | std::fstream::binary);
|
|
||||||
if(out.good()) {
|
|
||||||
m_layers_rst[i].raster.save(out, Raster::Compression::PNG);
|
|
||||||
} else {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "Can't create file for layer";
|
|
||||||
}
|
|
||||||
|
|
||||||
out.close();
|
|
||||||
m_layers_rst[i].raster.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_statistics(const std::vector<double> statistics)
|
|
||||||
{
|
|
||||||
if (statistics.size() != psCnt)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_used_material = statistics[psUsedMaterial];
|
|
||||||
m_cnt_fade_layers = int(statistics[psNumFade]);
|
|
||||||
m_cnt_slow_layers = int(statistics[psNumSlow]);
|
|
||||||
m_cnt_fast_layers = int(statistics[psNumFast]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // PRINTEXPORT_HPP
|
|
|
@ -1797,7 +1797,7 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
|
||||||
if (! volumes.empty()) {
|
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();
|
||||||
|
|
|
@ -62,9 +62,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
||||||
// Copy the points into the mesh, convert them from 2D to 3D
|
// Copy the points into the mesh, convert them from 2D to 3D
|
||||||
rpts.reserve(upoints.size() + lpoints.size());
|
rpts.reserve(upoints.size() + lpoints.size());
|
||||||
ind.reserve(2 * upoints.size() + 2 * lpoints.size());
|
ind.reserve(2 * upoints.size() + 2 * lpoints.size());
|
||||||
const double sf = SCALING_FACTOR;
|
for (auto &p : upoints)
|
||||||
for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm);
|
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm);
|
||||||
for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm);
|
for (auto &p : lpoints)
|
||||||
|
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm);
|
||||||
|
|
||||||
// Create pointing indices into vertex arrays. u-upper, l-lower
|
// Create pointing indices into vertex arrays. u-upper, l-lower
|
||||||
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
|
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
|
||||||
|
@ -202,7 +203,7 @@ void offset(ExPolygon& sh, coord_t distance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipperOffset offs;
|
ClipperOffset offs;
|
||||||
offs.ArcTolerance = 0.01*mm(1);
|
offs.ArcTolerance = 0.01*scaled(1.0);
|
||||||
Paths result;
|
Paths result;
|
||||||
offs.AddPath(ctour, jtRound, etClosedPolygon);
|
offs.AddPath(ctour, jtRound, etClosedPolygon);
|
||||||
offs.AddPaths(holes, jtRound, etClosedPolygon);
|
offs.AddPaths(holes, jtRound, etClosedPolygon);
|
||||||
|
@ -305,16 +306,6 @@ ExPolygons unify(const ExPolygons& shapes) {
|
||||||
return retv;
|
return retv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only a debug function to generate top and bottom plates from a 2D shape.
|
|
||||||
/// It is not used in the algorithm directly.
|
|
||||||
inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
|
|
||||||
auto lower = triangulate_expolygon_3d(poly);
|
|
||||||
auto upper = triangulate_expolygon_3d(poly, z_distance*SCALING_FACTOR, true);
|
|
||||||
Contour3D ret;
|
|
||||||
ret.merge(lower); ret.merge(upper);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This method will create a rounded edge around a flat polygon in 3d space.
|
/// This method will create a rounded edge around a flat polygon in 3d space.
|
||||||
/// 'base_plate' parameter is the target plate.
|
/// 'base_plate' parameter is the target plate.
|
||||||
/// 'radius' is the radius of the edges.
|
/// 'radius' is the radius of the edges.
|
||||||
|
@ -360,7 +351,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
||||||
double x2 = xx*xx;
|
double x2 = xx*xx;
|
||||||
double stepy = std::sqrt(r2 - x2);
|
double stepy = std::sqrt(r2 - x2);
|
||||||
|
|
||||||
offset(ob, s*mm(xx));
|
offset(ob, s*scaled(xx));
|
||||||
wh = ceilheight_mm - radius_mm + stepy;
|
wh = ceilheight_mm - radius_mm + stepy;
|
||||||
|
|
||||||
Contour3D pwalls;
|
Contour3D pwalls;
|
||||||
|
@ -384,7 +375,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
||||||
double xx = radius_mm - i*stepx;
|
double xx = radius_mm - i*stepx;
|
||||||
double x2 = xx*xx;
|
double x2 = xx*xx;
|
||||||
double stepy = std::sqrt(r2 - x2);
|
double stepy = std::sqrt(r2 - x2);
|
||||||
offset(ob, s*mm(xx));
|
offset(ob, s*scaled(xx));
|
||||||
wh = ceilheight_mm - radius_mm - stepy;
|
wh = ceilheight_mm - radius_mm - stepy;
|
||||||
|
|
||||||
Contour3D pwalls;
|
Contour3D pwalls;
|
||||||
|
@ -404,41 +395,6 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
||||||
return curvedwalls;
|
return curvedwalls;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generating the concave part of the 3D pool with the bottom plate and the
|
|
||||||
/// side walls.
|
|
||||||
Contour3D inner_bed(const ExPolygon& poly,
|
|
||||||
double depth_mm,
|
|
||||||
double begin_h_mm = 0)
|
|
||||||
{
|
|
||||||
Contour3D bottom;
|
|
||||||
Pointf3s triangles = triangulate_expolygon_3d(poly, -depth_mm + begin_h_mm);
|
|
||||||
bottom.merge(triangles);
|
|
||||||
|
|
||||||
coord_t depth = mm(depth_mm);
|
|
||||||
coord_t begin_h = mm(begin_h_mm);
|
|
||||||
|
|
||||||
auto lines = poly.lines();
|
|
||||||
|
|
||||||
// Generate outer walls
|
|
||||||
auto fp = [](const Point& p, Point::coord_type z) {
|
|
||||||
return unscale(x(p), y(p), z);
|
|
||||||
};
|
|
||||||
|
|
||||||
for(auto& l : lines) {
|
|
||||||
auto s = coord_t(bottom.points.size());
|
|
||||||
|
|
||||||
bottom.points.emplace_back(fp(l.a, -depth + begin_h));
|
|
||||||
bottom.points.emplace_back(fp(l.b, -depth + begin_h));
|
|
||||||
bottom.points.emplace_back(fp(l.a, begin_h));
|
|
||||||
bottom.points.emplace_back(fp(l.b, begin_h));
|
|
||||||
|
|
||||||
bottom.indices.emplace_back(s + 3, s + 1, s);
|
|
||||||
bottom.indices.emplace_back(s + 2, s + 3, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Point centroid(Points& pp) {
|
inline Point centroid(Points& pp) {
|
||||||
Point c;
|
Point c;
|
||||||
switch(pp.size()) {
|
switch(pp.size()) {
|
||||||
|
@ -520,7 +476,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
||||||
double dx = x(c) - x(cc), dy = y(c) - y(cc);
|
double dx = x(c) - x(cc), dy = y(c) - y(cc);
|
||||||
double l = std::sqrt(dx * dx + dy * dy);
|
double l = std::sqrt(dx * dx + dy * dy);
|
||||||
double nx = dx / l, ny = dy / l;
|
double nx = dx / l, ny = dy / l;
|
||||||
double max_dist = mm(max_dist_mm);
|
double max_dist = scaled(max_dist_mm);
|
||||||
|
|
||||||
ExPolygon& expo = punion[idx++];
|
ExPolygon& expo = punion[idx++];
|
||||||
BoundingBox querybb(expo);
|
BoundingBox querybb(expo);
|
||||||
|
@ -536,10 +492,10 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
||||||
ctour.reserve(3);
|
ctour.reserve(3);
|
||||||
ctour.emplace_back(cc);
|
ctour.emplace_back(cc);
|
||||||
|
|
||||||
Point d(coord_t(mm(1)*nx), coord_t(mm(1)*ny));
|
Point d(coord_t(scaled(1.)*nx), coord_t(scaled(1.)*ny));
|
||||||
ctour.emplace_back(c + Point( -y(d), x(d) ));
|
ctour.emplace_back(c + Point( -y(d), x(d) ));
|
||||||
ctour.emplace_back(c + Point( y(d), -x(d) ));
|
ctour.emplace_back(c + Point( y(d), -x(d) ));
|
||||||
offset(r, mm(1));
|
offset(r, scaled(1.));
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
});
|
});
|
||||||
|
@ -571,15 +527,16 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
|
||||||
// Now we have to unify all slice layers which can be an expensive operation
|
// Now we have to unify all slice layers which can be an expensive operation
|
||||||
// so we will try to simplify the polygons
|
// so we will try to simplify the polygons
|
||||||
ExPolygons tmp; tmp.reserve(count);
|
ExPolygons tmp; tmp.reserve(count);
|
||||||
for(ExPolygons& o : out) for(ExPolygon& e : o) {
|
for(ExPolygons& o : out)
|
||||||
auto&& exss = e.simplify(0.1/SCALING_FACTOR);
|
for(ExPolygon& e : o) {
|
||||||
|
auto&& exss = e.simplify(scaled(0.1));
|
||||||
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
||||||
}
|
}
|
||||||
|
|
||||||
ExPolygons utmp = unify(tmp);
|
ExPolygons utmp = unify(tmp);
|
||||||
|
|
||||||
for(auto& o : utmp) {
|
for(auto& o : utmp) {
|
||||||
auto&& smp = o.simplify(0.1/SCALING_FACTOR);
|
auto&& smp = o.simplify(scaled(0.1));
|
||||||
output.insert(output.end(), smp.begin(), smp.end());
|
output.insert(output.end(), smp.begin(), smp.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -609,11 +566,11 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
||||||
const double bottom_offs = (thickness + wingheight) / std::tan(slope);
|
const double bottom_offs = (thickness + wingheight) / std::tan(slope);
|
||||||
|
|
||||||
// scaled values
|
// scaled values
|
||||||
const coord_t s_thickness = mm(thickness);
|
const coord_t s_thickness = scaled(thickness);
|
||||||
const coord_t s_eradius = mm(cfg.edge_radius_mm);
|
const coord_t s_eradius = scaled(cfg.edge_radius_mm);
|
||||||
const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness);
|
const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness);
|
||||||
const coord_t s_wingdist = mm(wingdist);
|
const coord_t s_wingdist = scaled(wingdist);
|
||||||
const coord_t s_bottom_offs = mm(bottom_offs);
|
const coord_t s_bottom_offs = scaled(bottom_offs);
|
||||||
|
|
||||||
auto& thrcl = cfg.throw_on_cancel;
|
auto& thrcl = cfg.throw_on_cancel;
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,6 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
using coord_t = Point::coord_type;
|
|
||||||
|
|
||||||
/// get the scaled clipper units for a millimeter value
|
|
||||||
inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); }
|
|
||||||
|
|
||||||
/// Get x and y coordinates (because we are eigenizing...)
|
/// Get x and y coordinates (because we are eigenizing...)
|
||||||
inline coord_t x(const Point& p) { return p(0); }
|
inline coord_t x(const Point& p) { return p(0); }
|
||||||
inline coord_t y(const Point& p) { return p(1); }
|
inline coord_t y(const Point& p) { return p(1); }
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
@ -82,6 +94,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;
|
||||||
agg::scanline_p8 scanlines;
|
agg::scanline_p8 scanlines;
|
||||||
|
@ -90,13 +115,15 @@ public:
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,9 +136,9 @@ public:
|
||||||
|
|
||||||
inline TBuffer& buffer() { return m_buf; }
|
inline TBuffer& buffer() { return m_buf; }
|
||||||
|
|
||||||
inline const Raster::Resolution resolution() { return m_resolution; }
|
inline Format format() const { return m_fmt; }
|
||||||
|
|
||||||
inline Origin origin() const /*noexcept*/ { return m_o; }
|
inline const Raster::Resolution resolution() { return m_resolution; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline double getPx(const Point& p) {
|
inline double getPx(const Point& p) {
|
||||||
|
@ -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
|
|
@ -1,9 +1,11 @@
|
||||||
#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; }
|
||||||
|
@ -12,6 +14,8 @@ 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 {
|
||||||
|
|
||||||
|
@ -24,14 +28,17 @@ 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,23 +83,33 @@ 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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
/// Save the raster on the specified stream.
|
// Saving the raster:
|
||||||
void save(std::ostream& stream, Compression comp = Compression::RAW);
|
// It is possible to override the format given in the constructor but
|
||||||
|
// be aware that the mirroring will not be modified.
|
||||||
|
|
||||||
RawBytes save(Compression comp = Compression::RAW);
|
/// Save the raster on the specified stream.
|
||||||
|
void save(std::ostream& stream, Format);
|
||||||
|
void save(std::ostream& stream);
|
||||||
|
|
||||||
|
/// 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
|
136
src/libslic3r/SLA/SLARasterWriter.cpp
Normal file
136
src/libslic3r/SLA/SLARasterWriter.cpp
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
#include "SLARasterWriter.hpp"
|
||||||
|
#include "libslic3r/Zipper.hpp"
|
||||||
|
#include "ExPolygon.hpp"
|
||||||
|
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
std::string SLARasterWriter::createIniContent(const std::string& projectname) const
|
||||||
|
{
|
||||||
|
auto expt_str = std::to_string(m_exp_time_s);
|
||||||
|
auto expt_first_str = std::to_string(m_exp_time_first_s);
|
||||||
|
auto layerh_str = std::to_string(m_layer_height);
|
||||||
|
|
||||||
|
const std::string cnt_fade_layers = std::to_string(m_cnt_fade_layers);
|
||||||
|
const std::string cnt_slow_layers = std::to_string(m_cnt_slow_layers);
|
||||||
|
const std::string cnt_fast_layers = std::to_string(m_cnt_fast_layers);
|
||||||
|
const std::string used_material = std::to_string(m_used_material);
|
||||||
|
|
||||||
|
return std::string(
|
||||||
|
"action = print\n"
|
||||||
|
"jobDir = ") + projectname + "\n" +
|
||||||
|
"expTime = " + expt_str + "\n"
|
||||||
|
"expTimeFirst = " + expt_first_str + "\n"
|
||||||
|
"numFade = " + cnt_fade_layers + "\n"
|
||||||
|
"layerHeight = " + layerh_str + "\n"
|
||||||
|
"usedMaterial = " + used_material + "\n"
|
||||||
|
"numSlow = " + cnt_slow_layers + "\n"
|
||||||
|
"numFast = " + cnt_fast_layers + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLARasterWriter::flpXY(ClipperLib::Polygon &poly)
|
||||||
|
{
|
||||||
|
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
||||||
|
std::reverse(poly.Contour.begin(), poly.Contour.end());
|
||||||
|
|
||||||
|
for(auto& h : poly.Holes) {
|
||||||
|
for(auto& p : h) std::swap(p.X, p.Y);
|
||||||
|
std::reverse(h.begin(), h.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLARasterWriter::flpXY(ExPolygon &poly)
|
||||||
|
{
|
||||||
|
for(auto& p : poly.contour.points) p = Point(p.y(), p.x());
|
||||||
|
std::reverse(poly.contour.points.begin(), poly.contour.points.end());
|
||||||
|
|
||||||
|
for(auto& h : poly.holes) {
|
||||||
|
for(auto& p : h.points) p = Point(p.y(), p.x());
|
||||||
|
std::reverse(h.points.begin(), h.points.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SLARasterWriter::SLARasterWriter(const SLAPrinterConfig &cfg,
|
||||||
|
const SLAMaterialConfig &mcfg,
|
||||||
|
double layer_height)
|
||||||
|
{
|
||||||
|
double w = cfg.display_width.getFloat();
|
||||||
|
double h = cfg.display_height.getFloat();
|
||||||
|
auto pw = unsigned(cfg.display_pixels_x.getInt());
|
||||||
|
auto ph = unsigned(cfg.display_pixels_y.getInt());
|
||||||
|
|
||||||
|
m_mirror[X] = cfg.display_mirror_x.getBool();
|
||||||
|
|
||||||
|
// PNG raster will implicitly do an Y mirror
|
||||||
|
m_mirror[Y] = ! cfg.display_mirror_y.getBool();
|
||||||
|
|
||||||
|
auto ro = cfg.display_orientation.getInt();
|
||||||
|
|
||||||
|
if(ro == roPortrait) {
|
||||||
|
std::swap(w, h);
|
||||||
|
std::swap(pw, ph);
|
||||||
|
m_o = roPortrait;
|
||||||
|
|
||||||
|
// XY flipping implicitly does an X mirror
|
||||||
|
m_mirror[X] = ! m_mirror[X];
|
||||||
|
} else m_o = roLandscape;
|
||||||
|
|
||||||
|
m_res = Raster::Resolution(pw, ph);
|
||||||
|
m_pxdim = Raster::PixelDim(w/pw, h/ph);
|
||||||
|
m_exp_time_s = mcfg.exposure_time.getFloat();
|
||||||
|
m_exp_time_first_s = mcfg.initial_exposure_time.getFloat();
|
||||||
|
m_layer_height = layer_height;
|
||||||
|
|
||||||
|
m_gamma = cfg.gamma_correction.getFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLARasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Zipper zipper(fpath); // zipper with no compression
|
||||||
|
|
||||||
|
std::string project = prjname.empty()?
|
||||||
|
boost::filesystem::path(fpath).stem().string() : prjname;
|
||||||
|
|
||||||
|
zipper.add_entry("config.ini");
|
||||||
|
|
||||||
|
zipper << createIniContent(project);
|
||||||
|
|
||||||
|
for(unsigned i = 0; i < m_layers_rst.size(); i++)
|
||||||
|
{
|
||||||
|
if(m_layers_rst[i].rawbytes.size() > 0) {
|
||||||
|
char lyrnum[6];
|
||||||
|
std::sprintf(lyrnum, "%.5d", i);
|
||||||
|
auto zfilename = project + lyrnum + ".png";
|
||||||
|
|
||||||
|
// Add binary entry to the zipper
|
||||||
|
zipper.add_entry(zfilename,
|
||||||
|
m_layers_rst[i].rawbytes.data(),
|
||||||
|
m_layers_rst[i].rawbytes.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zipper.finalize();
|
||||||
|
} catch(std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||||
|
// Rethrow the exception
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLARasterWriter::set_statistics(const std::vector<double> statistics)
|
||||||
|
{
|
||||||
|
if (statistics.size() != psCnt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_used_material = statistics[psUsedMaterial];
|
||||||
|
m_cnt_fade_layers = int(statistics[psNumFade]);
|
||||||
|
m_cnt_slow_layers = int(statistics[psNumSlow]);
|
||||||
|
m_cnt_fast_layers = int(statistics[psNumFast]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sla
|
||||||
|
} // namespace Slic3r
|
167
src/libslic3r/SLA/SLARasterWriter.hpp
Normal file
167
src/libslic3r/SLA/SLARasterWriter.hpp
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
#ifndef SLARASTERWRITER_HPP
|
||||||
|
#define SLARASTERWRITER_HPP
|
||||||
|
|
||||||
|
// For png export of the sliced model
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
#include "SLARaster.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
// Implementation for PNG raster output
|
||||||
|
// Be aware that if a large number of layers are allocated, it can very well
|
||||||
|
// exhaust the available memory especially on 32 bit platform.
|
||||||
|
// This class is designed to be used in parallel mode. Layers have an ID and
|
||||||
|
// each layer can be written and compressed independently (in parallel).
|
||||||
|
// At the end when all layers where written, the save method can be used to
|
||||||
|
// write out the result into a zipped archive.
|
||||||
|
class SLARasterWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum RasterOrientation {
|
||||||
|
roLandscape,
|
||||||
|
roPortrait
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used for addressing parameters of set_statistics()
|
||||||
|
enum ePrintStatistics
|
||||||
|
{
|
||||||
|
psUsedMaterial = 0,
|
||||||
|
psNumFade,
|
||||||
|
psNumSlow,
|
||||||
|
psNumFast,
|
||||||
|
|
||||||
|
psCnt
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// A struct to bind the raster image data and its compressed bytes together.
|
||||||
|
struct Layer {
|
||||||
|
Raster raster;
|
||||||
|
RawBytes rawbytes;
|
||||||
|
|
||||||
|
Layer() = default;
|
||||||
|
Layer(const Layer&) = delete; // The image is big, do not copy by accident
|
||||||
|
Layer& operator=(const Layer&) = delete;
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////
|
||||||
|
// FIXME: the following is needed for MSVC2013 compatibility
|
||||||
|
// /////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Layer(Layer&& m) = default;
|
||||||
|
// Layer& operator=(Layer&&) = default;
|
||||||
|
Layer(Layer &&m):
|
||||||
|
raster(std::move(m.raster)), rawbytes(std::move(m.rawbytes)) {}
|
||||||
|
Layer& operator=(Layer &&m) {
|
||||||
|
raster = std::move(m.raster); rawbytes = std::move(m.rawbytes);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// We will save the compressed PNG data into RawBytes type buffers in
|
||||||
|
// parallel. Later we can write every layer to the disk sequentially.
|
||||||
|
std::vector<Layer> m_layers_rst;
|
||||||
|
Raster::Resolution m_res;
|
||||||
|
Raster::PixelDim m_pxdim;
|
||||||
|
double m_exp_time_s = .0, m_exp_time_first_s = .0;
|
||||||
|
double m_layer_height = .0;
|
||||||
|
RasterOrientation m_o = roPortrait;
|
||||||
|
std::array<bool, 2> m_mirror;
|
||||||
|
|
||||||
|
double m_gamma;
|
||||||
|
|
||||||
|
double m_used_material = 0.0;
|
||||||
|
int m_cnt_fade_layers = 0;
|
||||||
|
int m_cnt_slow_layers = 0;
|
||||||
|
int m_cnt_fast_layers = 0;
|
||||||
|
|
||||||
|
std::string createIniContent(const std::string& projectname) const;
|
||||||
|
|
||||||
|
static void flpXY(ClipperLib::Polygon& poly);
|
||||||
|
static void flpXY(ExPolygon& poly);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
SLARasterWriter(const SLAPrinterConfig& cfg,
|
||||||
|
const SLAMaterialConfig& mcfg,
|
||||||
|
double layer_height);
|
||||||
|
|
||||||
|
SLARasterWriter(const SLARasterWriter& ) = delete;
|
||||||
|
SLARasterWriter& operator=(const SLARasterWriter&) = delete;
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
// FIXME: the following is needed for MSVC2013 compatibility
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// SLARasterWriter(SLARasterWriter&& m) = default;
|
||||||
|
// SLARasterWriter& operator=(SLARasterWriter&&) = default;
|
||||||
|
SLARasterWriter(SLARasterWriter&& m):
|
||||||
|
m_layers_rst(std::move(m.m_layers_rst)),
|
||||||
|
m_res(m.m_res),
|
||||||
|
m_pxdim(m.m_pxdim),
|
||||||
|
m_exp_time_s(m.m_exp_time_s),
|
||||||
|
m_exp_time_first_s(m.m_exp_time_first_s),
|
||||||
|
m_layer_height(m.m_layer_height),
|
||||||
|
m_o(m.m_o),
|
||||||
|
m_mirror(std::move(m.m_mirror)),
|
||||||
|
m_gamma(m.m_gamma),
|
||||||
|
m_used_material(m.m_used_material),
|
||||||
|
m_cnt_fade_layers(m.m_cnt_fade_layers),
|
||||||
|
m_cnt_slow_layers(m.m_cnt_slow_layers),
|
||||||
|
m_cnt_fast_layers(m.m_cnt_fast_layers)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
||||||
|
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
||||||
|
|
||||||
|
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr) {
|
||||||
|
assert(lyr < m_layers_rst.size());
|
||||||
|
if(m_o == roPortrait) {
|
||||||
|
Poly poly(p); flpXY(poly);
|
||||||
|
m_layers_rst[lyr].raster.draw(poly);
|
||||||
|
}
|
||||||
|
else m_layers_rst[lyr].raster.draw(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void begin_layer(unsigned lyr) {
|
||||||
|
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
||||||
|
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void begin_layer() {
|
||||||
|
m_layers_rst.emplace_back();
|
||||||
|
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void finish_layer(unsigned lyr_id) {
|
||||||
|
assert(lyr_id < m_layers_rst.size());
|
||||||
|
m_layers_rst[lyr_id].rawbytes =
|
||||||
|
m_layers_rst[lyr_id].raster.save(Raster::Format::PNG);
|
||||||
|
m_layers_rst[lyr_id].raster.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void finish_layer() {
|
||||||
|
if(!m_layers_rst.empty()) {
|
||||||
|
m_layers_rst.back().rawbytes =
|
||||||
|
m_layers_rst.back().raster.save(Raster::Format::PNG);
|
||||||
|
m_layers_rst.back().raster.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(const std::string& fpath, const std::string& prjname = "");
|
||||||
|
|
||||||
|
void set_statistics(const std::vector<double> statistics);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sla
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // SLARASTERWRITER_HPP
|
|
@ -44,7 +44,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
||||||
// call the status callback in each iteration but the actual value may be
|
// 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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,7 +1072,7 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -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"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,41 +70,19 @@ 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";
|
||||||
|
|
||||||
|
@ -115,15 +90,17 @@ void TriangleMesh::repair()
|
||||||
#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
|
||||||
|
std::vector<realT> src_vertices;
|
||||||
try
|
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";
|
||||||
|
assert(stl_validate(&this->stl));
|
||||||
if (! this->repaired)
|
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, §ion);
|
this->make_expolygons_simple(upper_lines, §ion);
|
||||||
|
@ -1815,7 +1807,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lower != NULL) {
|
if (lower != nullptr) {
|
||||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part";
|
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part";
|
||||||
ExPolygons section;
|
ExPolygons section;
|
||||||
this->make_expolygons_simple(lower_lines, §ion);
|
this->make_expolygons_simple(lower_lines, §ion);
|
||||||
|
@ -1905,10 +1897,10 @@ TriangleMesh make_cylinder(double r, double h, double fa)
|
||||||
//FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html
|
//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);
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -49,29 +49,31 @@ typedef double coordf_t;
|
||||||
//inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
|
//inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
|
||||||
#define scale_(val) ((val) / SCALING_FACTOR)
|
#define scale_(val) ((val) / SCALING_FACTOR)
|
||||||
|
|
||||||
#if defined(_MSC_VER) && (_MSC_VER < 1910)
|
#define SCALED_EPSILON scale_(EPSILON)
|
||||||
template<class Tf> inline coord_t scaled(Tf val)
|
|
||||||
|
#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/"
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||||
|
# define SLIC3R_CONSTEXPR
|
||||||
|
# define SLIC3R_NOEXCEPT
|
||||||
#else
|
#else
|
||||||
template<class Tf> inline constexpr coord_t scaled(Tf val)
|
#define SLIC3R_CONSTEXPR constexpr
|
||||||
#endif // _MSC_VER
|
#define SLIC3R_NOEXCEPT noexcept
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<class Tf> inline SLIC3R_CONSTEXPR coord_t scaled(Tf val)
|
||||||
{
|
{
|
||||||
static_assert (std::is_floating_point<Tf>::value, "Floating point only");
|
static_assert (std::is_floating_point<Tf>::value, "Floating point only");
|
||||||
return coord_t(val / Tf(SCALING_FACTOR));
|
return coord_t(val / Tf(SCALING_FACTOR));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_MSC_VER) && (_MSC_VER < 1910)
|
template<class Tf = double> inline SLIC3R_CONSTEXPR Tf unscaled(coord_t val)
|
||||||
template<class Tf> inline Tf unscaled(coord_t val)
|
|
||||||
#else
|
|
||||||
template<class Tf> inline constexpr Tf unscaled(coord_t val)
|
|
||||||
#endif // _MSC_VER
|
|
||||||
{
|
{
|
||||||
static_assert (std::is_floating_point<Tf>::value, "Floating point only");
|
static_assert (std::is_floating_point<Tf>::value, "Floating point only");
|
||||||
return Tf(val * Tf(SCALING_FACTOR));
|
return Tf(val * Tf(SCALING_FACTOR));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SCALED_EPSILON scale_(EPSILON)
|
inline SLIC3R_CONSTEXPR float unscaledf(coord_t val) { return unscaled<float>(val); }
|
||||||
|
|
||||||
#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/"
|
|
||||||
|
|
||||||
inline std::string debug_out_path(const char *name, ...)
|
inline std::string debug_out_path(const char *name, ...)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -1182,6 +1182,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
|
||||||
|
|
||||||
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
|
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
|
||||||
: m_canvas(canvas)
|
: m_canvas(canvas)
|
||||||
|
@ -2438,6 +2439,20 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
|
||||||
}
|
}
|
||||||
else if (keyCode == WXK_CONTROL)
|
else if (keyCode == WXK_CONTROL)
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
|
// DoubleSlider navigation in Preview
|
||||||
|
else if (keyCode == WXK_LEFT ||
|
||||||
|
keyCode == WXK_RIGHT ||
|
||||||
|
keyCode == WXK_UP ||
|
||||||
|
keyCode == WXK_DOWN ||
|
||||||
|
keyCode == '+' ||
|
||||||
|
keyCode == WXK_NUMPAD_ADD ||
|
||||||
|
keyCode == '-' ||
|
||||||
|
keyCode == 390 ||
|
||||||
|
keyCode == WXK_DELETE ||
|
||||||
|
keyCode == WXK_BACK )
|
||||||
|
{
|
||||||
|
post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5445,7 +5460,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
|
||||||
|
|
|
@ -124,6 +124,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
|
||||||
|
|
||||||
class GLCanvas3D
|
class GLCanvas3D
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 },
|
||||||
|
@ -1415,13 +1415,18 @@ void ObjectList::update_opt_keys(t_config_option_keys& opt_keys)
|
||||||
|
|
||||||
void ObjectList::load_subobject(ModelVolumeType type)
|
void ObjectList::load_subobject(ModelVolumeType type)
|
||||||
{
|
{
|
||||||
auto item = GetSelection();
|
wxDataViewItem item = GetSelection();
|
||||||
if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0))
|
// we can add volumes for Object or Instance
|
||||||
|
if (!item || !(m_objects_model->GetItemType(item)&(itObject|itInstance)))
|
||||||
return;
|
return;
|
||||||
int obj_idx = m_objects_model->GetIdByItem(item);
|
const int obj_idx = m_objects_model->GetObjectIdByItem(item);
|
||||||
|
|
||||||
if (obj_idx < 0) return;
|
if (obj_idx < 0) return;
|
||||||
|
|
||||||
|
// Get object item, if Instance is selected
|
||||||
|
if (m_objects_model->GetItemType(item)&itInstance)
|
||||||
|
item = m_objects_model->GetItemById(obj_idx);
|
||||||
|
|
||||||
std::vector<std::pair<wxString, bool>> volumes_info;
|
std::vector<std::pair<wxString, bool>> volumes_info;
|
||||||
load_part((*m_objects)[obj_idx], volumes_info, type);
|
load_part((*m_objects)[obj_idx], volumes_info, type);
|
||||||
|
|
||||||
|
@ -1592,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") ?
|
||||||
|
@ -2150,9 +2155,11 @@ void ObjectList::update_selections()
|
||||||
if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings )
|
if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings )
|
||||||
{
|
{
|
||||||
const auto item = GetSelection();
|
const auto item = GetSelection();
|
||||||
if (selection.is_single_full_object() &&
|
if (selection.is_single_full_object()) {
|
||||||
m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx())
|
if ( m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx())
|
||||||
return;
|
return;
|
||||||
|
sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
|
||||||
|
}
|
||||||
if (selection.is_single_volume() || selection.is_any_modifier()) {
|
if (selection.is_single_volume() || selection.is_any_modifier()) {
|
||||||
const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin());
|
const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||||
if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx())
|
if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx())
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -240,6 +347,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ObjectManipulation::Show(const bool show)
|
void ObjectManipulation::Show(const bool show)
|
||||||
{
|
{
|
||||||
if (show != IsShown()) {
|
if (show != IsShown()) {
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -414,6 +414,12 @@ void Preview::msw_rescale()
|
||||||
refresh_print();
|
refresh_print();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Preview::move_double_slider(wxKeyEvent& evt)
|
||||||
|
{
|
||||||
|
if (m_slider)
|
||||||
|
m_slider->OnKeyDown(evt);
|
||||||
|
}
|
||||||
|
|
||||||
void Preview::bind_event_handlers()
|
void Preview::bind_event_handlers()
|
||||||
{
|
{
|
||||||
this->Bind(wxEVT_SIZE, &Preview::on_size, this);
|
this->Bind(wxEVT_SIZE, &Preview::on_size, this);
|
||||||
|
|
|
@ -122,6 +122,7 @@ public:
|
||||||
void refresh_print();
|
void refresh_print();
|
||||||
|
|
||||||
void msw_rescale();
|
void msw_rescale();
|
||||||
|
void move_double_slider(wxKeyEvent& evt);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model);
|
bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model);
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 +
|
||||||
|
@ -104,6 +104,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||||
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.
|
||||||
// Also the application closes much faster without these unnecessary screen refreshes.
|
// Also the application closes much faster without these unnecessary screen refreshes.
|
||||||
|
@ -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;
|
||||||
|
@ -976,6 +980,7 @@ void MainFrame::load_config(const DynamicPrintConfig& config)
|
||||||
if (! boost::algorithm::ends_with(opt_key, "_settings_id"))
|
if (! boost::algorithm::ends_with(opt_key, "_settings_id"))
|
||||||
tab->get_config()->option(opt_key)->set(config.option(opt_key));
|
tab->get_config()->option(opt_key)->set(config.option(opt_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
wxGetApp().load_current_presets();
|
wxGetApp().load_current_presets();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -1253,8 +1255,220 @@ 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Job(const Job&) = delete;
|
||||||
|
Job(Job&&) = default;
|
||||||
|
Job& operator=(const Job&) = delete;
|
||||||
|
Job& operator=(Job&&) = default;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
void process() override;
|
||||||
|
} rotoptimize_job{m_plater};
|
||||||
|
|
||||||
|
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
|
||||||
|
rotoptimize_job};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ExclusiveJobGroup(priv *_plater): m_plater(_plater) {}
|
||||||
|
|
||||||
|
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 +1643,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);
|
||||||
|
@ -1517,6 +1729,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||||
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
|
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
|
||||||
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values); });
|
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values); });
|
||||||
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
|
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
|
||||||
|
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
|
||||||
|
|
||||||
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
|
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
|
||||||
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
|
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
|
||||||
|
@ -1604,7 +1817,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
|
||||||
|
@ -1670,6 +1883,22 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||||
if (load_config && !config_loaded.empty()) {
|
if (load_config && !config_loaded.empty()) {
|
||||||
// Based on the printer technology field found in the loaded config, select the base for the config,
|
// Based on the printer technology field found in the loaded config, select the base for the config,
|
||||||
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
|
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
|
||||||
|
|
||||||
|
// We can't to load SLA project if there is at least one multi-part object on the bed
|
||||||
|
if (printer_technology == ptSLA)
|
||||||
|
{
|
||||||
|
const ModelObjectPtrs& objects = q->model().objects;
|
||||||
|
for (auto object : objects)
|
||||||
|
if (object->volumes.size() > 1)
|
||||||
|
{
|
||||||
|
Slic3r::GUI::show_info(nullptr,
|
||||||
|
_(L("You can't to load SLA project if there is at least one multi-part object on the bed")) + "\n\n" +
|
||||||
|
_(L("Please check your object list before preset changing.")),
|
||||||
|
_(L("Attention!")));
|
||||||
|
return obj_idxs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.apply(printer_technology == ptFFF ?
|
config.apply(printer_technology == ptFFF ?
|
||||||
static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
|
static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
|
||||||
static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
|
static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
|
||||||
|
@ -2125,59 +2354,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;
|
||||||
|
bed.points.reserve(bedpoints.size());
|
||||||
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
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();
|
||||||
|
|
||||||
|
@ -2193,81 +2408,60 @@ 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)
|
||||||
|
update_status(count - int(st), arrangestr);
|
||||||
|
},
|
||||||
|
[this]() { return was_canceled(); });
|
||||||
} catch (std::exception & /*e*/) {
|
} catch (std::exception & /*e*/) {
|
||||||
GUI::show_error(this->q, L("Could not arrange model objects! "
|
GUI::show_error(plater().q,
|
||||||
|
L("Could not arrange model objects! "
|
||||||
"Some geometries may be invalid."));
|
"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;
|
||||||
|
bed.points.reserve(bedpoints.size());
|
||||||
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
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;
|
double offs = mindist / 2.0 - EPSILON;
|
||||||
|
|
||||||
if(rotoptimizing) // wasn't canceled
|
if (!was_canceled()) // 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 trchull = o->convex_hull_2d(oi->get_transformation().get_matrix());
|
auto trchull = o->convex_hull_2d(
|
||||||
|
oi->get_transformation().get_matrix());
|
||||||
|
|
||||||
namespace opt = libnest2d::opt;
|
namespace opt = libnest2d::opt;
|
||||||
opt::StopCriteria stopcr;
|
opt::StopCriteria stopcr;
|
||||||
|
@ -2281,7 +2475,8 @@ void Plater::priv::sla_optimize_rotation() {
|
||||||
double binw = bin.size()(X) * SCALING_FACTOR - offs;
|
double binw = bin.size()(X) * SCALING_FACTOR - offs;
|
||||||
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
|
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
|
||||||
|
|
||||||
auto result = solver.optimize_min([&trchull, binw, binh](double rot){
|
auto result = solver.optimize_min(
|
||||||
|
[&trchull, binw, binh](double rot) {
|
||||||
auto chull = trchull;
|
auto chull = trchull;
|
||||||
chull.rotate(rot);
|
chull.rotate(rot);
|
||||||
|
|
||||||
|
@ -2297,25 +2492,32 @@ void Plater::priv::sla_optimize_rotation() {
|
||||||
if (hdiff > 0) diff += hdiff;
|
if (hdiff > 0) diff += hdiff;
|
||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
}, opt::initvals(0.0), opt::bound(-PI/2, PI/2));
|
},
|
||||||
|
opt::initvals(0.0),
|
||||||
|
opt::bound(-PI / 2, PI / 2));
|
||||||
|
|
||||||
double r = std::get<0>(result.optimum);
|
double r = std::get<0>(result.optimum);
|
||||||
|
|
||||||
Vec3d rt = oi->get_rotation(); rt(Z) += r;
|
Vec3d rt = oi->get_rotation();
|
||||||
|
rt(Z) += r;
|
||||||
oi->set_rotation(rt);
|
oi->set_rotation(rt);
|
||||||
}
|
|
||||||
|
|
||||||
arr::WipeTowerInfo wti; // useless in SLA context
|
arr::WipeTowerInfo wti; // useless in SLA context
|
||||||
arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti);
|
arr::find_new_position(plater().model,
|
||||||
|
o->instances,
|
||||||
|
coord_t(mindist / SCALING_FACTOR),
|
||||||
|
bed,
|
||||||
|
wti);
|
||||||
|
|
||||||
// Correct the z offset of the object which was corrupted be the rotation
|
// Correct the z offset of the object which was corrupted be
|
||||||
|
// the rotation
|
||||||
o->ensure_on_bed();
|
o->ensure_on_bed();
|
||||||
|
|
||||||
stfn(0, L("Orientation found."));
|
update_status(100, _(L("Orientation found.")));
|
||||||
statusbar()->set_range(prev_range);
|
}
|
||||||
statusbar()->set_cancel_callback();
|
else {
|
||||||
|
update_status(100, _(L("Orientation search canceled.")));
|
||||||
update(true);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::split_object()
|
void Plater::priv::split_object()
|
||||||
|
@ -2496,7 +2698,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;
|
||||||
}
|
}
|
||||||
|
@ -2727,7 +2929,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;
|
||||||
}
|
}
|
||||||
|
@ -3208,7 +3410,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3218,7 +3420,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3238,7 +3440,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
|
||||||
|
@ -3305,6 +3507,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3365,6 +3568,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); }
|
||||||
|
@ -3565,7 +3770,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>());
|
||||||
}
|
}
|
||||||
|
@ -3687,6 +3892,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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -2088,6 +2088,10 @@ void TabPrinter::build_sla()
|
||||||
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")), "" };
|
||||||
line.append_option(optgroup->get_option("fast_tilt_time"));
|
line.append_option(optgroup->get_option("fast_tilt_time"));
|
||||||
|
|
|
@ -586,7 +586,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
|
||||||
ItemAdded(parent_item, child);
|
ItemAdded(parent_item, child);
|
||||||
|
|
||||||
root->m_volumes_cnt++;
|
root->m_volumes_cnt++;
|
||||||
if (insert_position > 0) insert_position++;
|
if (insert_position >= 0) insert_position++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt);
|
const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(); %};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue