Add the full source of BambuStudio

using version 1.0.10
This commit is contained in:
lane.wei 2022-07-15 23:37:19 +08:00 committed by Lane.Wei
parent 30bcadab3e
commit 1555904bef
3771 changed files with 1251328 additions and 0 deletions

14
src/admesh/CMakeLists.txt Normal file
View file

@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 2.8.12)
project(admesh)
add_library(admesh STATIC
connect.cpp
normals.cpp
shared.cpp
stl.h
stl_io.cpp
stlinit.cpp
util.cpp
)
target_link_libraries(admesh PRIVATE boost_headeronly)

743
src/admesh/connect.cpp Normal file
View file

@ -0,0 +1,743 @@
/* ADMesh -- process triangulated solid meshes
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Questions, comments, suggestions, etc to
* https://github.com/admesh/admesh/issues
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <vector>
#include <boost/predef/other/endian.h>
#include <boost/log/trivial.hpp>
// Boost pool: Don't use mutexes to synchronize memory allocation.
#define BOOST_POOL_NO_MT
#include <boost/pool/object_pool.hpp>
#include "stl.h"
struct HashEdge {
// Key of a hash edge: sorted vertices of the edge.
uint32_t key[6];
// Compare two keys.
bool operator==(const HashEdge &rhs) const { return memcmp(key, rhs.key, sizeof(key)) == 0; }
bool operator!=(const HashEdge &rhs) const { 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;
HashEdge *next;
void load_exact(stl_file *stl, const stl_vertex *a, const stl_vertex *b)
{
{
stl_vertex diff = (*a - *b).cwiseAbs();
float max_diff = std::max(diff(0), std::max(diff(1), diff(2)));
stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge);
}
// Ensure identical vertex ordering of equal edges.
// This method is numerically robust.
if (vertex_lower(*a, *b)) {
} else {
// This edge is loaded backwards.
std::swap(a, b);
this->which_edge += 3;
}
memcpy(&this->key[0], a->data(), sizeof(stl_vertex));
memcpy(&this->key[3], b->data(), sizeof(stl_vertex));
// Switch negative zeros to positive zeros, so memcmp will consider them to be equal.
for (size_t i = 0; i < 6; ++ i) {
unsigned char *p = (unsigned char*)(this->key + i);
#if BOOST_ENDIAN_LITTLE_BYTE
if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80)
// Negative zero, switch to positive zero.
p[3] = 0;
#else /* BOOST_ENDIAN_LITTLE_BYTE */
if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0)
// Negative zero, switch to positive zero.
p[0] = 0;
#endif /* BOOST_ENDIAN_LITTLE_BYTE */
}
}
bool load_nearby(const stl_file *stl, const stl_vertex &a, const stl_vertex &b, float tolerance)
{
// Index of a grid cell spaced by tolerance.
typedef Eigen::Matrix<int32_t, 3, 1, Eigen::DontAlign> Vec3i;
Vec3i vertex1 = ((a - stl->stats.min) / tolerance).cast<int32_t>();
Vec3i vertex2 = ((b - stl->stats.min) / tolerance).cast<int32_t>();
static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect");
if (vertex1 == vertex2)
// Both vertices hash to the same value
return false;
// Ensure identical vertex ordering of edges, which vertices land into equal grid cells.
// This method is numerically robust.
if ((vertex1[0] != vertex2[0]) ?
(vertex1[0] < vertex2[0]) :
((vertex1[1] != vertex2[1]) ?
(vertex1[1] < vertex2[1]) :
(vertex1[2] < vertex2[2]))) {
memcpy(&this->key[0], vertex1.data(), sizeof(stl_vertex));
memcpy(&this->key[3], vertex2.data(), sizeof(stl_vertex));
} else {
memcpy(&this->key[0], vertex2.data(), sizeof(stl_vertex));
memcpy(&this->key[3], vertex1.data(), sizeof(stl_vertex));
this->which_edge += 3; /* this edge is loaded backwards */
}
return true;
}
private:
inline bool 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)));
}
};
struct HashTableEdges {
HashTableEdges(size_t number_of_faces) {
this->M = (int)hash_size_from_nr_faces(number_of_faces);
this->heads.assign(this->M, nullptr);
this->tail = pool.construct();
this->tail->next = this->tail;
for (int i = 0; i < this->M; ++ i)
this->heads[i] = this->tail;
}
~HashTableEdges() {
#ifndef NDEBUG
for (int i = 0; i < this->M; ++ i)
for (HashEdge *temp = this->heads[i]; temp != this->tail; temp = temp->next)
++ this->freed;
this->tail = nullptr;
#endif /* NDEBUG */
}
void insert_edge_exact(stl_file *stl, const HashEdge &edge)
{
this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { record_neighbors(stl, edge1, edge2); });
}
void insert_edge_nearby(stl_file *stl, const HashEdge &edge)
{
this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { match_neighbors_nearby(stl, edge1, edge2); });
}
// Hash table on edges
std::vector<HashEdge*> heads;
HashEdge* tail;
int M;
boost::object_pool<HashEdge> pool;
#ifndef NDEBUG
size_t malloced = 0;
size_t freed = 0;
size_t collisions = 0;
#endif /* NDEBUG */
private:
static inline size_t hash_size_from_nr_faces(const size_t nr_faces)
{
// Good primes for addressing a cca. 30 bit space.
// https://planetmath.org/goodhashtableprimes
static std::vector<uint32_t> primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 };
// Find a prime number for 50% filling of the shared triangle edges in the mesh.
auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1);
return (it == primes.end()) ? primes.back() : *it;
}
// MatchNeighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b)
template<typename MatchNeighbors>
void insert_edge(stl_file *stl, const HashEdge &edge, MatchNeighbors match_neighbors)
{
int chain_number = edge.hash(this->M);
HashEdge *link = this->heads[chain_number];
if (link == this->tail) {
// This list doesn't have any edges currently in it. Add this one.
HashEdge *new_edge = pool.construct(edge);
#ifndef NDEBUG
++ this->malloced;
#endif /* NDEBUG */
new_edge->next = this->tail;
this->heads[chain_number] = new_edge;
} else if (edges_equal(edge, *link)) {
// This is a match. Record result in neighbors list.
match_neighbors(edge, *link);
// Delete the matched edge from the list.
this->heads[chain_number] = link->next;
// pool.destroy(link);
#ifndef NDEBUG
++ this->freed;
#endif /* NDEBUG */
} else {
// Continue through the rest of the list.
for (;;) {
if (link->next == this->tail) {
// This is the last item in the list. Insert a new edge.
HashEdge *new_edge = pool.construct();
#ifndef NDEBUG
++ this->malloced;
#endif /* NDEBUG */
*new_edge = edge;
new_edge->next = this->tail;
link->next = new_edge;
#ifndef NDEBUG
++ this->collisions;
#endif /* NDEBUG */
break;
}
if (edges_equal(edge, *link->next)) {
// This is a match. Record result in neighbors list.
match_neighbors(edge, *link->next);
// Delete the matched edge from the list.
HashEdge *temp = link->next;
link->next = link->next->next;
// pool.destroy(temp);
#ifndef NDEBUG
++ this->freed;
#endif /* NDEBUG */
break;
}
// This is not a match. Go to the next link.
link = link->next;
#ifndef NDEBUG
++ this->collisions;
#endif /* NDEBUG */
}
}
}
// Edges equal for hashing. Edgesof different facet are allowed to be matched.
static inline bool edges_equal(const HashEdge &edge_a, const HashEdge &edge_b)
{
return edge_a.facet_number != edge_b.facet_number && edge_a == edge_b;
}
// Connect edge_a with edge_b, update edge connection statistics.
static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b)
{
// Facet a's neighbor is facet b
stl->neighbors_start[edge_a.facet_number].neighbor[edge_a.which_edge % 3] = edge_b.facet_number; /* sets the .neighbor part */
stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] = (edge_b.which_edge + 2) % 3; /* sets the .which_vertex_not part */
// Facet b's neighbor is facet a
stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */
stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */
if ((edge_a.which_edge < 3 && edge_b.which_edge < 3) || (edge_a.which_edge > 2 && edge_b.which_edge > 2)) {
// These facets are oriented in opposite directions, their normals are probably messed up.
stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3;
stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3;
}
// Count successful connects:
// Total connects:
stl->stats.connected_edges += 2;
// Count individual connects:
switch (stl->neighbors_start[edge_a.facet_number].num_neighbors()) {
case 1: ++ stl->stats.connected_facets_1_edge; break;
case 2: ++ stl->stats.connected_facets_2_edge; break;
case 3: ++ stl->stats.connected_facets_3_edge; break;
default: assert(false);
}
switch (stl->neighbors_start[edge_b.facet_number].num_neighbors()) {
case 1: ++ stl->stats.connected_facets_1_edge; break;
case 2: ++ stl->stats.connected_facets_2_edge; break;
case 3: ++ stl->stats.connected_facets_3_edge; break;
default: assert(false);
}
}
static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b)
{
record_neighbors(stl, edge_a, edge_b);
// Which vertices to change
int facet1 = -1;
int facet2 = -1;
int vertex1, vertex2;
stl_vertex new_vertex1, new_vertex2;
{
int v1a; // pair 1, facet a
int v1b; // pair 1, facet b
int v2a; // pair 2, facet a
int v2b; // pair 2, facet b
// Find first pair.
if (edge_a.which_edge < 3) {
v1a = edge_a.which_edge;
v2a = (edge_a.which_edge + 1) % 3;
} else {
v2a = edge_a.which_edge % 3;
v1a = (edge_a.which_edge + 1) % 3;
}
if (edge_b.which_edge < 3) {
v1b = edge_b.which_edge;
v2b = (edge_b.which_edge + 1) % 3;
} else {
v2b = edge_b.which_edge % 3;
v1b = (edge_b.which_edge + 1) % 3;
}
// Of the first pair, which vertex, if any, should be changed
if (stl->facet_start[edge_a.facet_number].vertex[v1a] != stl->facet_start[edge_b.facet_number].vertex[v1b]) {
// These facets are different.
if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v1a] == -1)
&& (stl->neighbors_start[edge_a.facet_number].neighbor[(v1a + 2) % 3] == -1)) {
// This vertex has no neighbors. This is a good one to change.
facet1 = edge_a.facet_number;
vertex1 = v1a;
new_vertex1 = stl->facet_start[edge_b.facet_number].vertex[v1b];
} else {
facet1 = edge_b.facet_number;
vertex1 = v1b;
new_vertex1 = stl->facet_start[edge_a.facet_number].vertex[v1a];
}
}
// Of the second pair, which vertex, if any, should be changed.
if (stl->facet_start[edge_a.facet_number].vertex[v2a] != stl->facet_start[edge_b.facet_number].vertex[v2b]) {
// These facets are different.
if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v2a] == -1)
&& (stl->neighbors_start[edge_a.facet_number].neighbor[(v2a + 2) % 3] == -1)) {
// This vertex has no neighbors. This is a good one to change.
facet2 = edge_a.facet_number;
vertex2 = v2a;
new_vertex2 = stl->facet_start[edge_b.facet_number].vertex[v2b];
} else {
facet2 = edge_b.facet_number;
vertex2 = v2b;
new_vertex2 = stl->facet_start[edge_a.facet_number].vertex[v2a];
}
}
}
auto change_vertices = [stl](int facet_num, int vnot, stl_vertex new_vertex)
{
int first_facet = facet_num;
bool direction = false;
for (;;) {
int pivot_vertex;
int next_edge;
if (vnot > 2) {
if (direction) {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot % 3;
}
else {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
}
direction = !direction;
}
else {
if (direction) {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
}
else {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot;
}
}
#if 0
if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) &&
stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) &&
stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2))
printf("Changing vertex %f,%f,%f: Same !!!\r\n", new_vertex(0), new_vertex(1), new_vertex(2));
else {
if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0))
printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n",
stl->facet_start[facet_num].vertex[pivot_vertex](0),
*reinterpret_cast<const int*>(&stl->facet_start[facet_num].vertex[pivot_vertex](0)),
new_vertex(0),
*reinterpret_cast<const int*>(&new_vertex(0)));
if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1))
printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n",
stl->facet_start[facet_num].vertex[pivot_vertex](1),
*reinterpret_cast<const int*>(&stl->facet_start[facet_num].vertex[pivot_vertex](1)),
new_vertex(1),
*reinterpret_cast<const int*>(&new_vertex(1)));
if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2))
printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n",
stl->facet_start[facet_num].vertex[pivot_vertex](2),
*reinterpret_cast<const int*>(&stl->facet_start[facet_num].vertex[pivot_vertex](2)),
new_vertex(2),
*reinterpret_cast<const int*>(&new_vertex(2)));
}
#endif
stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex;
vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge];
facet_num = stl->neighbors_start[facet_num].neighbor[next_edge];
if (facet_num == -1)
break;
if (facet_num == first_facet) {
// back to the beginning
BOOST_LOG_TRIVIAL(info) << "Back to the first facet changing vertices: probably a mobius part. Try using a smaller tolerance or don't do a nearby check.";
return;
}
}
};
if (facet1 != -1) {
int vnot1 = (facet1 == edge_a.facet_number) ?
(edge_a.which_edge + 2) % 3 :
(edge_b.which_edge + 2) % 3;
if (((vnot1 + 2) % 3) == vertex1)
vnot1 += 3;
change_vertices(facet1, vnot1, new_vertex1);
}
if (facet2 != -1) {
int vnot2 = (facet2 == edge_a.facet_number) ?
(edge_a.which_edge + 2) % 3 :
(edge_b.which_edge + 2) % 3;
if (((vnot2 + 2) % 3) == vertex2)
vnot2 += 3;
change_vertices(facet2, vnot2, new_vertex2);
}
stl->stats.edges_fixed += 2;
}
};
// This function builds the neighbors list. No modifications are made
// to any of the facets. The edges are said to match only if all six
// floats of the first edge matches all six floats of the second edge.
void stl_check_facets_exact(stl_file *stl)
{
assert(stl->facet_start.size() == stl->neighbors_start.size());
stl->stats.connected_edges = 0;
stl->stats.connected_facets_1_edge = 0;
stl->stats.connected_facets_2_edge = 0;
stl->stats.connected_facets_3_edge = 0;
// If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet.
// Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet
// will break the references.
for (uint32_t i = 0; i < stl->stats.number_of_facets;) {
stl_facet &facet = stl->facet_start[i];
if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) {
// Remove the degenerate facet.
facet = stl->facet_start[-- stl->stats.number_of_facets];
stl->facet_start.pop_back();
stl->neighbors_start.pop_back();
stl->stats.facets_removed += 1;
stl->stats.degenerate_facets += 1;
} else
++ i;
}
// Initialize hash table.
HashTableEdges hash_table(stl->stats.number_of_facets);
for (auto &neighbor : stl->neighbors_start)
neighbor.reset();
// Connect neighbor edges.
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
const stl_facet &facet = stl->facet_start[i];
for (int j = 0; j < 3; ++ j) {
HashEdge edge;
edge.facet_number = i;
edge.which_edge = j;
edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]);
hash_table.insert_edge_exact(stl, edge);
}
}
#if 0
printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n",
stl->stats.number_of_facets, stl->stats.number_of_facets * 3,
stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges);
#endif
}
void stl_check_facets_nearby(stl_file *stl, float tolerance)
{
assert(stl->stats.connected_facets_3_edge <= stl->stats.connected_facets_2_edge);
assert(stl->stats.connected_facets_2_edge <= stl->stats.connected_facets_1_edge);
assert(stl->stats.connected_facets_1_edge <= stl->stats.number_of_facets);
if (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)
// No need to check any further. All facets are connected.
return;
HashTableEdges hash_table(stl->stats.number_of_facets);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
//FIXME is the copy necessary?
stl_facet facet = stl->facet_start[i];
for (int j = 0; j < 3; j++) {
if (stl->neighbors_start[i].neighbor[j] == -1) {
HashEdge edge;
edge.facet_number = i;
edge.which_edge = j;
if (edge.load_nearby(stl, facet.vertex[j], facet.vertex[(j + 1) % 3], tolerance))
// Only insert edges that have different keys.
hash_table.insert_edge_nearby(stl, edge);
}
}
}
}
void stl_remove_unconnected_facets(stl_file *stl)
{
// A couple of things need to be done here. One is to remove any completely unconnected facets (0 edges connected) since these are
// useless and could be completely wrong. The second thing that needs to be done is to remove any degenerate facets that were created during
// stl_check_facets_nearby().
auto remove_facet = [stl](int facet_number)
{
++ stl->stats.facets_removed;
/* Update list of connected edges */
stl_neighbors &neighbors = stl->neighbors_start[facet_number];
// Update statistics on unconnected triangle edges.
switch (neighbors.num_neighbors()) {
case 3: -- stl->stats.connected_facets_3_edge; // fall through
case 2: -- stl->stats.connected_facets_2_edge; // fall through
case 1: -- stl->stats.connected_facets_1_edge; // fall through
case 0: break;
default: assert(false);
}
if (facet_number < int(-- stl->stats.number_of_facets)) {
// Removing a face, which was not the last one.
// Copy the face and neighborship from the last face to facet_number.
stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets];
neighbors = stl->neighbors_start[stl->stats.number_of_facets];
// Update neighborship of faces, which used to point to the last face, now moved to facet_number.
for (int i = 0; i < 3; ++ i)
if (neighbors.neighbor[i] != -1) {
int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3];
if (other_face_idx != stl->stats.number_of_facets) {
BOOST_LOG_TRIVIAL(info) << "in remove_facet: neighbor = " << other_face_idx << " numfacets = " << stl->stats.number_of_facets << " this is wrong";
return;
}
other_face_idx = facet_number;
}
}
stl->facet_start.pop_back();
stl->neighbors_start.pop_back();
};
auto remove_degenerate = [stl, remove_facet](int facet)
{
// Update statistics on face connectivity after one edge was disconnected on the facet "facet_num".
auto update_connects_remove_1 = [stl](int facet_num) {
switch (stl->neighbors_start[facet_num].num_neighbors()) {
case 0: assert(false); break;
case 1: -- stl->stats.connected_facets_1_edge; break;
case 2: -- stl->stats.connected_facets_2_edge; break;
case 3: -- stl->stats.connected_facets_3_edge; break;
default: assert(false);
}
};
int edge_to_collapse = 0;
if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) {
if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) {
// All 3 vertices are equal. Collapse the edge with no neighbor if it exists.
const int *nbr = stl->neighbors_start[facet].neighbor;
edge_to_collapse = (nbr[0] == -1) ? 0 : (nbr[1] == -1) ? 1 : 2;
} else {
edge_to_collapse = 0;
}
} else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) {
edge_to_collapse = 1;
} else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) {
edge_to_collapse = 2;
} else {
// No degenerate. Function shouldn't have been called.
return;
}
int edge[3] = { (edge_to_collapse + 1) % 3, (edge_to_collapse + 2) % 3, edge_to_collapse };
int neighbor[] = {
stl->neighbors_start[facet].neighbor[edge[0]],
stl->neighbors_start[facet].neighbor[edge[1]],
stl->neighbors_start[facet].neighbor[edge[2]]
};
int vnot[] = {
stl->neighbors_start[facet].which_vertex_not[edge[0]],
stl->neighbors_start[facet].which_vertex_not[edge[1]],
stl->neighbors_start[facet].which_vertex_not[edge[2]]
};
// Update statistics on edge connectivity.
if ((neighbor[0] == -1) && (neighbor[1] != -1))
update_connects_remove_1(neighbor[1]);
if ((neighbor[1] == -1) && (neighbor[0] != -1))
update_connects_remove_1(neighbor[0]);
if (neighbor[0] >= 0) {
if (neighbor[1] >= 0) {
// Adjust the "flip" flag for the which_vertex_not values.
if (vnot[0] > 2) {
if (vnot[1] > 2) {
// The face to be removed has its normal flipped compared to the left & right neighbors, therefore after removing this face
// the two remaining neighbors will be oriented correctly.
vnot[0] -= 3;
vnot[1] -= 3;
} else
// One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally.
// After removal, the two neighbors will have their normals flipped.
vnot[1] += 3;
} else if (vnot[1] > 2)
// One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally.
// After removal, the two neighbors will have their normals flipped.
vnot[0] += 3;
}
stl->neighbors_start[neighbor[0]].neighbor[(vnot[0] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[1];
stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = vnot[1];
}
if (neighbor[1] >= 0) {
stl->neighbors_start[neighbor[1]].neighbor[(vnot[1] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[0];
stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0];
}
if (neighbor[2] >= 0) {
update_connects_remove_1(neighbor[2]);
stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1;
}
remove_facet(facet);
};
// remove degenerate facets
for (uint32_t i = 0; i < stl->stats.number_of_facets;)
if (stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] ||
stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] ||
stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) {
remove_degenerate(i);
// assert(stl_validate(stl));
} else
++ i;
if (stl->stats.connected_facets_1_edge < (int)stl->stats.number_of_facets) {
// There are some faces with no connected edge at all. Remove completely unconnected facets.
for (uint32_t i = 0; i < stl->stats.number_of_facets;)
if (stl->neighbors_start[i].num_neighbors() == 0) {
// This facet is completely unconnected. Remove it.
remove_facet(i);
assert(stl_validate(stl));
} else
++ i;
}
}
void stl_fill_holes(stl_file *stl)
{
// Insert all unconnected edges into hash list.
HashTableEdges hash_table(stl->stats.number_of_facets);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
stl_facet facet = stl->facet_start[i];
for (int j = 0; j < 3; ++ j) {
if(stl->neighbors_start[i].neighbor[j] != -1)
continue;
HashEdge edge;
edge.facet_number = i;
edge.which_edge = j;
edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]);
hash_table.insert_edge_exact(stl, edge);
}
}
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
stl_facet facet = stl->facet_start[i];
int neighbors_initial[3] = { stl->neighbors_start[i].neighbor[0], stl->neighbors_start[i].neighbor[1], stl->neighbors_start[i].neighbor[2] };
int first_facet = i;
for (int j = 0; j < 3; ++ j) {
if (stl->neighbors_start[i].neighbor[j] != -1)
continue;
stl_facet new_facet;
new_facet.vertex[0] = facet.vertex[j];
new_facet.vertex[1] = facet.vertex[(j + 1) % 3];
bool direction = neighbors_initial[(j + 2) % 3] == -1;
int facet_num = i;
int vnot = (j + 2) % 3;
for (;;) {
int pivot_vertex = 0;
int next_edge = 0;
if (vnot > 2) {
if (direction) {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot % 3;
} else {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
}
direction = ! direction;
} else {
if(direction == 0) {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot;
} else {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
}
}
int next_facet = stl->neighbors_start[facet_num].neighbor[next_edge];
if (next_facet == -1) {
new_facet.vertex[2] = stl->facet_start[facet_num].vertex[vnot % 3];
stl_add_facet(stl, &new_facet);
for (int k = 0; k < 3; ++ k) {
HashEdge edge;
edge.facet_number = stl->stats.number_of_facets - 1;
edge.which_edge = k;
edge.load_exact(stl, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]);
hash_table.insert_edge_exact(stl, edge);
}
break;
}
vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge];
facet_num = next_facet;
if (facet_num == first_facet) {
// back to the beginning
BOOST_LOG_TRIVIAL(info) << "Back to the first facet filling holes: probably a mobius part. Try using a smaller tolerance or don't do a nearby check.";
return;
}
}
}
}
}
void stl_add_facet(stl_file *stl, const stl_facet *new_facet)
{
assert(stl->facet_start.size() == stl->stats.number_of_facets);
assert(stl->neighbors_start.size() == stl->stats.number_of_facets);
stl->facet_start.emplace_back(*new_facet);
// note that the normal vector is not set here, just initialized to 0.
stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero();
stl->neighbors_start.emplace_back();
++ stl->stats.facets_added;
++ stl->stats.number_of_facets;
}

