Rewrote .PRUSA file parser from wxWidgets zip to miniz.

Added tracing for mesh repair.
This commit is contained in:
bubnikv 2018-09-25 14:30:57 +02:00
parent 85bc3af88a
commit 8945763221
5 changed files with 327 additions and 353 deletions

View file

@ -1,12 +1,10 @@
#ifdef SLIC3R_PRUS
#include <string.h> #include <string.h>
#include <exception>
#include <boost/algorithm/string.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
#include <wx/string.h> #include <miniz/miniz_zip.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include <Eigen/Geometry> #include <Eigen/Geometry>
@ -35,65 +33,29 @@ struct StlHeader
static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct"); static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct");
// Buffered line reader for the wxInputStream. // Buffered line reader to a string buffer.
class LineReader class LineReader
{ {
public: public:
LineReader(wxInputStream &input_stream, const char *initial_data, int initial_len) : LineReader(std::vector<char> &data) : m_buffer(data), m_pos(0), m_len(data.size()) {}
m_input_stream(input_stream),
m_pos(0),
m_len(initial_len)
{
assert(initial_len >= 0 && initial_len < m_bufsize);
memcpy(m_buffer, initial_data, initial_len);
}
const char* next_line() { const char* next_line() {
for (;;) {
// Skip empty lines. // Skip empty lines.
while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')) while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n'))
++ m_pos; ++ m_pos;
if (m_pos == m_len) { if (m_pos == m_len) {
// Empty buffer, fill it from the input stream.
m_pos = 0;
m_input_stream.Read(m_buffer, m_bufsize - 1);
m_len = m_input_stream.LastRead();
assert(m_len >= 0 && m_len < m_bufsize);
if (m_len == 0)
// End of file. // End of file.
return nullptr; return nullptr;
// Skip empty lines etc.
continue;
} }
// The buffer is nonempty and it does not start with end of lines. Find the first end of line. // The buffer is nonempty and it does not start with end of lines. Find the first end of line.
int end = m_pos + 1; int end = m_pos + 1;
while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n') while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n')
++ end; ++ end;
if (end == m_len && ! m_input_stream.Eof() && m_len < m_bufsize) { char *ptr_out = m_buffer.data() + m_pos;
// Move the buffer content to the buffer start and fill the rest of the buffer.
assert(m_pos > 0);
memmove(m_buffer, m_buffer + m_pos, m_len - m_pos);
m_len -= m_pos;
assert(m_len >= 0 && m_len < m_bufsize);
m_pos = 0;
m_input_stream.Read(m_buffer + m_len, m_bufsize - 1 - m_len);
int new_data = m_input_stream.LastRead();
if (new_data > 0) {
m_len += new_data;
assert(m_len >= 0 && m_len < m_bufsize);
continue;
}
}
char *ptr_out = m_buffer + m_pos;
m_pos = end + 1; m_pos = end + 1;
m_buffer[end] = 0; m_buffer[end] = 0;
if (m_pos >= m_len) {
m_pos = 0;
m_len = 0;
}
return ptr_out; return ptr_out;
} }
}
int next_line_scanf(const char *format, ...) int next_line_scanf(const char *format, ...)
{ {
@ -109,57 +71,28 @@ public:
} }
private: private:
wxInputStream &m_input_stream; std::vector<char> &m_buffer;
static const int m_bufsize = 4096; int m_pos;
char m_buffer[m_bufsize]; int m_len;
int m_pos = 0;
int m_len = 0;
}; };
// Load a PrusaControl project file into a provided model. static void extract_model_from_archive(
bool load_prus(const char *path, Model *model) // name of the model file
const char *name,
// path to the archive
const char *path,
// content of scene.xml
const std::vector<char> &scene_xml_data,
// loaded data of this STL
std::vector<char> &data,
// Model, to which the newly loaded objects will be added
Model *model,
// To map multiple STLs into a single model object for multi-material prints.
std::map<int, ModelObject*> &group_to_model_object)
{ {
// To receive the content of the zipped 'scene.xml' file.
std::vector<char> scene_xml_data;
wxFFileInputStream in(
#ifdef WIN32
// On Windows, convert to a 16bit unicode string.
boost::nowide::widen(path).c_str()
#else
path
#endif
);
wxZipInputStream zip(in);
std::unique_ptr<wxZipEntry> entry;
size_t num_models = 0;
std::map<int, ModelObject*> group_to_model_object;
while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
wxString name = entry->GetName();
if (name == "scene.xml") {
if (! scene_xml_data.empty()) {
// scene.xml has been found more than once in the archive.
return false;
}
size_t size_last = 0;
size_t size_incr = 4096;
scene_xml_data.resize(size_incr);
while (! zip.Read(scene_xml_data.data() + size_last, size_incr).Eof()) {
size_last += zip.LastRead();
if (scene_xml_data.size() < size_last + size_incr)
scene_xml_data.resize(size_last + size_incr);
}
size_last += zip.LastRead();
if (scene_xml_data.size() == size_last)
scene_xml_data.resize(size_last + 1);
else if (scene_xml_data.size() > size_last + 1)
scene_xml_data.erase(scene_xml_data.begin() + size_last + 1, scene_xml_data.end());
scene_xml_data[size_last] = 0;
}
else if (name.EndsWith(".stl") || name.EndsWith(".STL")) {
// Find the model entry in the XML data. // Find the model entry in the XML data.
const wxScopedCharBuffer name_utf8 = name.ToUTF8();
char model_name_tag[1024]; char model_name_tag[1024];
sprintf(model_name_tag, "<model name=\"%s\">", name_utf8.data()); sprintf(model_name_tag, "<model name=\"%s\">", name);
const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); const char *model_xml = strstr(scene_xml_data.data(), model_name_tag);
const char *zero_tag = "<zero>"; const char *zero_tag = "<zero>";
const char *zero_xml = strstr(scene_xml_data.data(), zero_tag); const char *zero_xml = strstr(scene_xml_data.data(), zero_tag);
@ -247,13 +180,16 @@ bool load_prus(const char *path, Model *model)
extruder_id = e; extruder_id = e;
} }
} }
if (trafo_set) { if (! trafo_set)
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
// Extract the STL. // Extract the STL.
StlHeader header; StlHeader header;
TriangleMesh mesh; TriangleMesh mesh;
bool mesh_valid = false; bool mesh_valid = false;
bool stl_ascii = false; bool stl_ascii = false;
if (!zip.Read((void*)&header, sizeof(StlHeader)).Eof()) { if (data.size() > sizeof(StlHeader)) {
memcpy((char*)&header, data.data(), sizeof(StlHeader));
if (strncmp(header.comment, "solid ", 6) == 0) if (strncmp(header.comment, "solid ", 6) == 0)
stl_ascii = true; stl_ascii = true;
else { else {
@ -264,7 +200,8 @@ bool load_prus(const char *path, Model *model)
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 && zip.ReadAll((void*)stl.facet_start, 50 * header.nTriangles)) { if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) {
memcpy((char*)stl.facet_start, 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;
@ -285,12 +222,13 @@ bool load_prus(const char *path, Model *model)
} }
} else } else
stl_ascii = true; stl_ascii = true;
if (stl_ascii) { if (stl_ascii) {
// Try to parse ASCII STL. // Try to parse ASCII STL.
char normal_buf[3][32]; char normal_buf[3][32];
stl_facet facet; stl_facet facet;
std::vector<stl_facet> facets; std::vector<stl_facet> facets;
LineReader line_reader(zip, (char*)&header, zip.LastRead()); LineReader line_reader(data);
std::string solid_name; std::string solid_name;
facet.extra[0] = facet.extra[1] = 0; facet.extra[0] = facet.extra[1] = 0;
for (;;) { for (;;) {
@ -366,12 +304,14 @@ bool load_prus(const char *path, Model *model)
} }
} }
if (mesh_valid) { if (! mesh_valid)
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
// Add this mesh to the model. // Add this mesh to the model.
ModelVolume *volume = nullptr; ModelVolume *volume = nullptr;
if (model_object == nullptr) { if (model_object == nullptr) {
// This is a first mesh of a group. Create a new object & volume. // This is a first mesh of a group. Create a new object & volume.
model_object = model->add_object(name_utf8.data(), path, std::move(mesh)); model_object = model->add_object(name, path, std::move(mesh));
volume = model_object->volumes.front(); volume = model_object->volumes.front();
ModelInstance *instance = model_object->add_instance(); ModelInstance *instance = model_object->add_instance();
#if ENABLE_MODELINSTANCE_3D_ROTATION #if ENABLE_MODELINSTANCE_3D_ROTATION
@ -385,13 +325,12 @@ bool load_prus(const char *path, Model *model)
#else #else
instance->offset = instance_offset; instance->offset = instance_offset;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET #endif // ENABLE_MODELINSTANCE_3D_OFFSET
++num_models;
if (group_id != (size_t)-1) if (group_id != (size_t)-1)
group_to_model_object[group_id] = model_object; group_to_model_object[group_id] = model_object;
} else { } else {
// This is not the 1st mesh of a group. Add it to the ModelObject. // This is not the 1st mesh of a group. Add it to the ModelObject.
volume = model_object->add_volume(std::move(mesh)); volume = model_object->add_volume(std::move(mesh));
volume->name = name_utf8.data(); volume->name = name;
} }
// Set the extruder to the volume. // Set the extruder to the volume.
if (extruder_id != (unsigned int)-1) { if (extruder_id != (unsigned int)-1) {
@ -400,12 +339,46 @@ bool load_prus(const char *path, Model *model)
volume->config.set_deserialize("extruder", str_extruder); volume->config.set_deserialize("extruder", str_extruder);
} }
} }
// Load a PrusaControl project file into a provided model.
bool load_prus(const char *path, Model *model)
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
mz_bool res = mz_zip_reader_init_file(&archive, path, 0);
size_t n_models_initial = model->objects.size();
try {
if (res == MZ_FALSE)
throw std::runtime_error(std::string("Unable to init zip reader for ") + path);
std::vector<char> scene_xml_data;
// For grouping multiple STLs into a single ModelObject for multi-material prints.
std::map<int, ModelObject*> group_to_model_object;
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
for (mz_uint i = 0; i < num_entries; ++ i) {
mz_zip_archive_file_stat stat;
if (! mz_zip_reader_file_stat(&archive, i, &stat))
continue;
std::vector<char> buffer;
buffer.assign((size_t)stat.m_uncomp_size + 1, 0);
res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == MZ_FALSE)
std::runtime_error(std::string("Error while extracting a file from ") + path);
if (strcmp(stat.m_filename, "scene.xml") == 0) {
if (! scene_xml_data.empty())
throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path);
scene_xml_data = std::move(buffer);
} else if (boost::iends_with(stat.m_filename, ".stl")) {
// May throw std::exception
extract_model_from_archive(stat.m_filename, path, scene_xml_data, buffer, model, group_to_model_object);
} }
} }
} catch (std::exception &ex) {
mz_zip_reader_end(&archive);
throw ex;
} }
return num_models > 0;
mz_zip_reader_end(&archive);
return model->objects.size() > n_models_initial;
} }
}; // namespace Slic3r }; // namespace Slic3r
#endif /* SLIC3R_PRUS */