239
src/admesh/normals.cpp Normal file
View file

@ -0,0 +1,239 @@
/* ADMesh -- process triangulated solid meshes
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Questions, comments, suggestions, etc to
* https://github.com/admesh/admesh/issues
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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"
static void reverse_facet(stl_file *stl, int facet_num)
{
++ stl->stats.facets_reversed;
int neighbor[3] = { stl->neighbors_start[facet_num].neighbor[0], stl->neighbors_start[facet_num].neighbor[1], stl->neighbors_start[facet_num].neighbor[2] };
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] };
// reverse the facet
stl_vertex tmp_vertex = stl->facet_start[facet_num].vertex[0];
stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1];
stl->facet_start[facet_num].vertex[1] = tmp_vertex;
// fix the vnots of the neighboring facets
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] + 3) % 6;
if (neighbor[1] != -1)
stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = (stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6;
if (neighbor[2] != -1)
stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = (stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6;
// swap the neighbors of the facet that is being reversed
stl->neighbors_start[facet_num].neighbor[1] = neighbor[2];
stl->neighbors_start[facet_num].neighbor[2] = neighbor[1];
// swap the vnots of the facet that is being reversed
stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2];
stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1];
// reverse the values of the vnots of the facet that is being reversed
stl->neighbors_start[facet_num].which_vertex_not[0] = (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6;
stl->neighbors_start[facet_num].which_vertex_not[1] = (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6;
stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6;
}
// Returns true if the normal was flipped.
static bool check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag)
{
stl_facet *facet = &stl->facet_start[facet_num];
stl_normal normal;
stl_calculate_normal(normal, facet);
stl_normalize_vector(normal);
stl_normal normal_dif = (normal - facet->normal).cwiseAbs();
const float eps = 0.001f;
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
// Normal is within tolerance. It is not really necessary to change the values here, but just for consistency, I will.
facet->normal = normal;
return false;
}
stl_normal test_norm = facet->normal;
stl_normalize_vector(test_norm);
normal_dif = (normal - test_norm).cwiseAbs();
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
// The normal is not within tolerance, but direction is OK.
if (normal_fix_flag) {
facet->normal = normal;
++ stl->stats.normals_fixed;
}
return false;
}
test_norm *= -1.f;
normal_dif = (normal - test_norm).cwiseAbs();
if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
// The normal is not within tolerance and backwards.
if (normal_fix_flag) {
facet->normal = normal;
++ stl->stats.normals_fixed;
}
return true;
}
if (normal_fix_flag) {
facet->normal = normal;
++ stl->stats.normals_fixed;
}
// Status is unknown.
return false;
}
void stl_fix_normal_directions(stl_file *stl)
{
// This may happen for malformed models
if (stl->stats.number_of_facets == 0)
return;
struct stl_normal {
int facet_num;
stl_normal *next;
};
// Initialize linked list.
boost::object_pool<stl_normal> pool;
stl_normal *head = pool.construct();
stl_normal *tail = pool.construct();
head->next = tail;
tail->next = tail;
// 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;
reversed_ids.reserve(stl->stats.number_of_facets);
int 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 (check_normal_vector(stl, 0, 0)) {
reverse_facet(stl, 0);
reversed_ids.emplace_back(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 = int(reversed_ids.size()) - 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.emplace_back(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;
assert(facet_num < stl->stats.number_of_facets);
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 >= int(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.emplace_back(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)
{
stl_normal normal;
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
reverse_facet(stl, i);
stl_calculate_normal(normal, &stl->facet_start[i]);
stl_normalize_vector(normal);
stl->facet_start[i].normal = normal;
}
}

263
src/admesh/shared.cpp Normal file
View file

@ -0,0 +1,263 @@
/* ADMesh -- process triangulated solid meshes
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Questions, comments, suggestions, etc to
* https://github.com/admesh/admesh/issues
*/
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include "stl.h"
#include "libslic3r/LocalesUtils.hpp"
void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its)
{
// 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);
// A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop
// while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal
// are marked with a unique fan_traversal_stamp.
unsigned int fan_traversal_stamp = 0;
std::vector<unsigned int> fan_traversal_facet_visited(stl->stats.number_of_facets, 0);
for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) {
for (int j = 0; j < 3; ++ j) {
if (its.indices[facet_idx][j] != -1)
// Shared vertex was already assigned.
continue;
// Create a new shared vertex.
its.vertices.emplace_back(stl->facet_start[facet_idx].vertex[j]);
// Traverse the fan around the j-th vertex of the i-th face, assign the newly created shared vertex index to all the neighboring triangles in the triangle fan.
int facet_in_fan_idx = facet_idx;
bool edge_direction = false;
bool traversal_reversed = false;
int vnot = (j + 2) % 3;
// Increase the
++ fan_traversal_stamp;
for (;;) {
// Next edge on facet_in_fan_idx to be traversed. The edge is indexed by its starting vertex index.
int next_edge = 0;
// Vertex index in facet_in_fan_idx, which is being pivoted around, and which is being assigned a new shared vertex.
int pivot_vertex = 0;
if (vnot > 2) {
// The edge of facet_in_fan_idx opposite to vnot is equally oriented, therefore
// the neighboring facet is flipped.
if (! edge_direction) {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
} else {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot % 3;
}
edge_direction = ! edge_direction;
} else {
// The neighboring facet is correctly oriented.
if (! edge_direction) {
pivot_vertex = (vnot + 1) % 3;
next_edge = vnot;
} else {
pivot_vertex = (vnot + 2) % 3;
next_edge = pivot_vertex;
}
}
its.indices[facet_in_fan_idx][pivot_vertex] = its.vertices.size() - 1;
fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp;
// next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge!
int next_facet = stl->neighbors_start[facet_in_fan_idx].neighbor[next_edge];
if (next_facet == -1) {
// No neighbor going in the current direction.
if (traversal_reversed) {
// Went to one limit, then turned back and reached the other limit. Quit the fan traversal.
break;
} else {
// Reached the first limit. Now try to reverse and traverse up to the other limit.
edge_direction = true;
vnot = (j + 1) % 3;
traversal_reversed = true;
facet_in_fan_idx = facet_idx;
}
} else if (next_facet == facet_idx) {
// Traversed a closed fan all around.
// assert(! traversal_reversed);
break;
} else if (next_facet >= (int)stl->stats.number_of_facets) {
// The mesh is not valid!
// assert(false);
break;
} else if (fan_traversal_facet_visited[next_facet] == fan_traversal_stamp) {
// Traversed a closed fan all around, but did not reach the starting face.
// This indicates an invalid geometry (non-manifold).
//assert(false);
break;
} else {
// Continue traversal.
// next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge!
vnot = stl->neighbors_start[facet_in_fan_idx].which_vertex_not[next_edge];
facet_in_fan_idx = next_facet;
}
}
}
}
}
bool its_write_off(const indexed_triangle_set &its, const char *file)
{
Slic3r::CNumericLocalesSetter locales_setter;
/* Open the file */
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing";
return false;
}
fprintf(fp, "OFF\n");
fprintf(fp, "%d %d 0\n", (int)its.vertices.size(), (int)its.indices.size());
for (int i = 0; i < its.vertices.size(); ++ i)
fprintf(fp, "\t%f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2));
for (uint32_t i = 0; i < its.indices.size(); ++ i)
fprintf(fp, "\t3 %d %d %d\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
fclose(fp);
return true;
}
bool its_write_vrml(const indexed_triangle_set &its, const char *file)
{
Slic3r::CNumericLocalesSetter locales_setter;
/* Open the file */
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_vrml: Couldn't open " << file << " for writing";
return false;
}
fprintf(fp, "#VRML V1.0 ascii\n\n");
fprintf(fp, "Separator {\n");
fprintf(fp, "\tDEF STLShape ShapeHints {\n");
fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n");
fprintf(fp, "\t\tfaceType CONVEX\n");
fprintf(fp, "\t\tshapeType SOLID\n");
fprintf(fp, "\t\tcreaseAngle 0.0\n");
fprintf(fp, "\t}\n");
fprintf(fp, "\tDEF STLModel Separator {\n");
fprintf(fp, "\t\tDEF STLColor Material {\n");
fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n");
fprintf(fp, "\t\t}\n");
fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n");
fprintf(fp, "\t\t\tpoint [\n");
int i = 0;
for (; i + 1 < its.vertices.size(); ++ i)
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}\n");
fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n");
fprintf(fp, "\t\t\tcoordIndex [\n");
for (size_t i = 0; i + 1 < its.indices.size(); ++ i)
fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]);
fprintf(fp, "\t\t}\n");
fprintf(fp, "\t}\n");
fprintf(fp, "}\n");
fclose(fp);
return true;
}
bool its_write_obj(const indexed_triangle_set &its, const char *file)
{
Slic3r::CNumericLocalesSetter locales_setter;
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing";
return false;
}
for (size_t i = 0; i < its.vertices.size(); ++ i)
fprintf(fp, "v %f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2));
for (size_t i = 0; i < its.indices.size(); ++ i)
fprintf(fp, "f %d %d %d\n", its.indices[i][0]+1, its.indices[i][1]+1, its.indices[i][2]+1);
fclose(fp);
return true;
}
// 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);
}

410
src/admesh/stl.h Normal file
View file

@ -0,0 +1,410 @@
/* ADMesh -- process triangulated solid meshes
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Questions, comments, suggestions, etc to
* https://github.com/admesh/admesh/issues
*/
#ifndef __admesh_stl__
#define __admesh_stl__
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <vector>
#include <Eigen/Geometry>
// Size of the binary STL header, free form.
#define LABEL_SIZE 80
// Binary STL, length of the "number of faces" counter.
#define NUM_FACET_SIZE 4
// Binary STL, sizeof header + number of faces.
#define HEADER_SIZE 84
#define STL_MIN_FILE_SIZE 284
#define ASCII_LINES_PER_FACET 7
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_vertex;
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_normal) == 12, "size of stl_normal incorrect");
typedef enum {
eNormal, // normal face
eSmallOverhang, // small overhang
eSmallHole, // face with small hole
eExteriorAppearance, // exterior appearance
eMaxNumFaceTypes
}EnumFaceTypes;
struct stl_facet {
stl_normal normal;
stl_vertex vertex[3];
char extra[2];
stl_facet rotated(const Eigen::Quaternion<float, Eigen::DontAlign> &rot) const {
stl_facet out;
out.normal = rot * this->normal;
out.vertex[0] = rot * this->vertex[0];
out.vertex[1] = rot * this->vertex[1];
out.vertex[2] = rot * this->vertex[2];
return out;
}
};
#define SIZEOF_STL_FACET 50
static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset");
static_assert(offsetof(stl_facet, vertex) == 12, "stl_facet.vertex has correct offset");
static_assert(offsetof(stl_facet, extra ) == 48, "stl_facet.extra has correct offset");
static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrect");
typedef enum {binary, ascii, inmemory} stl_type;
struct stl_neighbors {
stl_neighbors() { reset(); }
void reset() {
neighbor[0] = -1;
neighbor[1] = -1;
neighbor[2] = -1;
which_vertex_not[0] = -1;
which_vertex_not[1] = -1;
which_vertex_not[2] = -1;
}
int num_neighbors() const { return 3 - ((this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1)); }
// Index of a neighbor facet.
int neighbor[3];
// Index of an opposite vertex at the neighbor face.
char which_vertex_not[3];
};
struct stl_stats {
stl_stats() { memset(&header, 0, 81); }
char header[81];
stl_type type = (stl_type)0;
// Should always match the number of facets stored inside stl_file::facet_start.
uint32_t number_of_facets = 0;
// Bounding box.
stl_vertex max = stl_vertex::Zero();
stl_vertex min = stl_vertex::Zero();
stl_vertex size = stl_vertex::Zero();
float bounding_diameter = 0.f;
float shortest_edge = 0.f;
// After repair, the volume shall always be positive.
float volume = -1.f;
// Number of face edges connected to another face.
// Don't use this statistics after repair, use the connected_facets_1/2/3_edge instead!
int connected_edges = 0;
// Faces with >=1, >=2 and 3 edges connected to another face.
int connected_facets_1_edge = 0;
int connected_facets_2_edge = 0;
int connected_facets_3_edge = 0;
// Faces with 1, 2 and 3 open edges after exact chaining, but before repair.
int facets_w_1_bad_edge = 0;
int facets_w_2_bad_edge = 0;
int facets_w_3_bad_edge = 0;
// Number of faces read form an STL file.
int original_num_facets = 0;
// Number of edges connected one to another by snapping their end vertices.
int edges_fixed = 0;
// Number of faces removed because they were degenerated.
int degenerate_facets = 0;
// Total number of facets removed: Degenerate faces and unconnected faces.
int facets_removed = 0;
// Number of faces added by hole filling.
int facets_added = 0;
// Number of faces reversed because of negative volume or because one patch was connected to another patch with incompatible normals.
int facets_reversed = 0;
// Number of incompatible edges remaining after the patches were connected together and possibly their normals flipped.
int backwards_edges = 0;
// Number of triangles, which were flipped during the fixing process.
int normals_fixed = 0;
// Number of connected triangle patches.
int number_of_parts = 0;
void clear() { *this = stl_stats(); }
};
struct stl_file {
stl_file() {}
void clear() {
this->facet_start.clear();
this->neighbors_start.clear();
this->stats.clear();
}
size_t memsize() const {
return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size();
}
std::vector<stl_facet> facet_start;
std::vector<stl_neighbors> neighbors_start;
// Statistics
stl_stats stats;
};
struct FaceProperty
{ // triangle face property
EnumFaceTypes type;
double area;
// stl_normal normal;
std::string to_string() const
{
std::string str;
// skip normal type facet to improve performance
if (type > eNormal && type < eMaxNumFaceTypes) {
str += std::to_string(type);
if (area != 0.f)
str += " " + std::to_string(area);
}
return str;
}
void from_string(const std::string& str)
{
std::string val_str, area_str;
do {
if (str.empty())
break;
this->type = (EnumFaceTypes)std::atoi(str.c_str());
if (this->type <= eNormal || this->type >= eMaxNumFaceTypes)
break;
size_t type_end_pos = str.find(" ");
if (type_end_pos == std::string::npos) {
this->area = 0.f;
return;
}
area_str = str.substr(type_end_pos + 1);
if (!area_str.empty())
this->area = std::atof(area_str.c_str());
else
this->area = 0.f;
return;
} while (0);
this->type = eNormal;
this->area = 0.f;
}
};
struct indexed_triangle_set
{
indexed_triangle_set(std::vector<stl_triangle_vertex_indices> indices_,
std::vector<stl_vertex> vertices_) :indices(indices_), vertices(vertices_) {
properties.resize(indices_.size());
}
indexed_triangle_set() {}
void clear() { indices.clear(); vertices.clear(); properties.clear(); }
size_t memsize() const {
return sizeof(*this) + (sizeof(stl_triangle_vertex_indices) + sizeof(FaceProperty)) * indices.size() + sizeof(stl_vertex) * vertices.size();
}
std::vector<stl_triangle_vertex_indices> indices;
std::vector<stl_vertex> vertices;
std::vector<FaceProperty> properties;
bool empty() const { return indices.empty() || vertices.empty(); }
stl_vertex get_vertex(int facet_idx, int vertex_idx) const{
return vertices[indices[facet_idx][vertex_idx]];
}
float facet_area(int facet_idx) const {
return std::abs((get_vertex(facet_idx, 0) - get_vertex(facet_idx, 1))
.cross(get_vertex(facet_idx, 0) - get_vertex(facet_idx, 2)).norm()) / 2;
}
FaceProperty& get_property(int face_idx) {
if (properties.size() != indices.size()) {
properties.clear();
properties.resize(indices.size());
}
return properties[face_idx];
}
};
extern bool stl_open(stl_file *stl, const char *file);
extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file);
extern bool stl_print_neighbors(stl_file *stl, char *file);
extern bool stl_write_ascii(stl_file *stl, const char *file, const char *label);
extern bool stl_write_binary(stl_file *stl, const char *file, const char *label);
extern void stl_check_facets_exact(stl_file *stl);
extern void stl_check_facets_nearby(stl_file *stl, float tolerance);
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_facet(stl_file *stl, char *label, int facet);
extern void stl_write_neighbor(stl_file *stl, int facet);
extern bool stl_write_quad_object(stl_file *stl, char *file);
extern void stl_verify_neighbors(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_values(stl_file *stl);
extern void stl_reverse_all_facets(stl_file *stl);
extern void stl_translate(stl_file *stl, float x, float y, float z);
extern void stl_translate_relative(stl_file *stl, float x, float y, float z);
extern void stl_scale_versor(stl_file *stl, const stl_vertex &versor);
inline void stl_scale(stl_file *stl, float factor) { stl_scale_versor(stl, stl_vertex(factor, factor, factor)); }
extern void stl_rotate_x(stl_file *stl, float angle);
extern void stl_rotate_y(stl_file *stl, float angle);
extern void stl_rotate_z(stl_file *stl, float angle);
extern void stl_mirror_xy(stl_file *stl);
extern void stl_mirror_yz(stl_file *stl);
extern void stl_mirror_xz(stl_file *stl);
extern float get_area(stl_facet* facet);
extern void stl_get_size(stl_file *stl);
// the following function is not used
/*
template<typename T>
extern void stl_transform(stl_file *stl, T *trafo3x4)
{
Eigen::Matrix<T, 3, 3, Eigen::DontAlign> trafo3x3;
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
trafo3x3(i, j) = (i * 4) + j;
}
}
Eigen::Matrix<T, 3, 3, Eigen::DontAlign> r = trafo3x3.inverse().transpose();
for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) {
stl_facet &face = stl->facet_start[i_face];
for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) {
stl_vertex &v_dst = face.vertex[i_vertex];
stl_vertex v_src = v_dst;
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]);
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]);
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]);
}
face.normal = (r * face.normal.template cast<T>()).template cast<float>().eval();
}
stl_get_size(stl);
}
*/
template<typename T>
inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t)
{
const Eigen::Matrix<T, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0).inverse().transpose();
for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) {
stl_facet &f = stl->facet_start[i];
for (size_t j = 0; j < 3; ++j)
f.vertex[j] = (t * f.vertex[j].template cast<T>()).template cast<float>().eval();
f.normal = (r * f.normal.template cast<T>()).template cast<float>().eval();
}
stl_get_size(stl);
}
template<typename T>
inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m)
{
const Eigen::Matrix<T, 3, 3, Eigen::DontAlign> r = m.inverse().transpose();
for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) {
stl_facet &f = stl->facet_start[i];
for (size_t j = 0; j < 3; ++j)
f.vertex[j] = (m * f.vertex[j].template cast<T>()).template cast<float>().eval();
f.normal = (r * f.normal.template cast<T>()).template cast<float>().eval();
}
stl_get_size(stl);
}
template<typename V>
inline void its_translate(indexed_triangle_set &its, const V v)
{
for (stl_vertex &v_dst : its.vertices)
v_dst += v;
}
template<typename T>
inline void its_transform(indexed_triangle_set &its, T *trafo3x4)
{
for (stl_vertex &v_dst : its.vertices) {
stl_vertex v_src = v_dst;
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]);
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]);
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]);
}
}
template<typename T>
inline void its_transform(indexed_triangle_set &its, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t, bool fix_left_handed = false)
{
//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();
if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.)
for (stl_triangle_vertex_indices &i : its.indices)
std::swap(i[0], i[1]);
}
template<typename T>
inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m, bool fix_left_handed = false)
{
for (stl_vertex &v : its.vertices)
v = (m * v.template cast<T>()).template cast<float>().eval();
if (fix_left_handed && m.determinant() < 0.)
for (stl_triangle_vertex_indices &i : its.indices)
std::swap(i[0], i[1]);
}
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) {
normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]);
}
inline void stl_normalize_vector(stl_normal &normal) {
double length = normal.cast<double>().norm();
if (length < 0.000000000001)
normal = stl_normal::Zero();
else
normal *= float(1.0 / length);
}
extern void stl_calculate_volume(stl_file *stl);
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_allocate(stl_file *stl);
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_reallocate(stl_file *stl);
extern void stl_add_facet(stl_file *stl, const stl_facet *new_facet);
// Validate the mesh, assert on error.
extern bool stl_validate(const stl_file *stl);
extern bool stl_validate(const stl_file *stl, const indexed_triangle_set &its);
#endif