View file

@ -1,4 +1,3 @@
#if defined(SLIC3R_PRUS) && ! defined(slic3r_Format_PRUS_hpp_)
#define slic3r_Format_PRUS_hpp_ #define slic3r_Format_PRUS_hpp_
namespace Slic3r { namespace Slic3r {
@ -10,5 +9,3 @@ class Model;
extern bool load_prus(const char *path, Model *model); extern bool load_prus(const char *path, Model *model);
}; // namespace Slic3r }; // namespace Slic3r
#endif /* SLIC3R_PRUS && ! defined(slic3r_Format_PRUS_hpp_) */

View file

@ -62,10 +62,8 @@ Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *c
result = load_amf(input_file.c_str(), config, &model); result = load_amf(input_file.c_str(), config, &model);
else if (boost::algorithm::iends_with(input_file, ".3mf")) else if (boost::algorithm::iends_with(input_file, ".3mf"))
result = load_3mf(input_file.c_str(), config, &model); result = load_3mf(input_file.c_str(), config, &model);
#ifdef SLIC3R_PRUS
else if (boost::algorithm::iends_with(input_file, ".prusa")) else if (boost::algorithm::iends_with(input_file, ".prusa"))
result = load_prus(input_file.c_str(), &model); result = load_prus(input_file.c_str(), &model);
#endif /* SLIC3R_PRUS */
else else
throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");

View file

@ -134,25 +134,31 @@ void TriangleMesh::repair()
// remove_unconnected // remove_unconnected
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets";
stl_remove_unconnected_facets(&stl); stl_remove_unconnected_facets(&stl);
} }
// fill_holes // fill_holes
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
BOOST_LOG_TRIVIAL(trace) << "\tstl_fill_holes";
stl_fill_holes(&stl); stl_fill_holes(&stl);
stl_clear_error(&stl); stl_clear_error(&stl);
} }
// normal_directions // normal_directions
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions";
stl_fix_normal_directions(&stl); stl_fix_normal_directions(&stl);
// normal_values // normal_values
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values";
stl_fix_normal_values(&stl); stl_fix_normal_values(&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
BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume";
stl_calculate_volume(&stl); stl_calculate_volume(&stl);
// neighbors // neighbors
BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors";
stl_verify_neighbors(&stl); stl_verify_neighbors(&stl);
this->repaired = true; this->repaired = true;

View file

@ -117,10 +117,10 @@ inline uint64_t next_highest_power_of_2(uint64_t v)
inline size_t next_highest_power_of_2(size_t v) inline size_t next_highest_power_of_2(size_t v)
{ {
#if SSIZE_MAX == 9223372036854775807 #if SSIZE_MAX == 9223372036854775807
static_assert(sizeof(size_t) == sizeof(uint64_t)); static_assert(sizeof(size_t) == sizeof(uint64_t), "sizeof(size_t) == sizeof(uint64_t)");
return next_highest_power_of_2(uint64_t(v)); return next_highest_power_of_2(uint64_t(v));
#else #else
static_assert(sizeof(size_t) == sizeof(uint32_t)); static_assert(sizeof(size_t) == sizeof(uint32_t), "sizeof(size_t) == sizeof(uint32_t)");
return next_highest_power_of_2(uint32_t(v)); return next_highest_power_of_2(uint32_t(v));
#endif #endif
} }