251
src/admesh/stl_io.cpp Normal file
View file

@ -0,0 +1,251 @@
/* ADMesh -- process triangulated solid meshes
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Questions, comments, suggestions, etc to
* https://github.com/admesh/admesh/issues
*/
#include <stdlib.h>
#include <string.h>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/predef/other/endian.h>
#include "stl.h"
void stl_stats_out(stl_file *stl, FILE *file, char *input_file)
{
// This is here for Slic3r, without our config.h it won't use this part of the code anyway.
#ifndef VERSION
#define VERSION "unknown"
#endif
fprintf(file, "\n================= Results produced by ADMesh version " VERSION " ================\n");
fprintf(file, "Input file : %s\n", input_file);
if (stl->stats.type == binary)
fprintf(file, "File type : Binary STL file\n");
else
fprintf(file, "File type : ASCII STL file\n");
fprintf(file, "Header : %s\n", stl->stats.header);
fprintf(file, "============== Size ==============\n");
fprintf(file, "Min X = % f, Max X = % f\n", 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 Z = % f, Max Z = % f\n", stl->stats.min(2), stl->stats.max(2));
fprintf(file, "========= Facet Status ========== Original ============ Final ====\n");
fprintf(file, "Number of facets : %5d %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets);
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, "Facets with 2 disconnected edges : %5d %5d\n",
stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge);
fprintf(file, "Facets with 3 disconnected edges : %5d %5d\n",
stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge);
fprintf(file, "Total disconnected facets : %5d %5d\n",
stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_3_edge);
fprintf(file, "=== Processing Statistics === ===== Other Statistics =====\n");
fprintf(file, "Number of parts : %5d Volume : %f\n", stl->stats.number_of_parts, stl->stats.volume);
fprintf(file, "Degenerate facets : %5d\n", stl->stats.degenerate_facets);
fprintf(file, "Edges fixed : %5d\n", stl->stats.edges_fixed);
fprintf(file, "Facets removed : %5d\n", stl->stats.facets_removed);
fprintf(file, "Facets added : %5d\n", stl->stats.facets_added);
fprintf(file, "Facets reversed : %5d\n", stl->stats.facets_reversed);
fprintf(file, "Backwards edges : %5d\n", stl->stats.backwards_edges);
fprintf(file, "Normals fixed : %5d\n", stl->stats.normals_fixed);
}
bool stl_write_ascii(stl_file *stl, const char *file, const char *label)
{
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing";
return false;
}
fprintf(fp, "solid %s\n", label);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
fprintf(fp, " facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), stl->facet_start[i].normal(2));
fprintf(fp, " outer loop\n");
fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2));
fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2));
fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2));
fprintf(fp, " endloop\n");
fprintf(fp, " endfacet\n");
}
fprintf(fp, "endsolid %s\n", label);
fclose(fp);
return true;
}
bool stl_print_neighbors(stl_file *stl, char *file)
{
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_print_neighbors: Couldn't open " << file << " for writing";
return false;
}
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n",
i,
stl->neighbors_start[i].neighbor[0],
(int)stl->neighbors_start[i].which_vertex_not[0],
stl->neighbors_start[i].neighbor[1],
(int)stl->neighbors_start[i].which_vertex_not[1],
stl->neighbors_start[i].neighbor[2],
(int)stl->neighbors_start[i].which_vertex_not[2]);
}
fclose(fp);
return true;
}
#if BOOST_ENDIAN_BIG_BYTE
// 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)
{
for (size_t i = 0; i < cnt; i += 4) {
std::swap(buf[i], buf[i+3]);
std::swap(buf[i+1], buf[i+2]);
}
}
#endif
bool stl_write_binary(stl_file *stl, const char *file, const char *label)
{
FILE *fp = boost::nowide::fopen(file, "wb");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_binary: Couldn't open " << file << " for writing";
return false;
}
fprintf(fp, "%s", label);
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);
#if BOOST_ENDIAN_LITTLE_BYTE
fwrite(&stl->stats.number_of_facets, 4, 1, fp);
for (const stl_facet &facet : stl->facet_start)
fwrite(&facet, SIZEOF_STL_FACET, 1, fp);
#else /* BOOST_ENDIAN_LITTLE_BYTE */
char buffer[50];
// Convert the number of facets to little endian.
memcpy(buffer, &stl->stats.number_of_facets, 4);
stl_internal_reverse_quads(buffer, 4);
fwrite(buffer, 4, 1, fp);
for (const stl_facet &facet : stl->facet_start) {
memcpy(buffer, &facet, 50);
// Convert to little endian.
stl_internal_reverse_quads(buffer, 48);
fwrite(buffer, SIZEOF_STL_FACET, 1, fp);
}
#endif /* BOOST_ENDIAN_LITTLE_BYTE */
fclose(fp);
return true;
}
void stl_write_vertex(stl_file *stl, int facet, int vertex)
{
printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet,
stl->facet_start[facet].vertex[vertex](0),
stl->facet_start[facet].vertex[vertex](1),
stl->facet_start[facet].vertex[vertex](2));
}
void stl_write_facet(stl_file *stl, char *label, int facet)
{
printf("facet (%d)/ %s\n", facet, label);
stl_write_vertex(stl, facet, 0);
stl_write_vertex(stl, facet, 1);
stl_write_vertex(stl, facet, 2);
}
void stl_write_neighbor(stl_file *stl, int facet)
{
printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet,
stl->neighbors_start[facet].neighbor[0],
stl->neighbors_start[facet].neighbor[1],
stl->neighbors_start[facet].neighbor[2],
stl->neighbors_start[facet].which_vertex_not[0],
stl->neighbors_start[facet].which_vertex_not[1],
stl->neighbors_start[facet].which_vertex_not[2]);
}
bool stl_write_quad_object(stl_file *stl, char *file)
{
stl_vertex connect_color = stl_vertex::Zero();
stl_vertex uncon_1_color = stl_vertex::Zero();
stl_vertex uncon_2_color = stl_vertex::Zero();
stl_vertex uncon_3_color = stl_vertex::Zero();
stl_vertex color;
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing";
return false;
}
fprintf(fp, "CQUAD\n");
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
switch (stl->neighbors_start[i].num_neighbors()) {
case 0:
default: color = uncon_3_color; break;
case 1: color = uncon_2_color; break;
case 2: color = uncon_1_color; break;
case 3: color = connect_color; break;
}
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2));
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2));
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
}
fclose(fp);
return true;
}
bool stl_write_dxf(stl_file *stl, const char *file, char *label)
{
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing";
return false;
}
fprintf(fp, "999\n%s\n", label);
fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n");
fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\
0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n");
fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n");
fprintf(fp, "0\nSECTION\n2\nENTITIES\n");
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
fprintf(fp, "0\n3DFACE\n8\n0\n");
fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2));
fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2));
fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2));
fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2));
}
fprintf(fp, "0\nENDSEC\n0\nEOF\n");
fclose(fp);
return true;
}

281
src/admesh/stlinit.cpp Normal file
View file

@ -0,0 +1,281 @@
/* ADMesh -- process triangulated solid meshes
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Questions, comments, suggestions, etc to
* https://github.com/admesh/admesh/issues
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/predef/other/endian.h>
#include "stl.h"
#include "libslic3r/LocalesUtils.hpp"
#ifndef SEEK_SET
#error "SEEK_SET not defined"
#endif
#if BOOST_ENDIAN_BIG_BYTE
extern void stl_internal_reverse_quads(char *buf, size_t cnt);
#endif /* BOOST_ENDIAN_BIG_BYTE */
static FILE* stl_open_count_facets(stl_file *stl, const char *file)
{
// Open the file in binary mode first.
FILE *fp = boost::nowide::fopen(file, "rb");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading";
return nullptr;
}
// Find size of file.
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
// Check for binary or ASCII file.
fseek(fp, HEADER_SIZE, SEEK_SET);
unsigned char chtest[128];
if (! fread(chtest, sizeof(chtest), 1, fp)) {
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The input is an empty file: " << file;
fclose(fp);
return nullptr;
}
stl->stats.type = ascii;
for (size_t s = 0; s < sizeof(chtest); s++) {
if (chtest[s] > 127) {
stl->stats.type = binary;
break;
}
}
rewind(fp);
uint32_t num_facets = 0;
// Get the header and the number of facets in the .STL file.
// If the .STL file is binary, then do the following:
if (stl->stats.type == binary) {
// Test if the STL file has the right size.
if (((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) {
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The file " << file << " has the wrong size.";
fclose(fp);
return nullptr;
}
num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET;
// Read the header.
if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79)
stl->stats.header[80] = '\0';
// Read the int following the header. This should contain # of facets.
uint32_t header_num_facets;
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0;
#if BOOST_ENDIAN_BIG_BYTE
// Convert from little endian to big endian.
stl_internal_reverse_quads((char*)&header_num_facets, 4);
#endif /* BOOST_ENDIAN_BIG_BYTE */
if (! header_num_faces_read || num_facets != header_num_facets)
BOOST_LOG_TRIVIAL(info) << "stl_open_count_facets: Warning: File size doesn't match number of facets in the header: " << file;
}
// Otherwise, if the .STL file is ASCII, then do the following:
else
{
// Reopen the file in text mode (for getting correct newlines on Windows)
// fix to silence a warning about unused return value.
// obviously if it fails we have problems....
fp = boost::nowide::freopen(file, "r", fp);
// do another null check to be safe
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading";
fclose(fp);
return nullptr;
}
// Find the number of facets.
char linebuf[100];
int num_lines = 1;
while (fgets(linebuf, 100, fp) != nullptr) {
// Don't count short lines.
if (strlen(linebuf) <= 4)
continue;
// Skip solid/endsolid lines as broken STL file generators may put several of them.
if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0)
continue;
++ num_lines;
}
rewind(fp);
// Get the header.
int i = 0;
for (; i < 80 && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ;
stl->stats.header[i] = '\0'; // Lose the '\n'
stl->stats.header[80] = '\0';
num_facets = num_lines / ASCII_LINES_PER_FACET;
}
stl->stats.number_of_facets += num_facets;
stl->stats.original_num_facets = stl->stats.number_of_facets;
return fp;
}
/* Reads the contents of the file pointed to by fp into the stl structure,
starting at facet first_facet. The second argument says if it's our first
time running this for the stl and therefore we should reset our max and min stats. */
static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first)
{
if (stl->stats.type == binary)
fseek(fp, HEADER_SIZE, SEEK_SET);
else
rewind(fp);
char normal_buf[3][32];
for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++ i) {
stl_facet facet;
if (stl->stats.type == binary) {
// Read a single facet from a binary .STL file. We assume little-endian architecture!
if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET)
return false;
#if BOOST_ENDIAN_BIG_BYTE
// Convert the loaded little endian data to big endian.
stl_internal_reverse_quads((char*)&facet, 48);
#endif /* BOOST_ENDIAN_BIG_BYTE */
} else {
// Read a single facet from an ASCII .STL file
// skip solid/endsolid
// (in this order, otherwise it won't work when they are paired in the middle of a file)
fscanf(fp, " endsolid%*[^\n]\n");
fscanf(fp, " solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid")
// Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs.
int res_normal = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
assert(res_normal == 3);
int res_outer_loop = fscanf(fp, " outer loop");
assert(res_outer_loop == 0);
int res_vertex1 = fscanf(fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
assert(res_vertex1 == 3);
int res_vertex2 = fscanf(fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
assert(res_vertex2 == 3);
// Trailing whitespace is there to eat all whitespaces and empty lines up to the next non-whitespace.
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);
// Some G-code generators tend to produce text after "endloop" and "endfacet". Just ignore it.
char buf[2048];
fgets(buf, 2047, fp);
bool endloop_ok = strncmp(buf, "endloop", 7) == 0 && (buf[7] == '\r' || buf[7] == '\n' || buf[7] == ' ' || buf[7] == '\t');
assert(endloop_ok);
// Skip the trailing whitespaces and empty lines.
fscanf(fp, " ");
fgets(buf, 2047, fp);
bool endfacet_ok = strncmp(buf, "endfacet", 8) == 0 && (buf[8] == '\r' || buf[8] == '\n' || buf[8] == ' ' || buf[8] == '\t');
assert(endfacet_ok);
if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || ! endloop_ok || ! endfacet_ok) {
BOOST_LOG_TRIVIAL(error) << "Something is syntactically very wrong with this ASCII STL! ";
return false;
}
// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
// Normal was mangled. Maybe denormals or "not a number" were stored?
// Just reset the normal and silently ignore it.
memset(&facet.normal, 0, sizeof(facet.normal));
}
}
#if 0
// Report close to zero vertex coordinates. Due to the nature of the floating point numbers,
// close to zero values may be represented with singificantly higher precision than the rest of the vertices.
// It may be worth to round these numbers to zero during loading to reduce the number of errors reported
// during the STL import.
for (size_t j = 0; j < 3; ++ j) {
if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f)
printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0));
if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f)
printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1));
if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f)
printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2));
}
#endif
// Write the facet into memory.
stl->facet_start[i] = facet;
stl_facet_stats(stl, facet, first);
}
stl->stats.size = stl->stats.max - stl->stats.min;
stl->stats.bounding_diameter = stl->stats.size.norm();
return true;
}
bool stl_open(stl_file *stl, const char *file)
{
Slic3r::CNumericLocalesSetter locales_setter;
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;
}
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)
{
// While we are going through all of the facets, let's find the
// maximum and minimum values for x, y, and z
if (first) {
// Initialize the max and min values the first time through
stl->stats.min = facet.vertex[0];
stl->stats.max = facet.vertex[0];
stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs();
stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2)));
first = false;
}
// Now find the max and min values.
for (size_t i = 0; i < 3; ++ i) {
stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]);
stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]);
}
}

399
src/admesh/util.cpp Normal file
View file

@ -0,0 +1,399 @@
/* ADMesh -- process triangulated solid meshes
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Questions, comments, suggestions, etc to
* https://github.com/admesh/admesh/issues
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <boost/log/trivial.hpp>
#include "stl.h"
void stl_verify_neighbors(stl_file *stl)
{
stl->stats.backwards_edges = 0;
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
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.p2 = stl->facet_start[i].vertex[(j + 1) % 3];
int neighbor = stl->neighbors_start[i].neighbor[j];
if (neighbor == -1)
continue; // this edge has no neighbor... Continue.
int vnot = stl->neighbors_start[i].which_vertex_not[j];
stl_edge edge_b;
if (vnot < 3) {
edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3];
edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3];
} else {
stl->stats.backwards_edges += 1;
edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3];
edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3];
}
if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) {
// These edges should match but they don't. Print results.
BOOST_LOG_TRIVIAL(info) << "edge " << j << " of facet " << i << " doesn't match edge " << (vnot + 1) << " of facet " << neighbor;
stl_write_facet(stl, (char*)"first facet", i);
stl_write_facet(stl, (char*)"second facet", neighbor);
}
}
}
}
void stl_translate(stl_file *stl, float x, float y, float z)
{
stl_vertex new_min(x, y, z);
stl_vertex shift = new_min - stl->stats.min;
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
stl->facet_start[i].vertex[j] += shift;
stl->stats.min = new_min;
stl->stats.max += shift;
}
/* 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)
{
stl_vertex shift(x, y, z);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
stl->facet_start[i].vertex[j] += shift;
stl->stats.min += shift;
stl->stats.max += shift;
}
void stl_scale_versor(stl_file *stl, const stl_vertex &versor)
{
// Scale extents.
auto s = versor.array();
stl->stats.min.array() *= s;
stl->stats.max.array() *= s;
// Scale size.
stl->stats.size.array() *= s;
// Scale volume.
if (stl->stats.volume > 0.0)
stl->stats.volume *= versor(0) * versor(1) * versor(2);
// Scale the mesh.
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
stl->facet_start[i].vertex[j].array() *= s;
}
static void calculate_normals(stl_file *stl)
{
stl_normal normal;
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
stl_calculate_normal(normal, &stl->facet_start[i]);
stl_normalize_vector(normal);
stl->facet_start[i].normal = normal;
}
}
static inline void rotate_point_2d(float &x, float &y, const double c, const double s)
{
double xold = x;
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 c = cos(radian_angle);
double s = sin(radian_angle);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
rotate_point_2d(stl->facet_start[i].vertex[j](1), stl->facet_start[i].vertex[j](2), c, s);
stl_get_size(stl);
calculate_normals(stl);
}
void stl_rotate_y(stl_file *stl, float angle)
{
double radian_angle = (angle / 180.0) * M_PI;
double c = cos(radian_angle);
double s = sin(radian_angle);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
rotate_point_2d(stl->facet_start[i].vertex[j](2), stl->facet_start[i].vertex[j](0), c, s);
stl_get_size(stl);
calculate_normals(stl);
}
void stl_rotate_z(stl_file *stl, float angle)
{
double radian_angle = (angle / 180.0) * M_PI;
double c = cos(radian_angle);
double s = sin(radian_angle);
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i)
for (int j = 0; j < 3; ++ j)
rotate_point_2d(stl->facet_start[i].vertex[j](0), stl->facet_start[i].vertex[j](1), c, s);
stl_get_size(stl);
calculate_normals(stl);
}
void its_rotate_x(indexed_triangle_set &its, float angle)
{
double radian_angle = (angle / 180.0) * M_PI;
double c = cos(radian_angle);
double s = sin(radian_angle);
for (stl_vertex &v : its.vertices)
rotate_point_2d(v(1), v(2), c, s);
}
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);
}
void its_rotate_z(indexed_triangle_set& its, float angle)
{
double radian_angle = (angle / 180.0) * M_PI;
double c = cos(radian_angle);
double s = sin(radian_angle);
for (stl_vertex& v : its.vertices)
rotate_point_2d(v(0), v(1), c, s);
}
void stl_get_size(stl_file *stl)
{
if (stl->stats.number_of_facets == 0)
return;
stl->stats.min = stl->facet_start[0].vertex[0];
stl->stats.max = stl->stats.min;
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
const stl_facet &face = stl->facet_start[i];
for (int j = 0; j < 3; ++ j) {
stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]);
stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]);
}
}
stl->stats.size = stl->stats.max - stl->stats.min;
stl->stats.bounding_diameter = stl->stats.size.norm();
}
void stl_mirror_xy(stl_file *stl)
{
for (uint32_t 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;
float temp_size = stl->stats.min(2);
stl->stats.min(2) = stl->stats.max(2);
stl->stats.max(2) = temp_size;
stl->stats.min(2) *= -1.0;
stl->stats.max(2) *= -1.0;
stl_reverse_all_facets(stl);
stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
}
void stl_mirror_yz(stl_file *stl)
{
for (uint32_t 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;
float temp_size = stl->stats.min(0);
stl->stats.min(0) = stl->stats.max(0);
stl->stats.max(0) = temp_size;
stl->stats.min(0) *= -1.0;
stl->stats.max(0) *= -1.0;
stl_reverse_all_facets(stl);
stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */
}
void stl_mirror_xz(stl_file *stl)
{
for (uint32_t 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;
float temp_size = stl->stats.min(1);
stl->stats.min(1) = stl->stats.max(1);
stl->stats.max(1) = temp_size;
stl->stats.min(1) *= -1.0;
stl->stats.max(1) *= -1.0;
stl_reverse_all_facets(stl);
stl->stats.facets_reversed -= stl->stats.number_of_facets; // for not altering stats
}
float get_area(stl_facet *facet)
{
/* cast to double before calculating cross product because large coordinates
can result in overflowing product
(bad area is responsible for bad volume and bad facets reversal) */
double cross[3][3];
for (int i = 0; i < 3; i++) {
cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) -
((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1)));
cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) -
((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2)));
cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) -
((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0)));
}
stl_normal sum;
sum(0) = cross[0][0] + cross[1][0] + cross[2][0];
sum(1) = cross[0][1] + cross[1][1] + cross[2][1];
sum(2) = cross[0][2] + cross[1][2] + cross[2][2];
// This should already be done. But just in case, let's do it again.
//FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy.
stl_normal n;
stl_calculate_normal(n, facet);
stl_normalize_vector(n);
return 0.5f * n.dot(sum);
}
static float get_volume(stl_file *stl)
{
// 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)
{
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,
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)
{
if (exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) {
if (verbose_flag)
printf("Checking exact...\n");
exact_flag = true;
stl_check_facets_exact(stl);
stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge);
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 (! tolerance_flag)
tolerance = stl->stats.shortest_edge;
if (! increment_flag)
increment = stl->stats.bounding_diameter / 10000.0;
}
if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) {
int last_edges_fixed = 0;
for (int i = 0; i < iterations; ++ i) {
if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) {
if (verbose_flag)
printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations);
stl_check_facets_nearby(stl, tolerance);
if (verbose_flag)
printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed);
last_edges_fixed = stl->stats.edges_fixed;
tolerance += increment;
} else {
if (verbose_flag)
printf("All facets connected. No further nearby check necessary.\n");
break;
}
}
} else if (verbose_flag)
printf("All facets connected. No nearby check necessary.\n");
if (remove_unconnected_flag || fixall_flag || fill_holes_flag) {
if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) {
if (verbose_flag)
printf("Removing unconnected facets...\n");
stl_remove_unconnected_facets(stl);
} else if (verbose_flag)
printf("No unconnected need to be removed.\n");
}
if (fill_holes_flag || fixall_flag) {
if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) {
if (verbose_flag)
printf("Filling holes...\n");
stl_fill_holes(stl);
} else if (verbose_flag)
printf("No holes need to be filled.\n");
}
if (reverse_all_flag) {
if (verbose_flag)
printf("Reversing all facets...\n");
stl_reverse_all_facets(stl);
}
if (normal_directions_flag || fixall_flag) {
if (verbose_flag)
printf("Checking normal directions...\n");
stl_fix_normal_directions(stl);
}
if (normal_values_flag || fixall_flag) {
if (verbose_flag)
printf("Checking normal values...\n");
stl_fix_normal_values(stl);
}
// Always calculate the volume. It shouldn't take too long.
if (verbose_flag)
printf("Calculating volume...\n");
stl_calculate_volume(stl);
if (exact_flag) {
if (verbose_flag)
printf("Verifying neighbors...\n");
stl_verify_neighbors(stl);
}
}