diff --git a/xs/Build.PL b/xs/Build.PL index 51d1a014f..7fa9c1b1a 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -33,14 +33,15 @@ my @early_includes = (); my @INC = qw(-Isrc/libslic3r -Isrc/glew/include); my @LIBS = $cpp_guess->is_msvc ? qw(-LIBPATH:src/libslic3r) : qw(-Lsrc/libslic3r); -if ($ENV{SLIC3R_GUI}) +if ($ENV{SLIC3R_GUI} || $ENV{SLIC3R_PRUS}) { print "Slic3r will be built with GUI support\n"; require Alien::wxWidgets; Alien::wxWidgets->load; push @INC, Alien::wxWidgets->include_path; - push @cflags, qw(-DSLIC3R_GUI -DUNICODE), Alien::wxWidgets->defines, Alien::wxWidgets->c_flags; - my $alienwx_libraries = Alien::wxWidgets->libraries(qw(gl html)); + push @cflags, qw(-DSLIC3R_GUI) if $ENV{SLIC3R_GUI}; + push @cflags, qw(-DSLIC3R_PRUS -DUNICODE), Alien::wxWidgets->defines, Alien::wxWidgets->c_flags; + my $alienwx_libraries = Alien::wxWidgets->libraries($ENV{SLIC3R_GUI} ? qw(gl html) : qw(base)); $alienwx_libraries =~ s/-L/-LIBPATH:/g if ($cpp_guess->is_msvc); push @ldflags, Alien::wxWidgets->link_flags, $alienwx_libraries; # push @early_includes, qw(slic3r/GUI/wxinit.h); @@ -216,6 +217,8 @@ if ($cpp_guess->is_gcc) { } print "\n"; +print 'With @cflags: ', join(', ', map "\"$_\"", @cflags), "\n"; +print 'With @ldflags: ', join(', ', map "\"$_\"", @ldflags), "\n"; print 'With @INC: ', join(', ', map "\"$_\"", @INC), "\n"; print 'With @LIBS: ', join(', ', map "\"$_\"", @LIBS), "\n"; diff --git a/xs/MANIFEST b/xs/MANIFEST index 2be8934c7..a6581c9da 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -50,6 +50,16 @@ src/libslic3r/Fill/FillRectilinear2.cpp src/libslic3r/Fill/FillRectilinear2.hpp src/libslic3r/Flow.cpp src/libslic3r/Flow.hpp +src/libslic3r/Format/AMF.cpp +src/libslic3r/Format/AMF.hpp +src/libslic3r/Format/OBJ.cpp +src/libslic3r/Format/OBJ.hpp +src/libslic3r/Format/objparser.cpp +src/libslic3r/Format/objparser.hpp +src/libslic3r/Format/PRUS.cpp +src/libslic3r/Format/PRUS.hpp +src/libslic3r/Format/STL.cpp +src/libslic3r/Format/STL.hpp src/libslic3r/GCode.cpp src/libslic3r/GCode.hpp src/libslic3r/GCode/CoolingBuffer.cpp @@ -67,7 +77,6 @@ src/libslic3r/Geometry.hpp src/libslic3r/Layer.cpp src/libslic3r/Layer.hpp src/libslic3r/LayerRegion.cpp -src/libslic3r/LayerRegionFill.cpp src/libslic3r/libslic3r.h src/libslic3r/Line.cpp src/libslic3r/Line.hpp diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp new file mode 100644 index 000000000..0d19e25ad --- /dev/null +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -0,0 +1,619 @@ +#include +#include +#include +#include + +#include "../libslic3r.h" +#include "../Model.hpp" +#include "AMF.hpp" + +#if 0 +// Enable debugging and assert in this file. +#define DEBUG +#define _DEBUG +#undef NDEBUG +#endif + +#include + +namespace Slic3r +{ + +struct AMFParserContext +{ + AMFParserContext(XML_Parser parser, Model *model) : + m_parser(parser), + m_model(*model), + m_object(nullptr), + m_volume(nullptr), + m_material(nullptr), + m_instance(nullptr) + { + m_path.reserve(12); + } + + void stop() + { + XML_StopParser(m_parser, 0); + } + + void startElement(const char *name, const char **atts); + void endElement(const char *name); + void endDocument(); + void characters(const XML_Char *s, int len); + + static void XMLCALL startElement(void *userData, const char *name, const char **atts) + { + AMFParserContext *ctx = (AMFParserContext*)userData; + ctx->startElement(name, atts); + } + + static void XMLCALL endElement(void *userData, const char *name) + { + AMFParserContext *ctx = (AMFParserContext*)userData; + ctx->endElement(name); + } + + /* s is not 0 terminated. */ + static void XMLCALL characters(void *userData, const XML_Char *s, int len) + { + AMFParserContext *ctx = (AMFParserContext*)userData; + ctx->characters(s, len); + } + + static const char* get_attribute(const char **atts, const char *id) { + if (atts == nullptr) + return nullptr; + while (*atts != nullptr) { + if (strcmp(*(atts ++), id) == 0) + return *atts; + ++ atts; + } + return nullptr; + } + + enum AMFNodeType { + NODE_TYPE_INVALID = 0, + NODE_TYPE_UNKNOWN, + NODE_TYPE_AMF, // amf + // amf/metadata + NODE_TYPE_MATERIAL, // amf/material + // amf/material/metadata + NODE_TYPE_OBJECT, // amf/object + // amf/object/metadata + NODE_TYPE_MESH, // amf/object/mesh + NODE_TYPE_VERTICES, // amf/object/mesh/vertices + NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex + NODE_TYPE_COORDINATES, // amf/object/mesh/vertices/vertex/coordinates + NODE_TYPE_COORDINATE_X, // amf/object/mesh/vertices/vertex/coordinates/x + NODE_TYPE_COORDINATE_Y, // amf/object/mesh/vertices/vertex/coordinates/y + NODE_TYPE_COORDINATE_Z, // amf/object/mesh/vertices/vertex/coordinates/z + NODE_TYPE_VOLUME, // amf/object/mesh/volume + // amf/object/mesh/volume/metadata + NODE_TYPE_TRIANGLE, // amf/object/mesh/volume/triangle + NODE_TYPE_VERTEX1, // amf/object/mesh/volume/triangle/v1 + NODE_TYPE_VERTEX2, // amf/object/mesh/volume/triangle/v2 + NODE_TYPE_VERTEX3, // amf/object/mesh/volume/triangle/v3 + NODE_TYPE_CONSTELLATION, // amf/constellation + NODE_TYPE_INSTANCE, // amf/constellation/instance + NODE_TYPE_DELTAX, // amf/constellation/instance/deltax + NODE_TYPE_DELTAY, // amf/constellation/instance/deltay + NODE_TYPE_RZ, // amf/constellation/instance/rz + NODE_TYPE_METADATA, // anywhere under amf/*/metadata + }; + + struct Instance { + Instance() : deltax_set(false), deltay_set(false), rz_set(false) {} + // Shift in the X axis. + float deltax; + bool deltax_set; + // Shift in the Y axis. + float deltay; + bool deltay_set; + // Rotation around the Z axis. + float rz; + bool rz_set; + }; + + struct Object { + Object() : idx(-1) {} + int idx; + std::vector instances; + }; + + // Current Expat XML parser instance. + XML_Parser m_parser; + // Model to receive objects extracted from an AMF file. + Model &m_model; + // Current parsing path in the XML file. + std::vector m_path; + // Current object allocated for an amf/object XML subtree. + ModelObject *m_object; + // Map from obect name to object idx & instances. + std::map m_object_instances_map; + // Vertices parsed for the current m_object. + std::vector m_object_vertices; + // Current volume allocated for an amf/object/mesh/volume subtree. + ModelVolume *m_volume; + // Faces collected for the current m_volume. + std::vector m_volume_facets; + // Current material allocated for an amf/metadata subtree. + ModelMaterial *m_material; + // Current instance allocated for an amf/constellation/instance subtree. + Instance *m_instance; + // Generic string buffer for vertices, face indices, metadata etc. + std::string m_value[3]; +}; + +void AMFParserContext::startElement(const char *name, const char **atts) +{ + AMFNodeType node_type_new = NODE_TYPE_UNKNOWN; + switch (m_path.size()) { + case 0: + // An AMF file must start with an tag. + node_type_new = NODE_TYPE_AMF; + if (strcmp(name, "amf") != 0) + this->stop(); + break; + case 1: + if (strcmp(name, "metadata") == 0) { + const char *type = get_attribute(atts, "type"); + if (type != nullptr) { + m_value[0] = type; + node_type_new = NODE_TYPE_METADATA; + } + } else if (strcmp(name, "material") == 0) { + const char *material_id = get_attribute(atts, "id"); + m_material = m_model.add_material((material_id == nullptr) ? "_" : material_id); + node_type_new = NODE_TYPE_MATERIAL; + } else if (strcmp(name, "object") == 0) { + const char *object_id = get_attribute(atts, "id"); + if (object_id == nullptr) + this->stop(); + else { + assert(m_object_vertices.empty()); + m_object = m_model.add_object(); + m_object_instances_map[object_id].idx = int(m_model.objects.size())-1; + node_type_new = NODE_TYPE_OBJECT; + } + } else if (strcmp(name, "constellation") == 0) { + node_type_new = NODE_TYPE_CONSTELLATION; + } + break; + case 2: + if (strcmp(name, "metadata") == 0) { + if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) { + m_value[0] = get_attribute(atts, "type"); + node_type_new = NODE_TYPE_METADATA; + } + } else if (strcmp(name, "mesh") == 0) { + if (m_path[1] == NODE_TYPE_OBJECT) + node_type_new = NODE_TYPE_MESH; + } else if (strcmp(name, "instance") == 0) { + if (m_path[1] == NODE_TYPE_CONSTELLATION) { + const char *object_id = get_attribute(atts, "objectid"); + if (object_id == nullptr) + this->stop(); + else { + m_object_instances_map[object_id].instances.push_back(AMFParserContext::Instance()); + m_instance = &m_object_instances_map[object_id].instances.back(); + node_type_new = NODE_TYPE_INSTANCE; + } + } + else + this->stop(); + } + break; + case 3: + if (m_path[2] == NODE_TYPE_MESH) { + assert(m_object); + if (strcmp(name, "vertices") == 0) + node_type_new = NODE_TYPE_VERTICES; + else if (strcmp(name, "volume") == 0) { + assert(! m_volume); + m_volume = m_object->add_volume(TriangleMesh()); + node_type_new = NODE_TYPE_VOLUME; + } + } else if (m_path[2] == NODE_TYPE_INSTANCE) { + assert(m_instance); + if (strcmp(name, "deltax") == 0) + node_type_new = NODE_TYPE_DELTAX; + else if (strcmp(name, "deltay") == 0) + node_type_new = NODE_TYPE_DELTAY; + else if (strcmp(name, "rz") == 0) + node_type_new = NODE_TYPE_RZ; + } + break; + case 4: + if (m_path[3] == NODE_TYPE_VERTICES) { + if (strcmp(name, "vertex") == 0) + node_type_new = NODE_TYPE_VERTEX; + } else if (m_path[3] == NODE_TYPE_VOLUME) { + if (strcmp(name, "metadata") == 0) { + const char *type = get_attribute(atts, "type"); + if (type == nullptr) + this->stop(); + else { + m_value[0] = type; + node_type_new = NODE_TYPE_METADATA; + } + } else if (strcmp(name, "triangle") == 0) + node_type_new = NODE_TYPE_TRIANGLE; + } + break; + case 5: + if (strcmp(name, "coordinates") == 0) { + if (m_path[4] == NODE_TYPE_VERTEX) { + node_type_new = NODE_TYPE_COORDINATES; + } else + this->stop(); + } else if (name[0] == 'v' && name[1] >= '1' && name[1] <= '3' && name[2] == 0) { + if (m_path[4] == NODE_TYPE_TRIANGLE) { + node_type_new = AMFNodeType(NODE_TYPE_VERTEX1 + name[1] - '1'); + } else + this->stop(); + } + break; + case 6: + if ((name[0] == 'x' || name[0] == 'y' || name[0] == 'z') && name[1] == 0) { + if (m_path[5] == NODE_TYPE_COORDINATES) + node_type_new = AMFNodeType(NODE_TYPE_COORDINATE_X + name[0] - 'x'); + else + this->stop(); + } + break; + default: + break; + } + + m_path.push_back(node_type_new); +} + +void AMFParserContext::characters(const XML_Char *s, int len) +{ + if (m_path.back() == NODE_TYPE_METADATA) { + m_value[1].append(s, len); + } + else + { + switch (m_path.size()) { + case 4: + if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ) + m_value[0].append(s, len); + break; + case 6: + switch (m_path.back()) { + case NODE_TYPE_VERTEX1: m_value[0].append(s, len); break; + case NODE_TYPE_VERTEX2: m_value[1].append(s, len); break; + case NODE_TYPE_VERTEX3: m_value[2].append(s, len); break; + default: break; + } + case 7: + switch (m_path.back()) { + case NODE_TYPE_COORDINATE_X: m_value[0].append(s, len); break; + case NODE_TYPE_COORDINATE_Y: m_value[1].append(s, len); break; + case NODE_TYPE_COORDINATE_Z: m_value[2].append(s, len); break; + default: break; + } + default: + break; + } + } +} + +void AMFParserContext::endElement(const char *name) +{ + switch (m_path.back()) { + + // Constellation transformation: + case NODE_TYPE_DELTAX: + assert(m_instance); + m_instance->deltax = float(atof(m_value[0].c_str())); + m_instance->deltax_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_DELTAY: + assert(m_instance); + m_instance->deltay = float(atof(m_value[0].c_str())); + m_instance->deltay_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_RZ: + assert(m_instance); + m_instance->rz = float(atof(m_value[0].c_str())); + m_instance->rz_set = true; + m_value[0].clear(); + break; + + // Object vertices: + case NODE_TYPE_VERTEX: + assert(m_object); + // Parse the vertex data + m_object_vertices.emplace_back(atof(m_value[0].c_str())); + m_object_vertices.emplace_back(atof(m_value[1].c_str())); + m_object_vertices.emplace_back(atof(m_value[2].c_str())); + m_value[0].clear(); + m_value[1].clear(); + m_value[2].clear(); + break; + + // Faces of the current volume: + case NODE_TYPE_TRIANGLE: + assert(m_object && m_volume); + m_volume_facets.push_back(atoi(m_value[0].c_str())); + m_volume_facets.push_back(atoi(m_value[1].c_str())); + m_volume_facets.push_back(atoi(m_value[2].c_str())); + m_value[0].clear(); + m_value[1].clear(); + m_value[2].clear(); + break; + + // Closing the current volume. Create an STL from m_volume_facets pointing to m_object_vertices. + case NODE_TYPE_VOLUME: + { + assert(m_object && m_volume); + stl_file &stl = m_volume->mesh.stl; + stl.stats.type = inmemory; + stl.stats.number_of_facets = int(m_volume_facets.size() / 3); + stl.stats.original_num_facets = stl.stats.number_of_facets; + stl_allocate(&stl); + for (size_t i = 0; i < m_volume_facets.size();) { + stl_facet &facet = stl.facet_start[i/3]; + for (unsigned int v = 0; v < 3; ++ v) + memcpy(&facet.vertex[v].x, &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); + } + stl_get_size(&stl); + m_volume->mesh.repair(); + m_volume_facets.clear(); + m_volume = nullptr; + break; + } + + case NODE_TYPE_OBJECT: + assert(m_object); + m_object_vertices.clear(); + m_object = nullptr; + break; + + case NODE_TYPE_MATERIAL: + assert(m_material); + m_material = nullptr; + break; + + case NODE_TYPE_INSTANCE: + assert(m_instance); + m_instance = nullptr; + break; + + case NODE_TYPE_METADATA: + if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) { + const char *opt_key = m_value[0].c_str() + 7; + if (print_config_def.options.find(opt_key) != print_config_def.options.end()) { + DynamicPrintConfig *config = nullptr; + if (m_path.size() == 3) { + if (m_path[1] == NODE_TYPE_MATERIAL && m_material) + config = &m_material->config; + else if (m_path[1] == NODE_TYPE_OBJECT && m_object) + config = &m_object->config; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) + config = &m_volume->config; + if (config) + config->set_deserialize(opt_key, m_value[1]); + } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { + // Parse object's layer height profile, a semicolon separated list of floats. + char *p = const_cast(m_value[1].c_str()); + for (;;) { + char *end = strchr(p, ';'); + if (end != nullptr) + *end = 0; + m_object->layer_height_profile.push_back(float(atof(p))); + if (end == nullptr) + break; + p = end + 1; + } + m_object->layer_height_profile_valid = true; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) { + // Is this volume a modifier volume? + m_volume->modifier = atoi(m_value[1].c_str()) == 1; + } + } else if (m_path.size() == 3) { + if (m_path[1] == NODE_TYPE_MATERIAL) { + if (m_material) + m_material->attributes[m_value[0]] = m_value[1]; + } else if (m_path[1] == NODE_TYPE_OBJECT) { + if (m_object && m_value[0] == "name") + m_object->name = std::move(m_value[1]); + } + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { + if (m_volume && m_value[0] == "name") + m_volume->name = std::move(m_value[1]); + } + m_value[0].clear(); + m_value[1].clear(); + break; + default: + break; + } + + m_path.pop_back(); +} + +void AMFParserContext::endDocument() +{ + for (const auto &object : m_object_instances_map) { + if (object.second.idx == -1) { + printf("Undefined object %s referenced in constellation\n", object.first.c_str()); + continue; + } + for (const Instance &instance : object.second.instances) + if (instance.deltax_set && instance.deltay_set) { + ModelInstance *mi = m_model.objects[object.second.idx]->add_instance(); + mi->offset.x = instance.deltax; + mi->offset.y = instance.deltay; + mi->rotation = instance.rz_set ? instance.rz : 0.f; + } + } +} + +// Load an AMF file into a provided model. +bool load_amf(const char *path, Model *model) +{ + XML_Parser parser = XML_ParserCreate(nullptr); // encoding + if (! parser) { + printf("Couldn't allocate memory for parser\n"); + return false; + } + + FILE *pFile = ::fopen(path, "rt"); + if (pFile == nullptr) { + printf("Cannot open file %s\n", path); + return false; + } + + AMFParserContext ctx(parser, model); + XML_SetUserData(parser, (void*)&ctx); + XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); + XML_SetCharacterDataHandler(parser, AMFParserContext::characters); + + char buff[8192]; + bool result = false; + for (;;) { + int len = (int)fread(buff, 1, 8192, pFile); + if (ferror(pFile)) { + printf("AMF parser: Read error\n"); + break; + } + int done = feof(pFile); + if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) { + printf("AMF parser: Parse error at line %u:\n%s\n", + XML_GetCurrentLineNumber(parser), + XML_ErrorString(XML_GetErrorCode(parser))); + break; + } + if (done) { + result = true; + break; + } + } + + XML_ParserFree(parser); + ::fclose(pFile); + + if (result) + ctx.endDocument(); + return result; +} + +bool store_amf(const char *path, Model *model) +{ + FILE *file = ::fopen(path, "wb"); + if (file == nullptr) + return false; + + fprintf(file, "\n"); + fprintf(file, "\n"); + fprintf(file, "Slic3r %s\n", SLIC3R_VERSION); + for (const auto &material : model->materials) { + if (material.first.empty()) + continue; + // note that material-id must never be 0 since it's reserved by the AMF spec + fprintf(file, " \n", material.first.c_str()); + for (const auto &attr : material.second->attributes) + fprintf(file, " %s\n", attr.first.c_str(), attr.second.c_str()); + for (const std::string &key : material.second->config.keys()) + fprintf(file, " %s\n", key.c_str(), material.second->config.serialize(key)); + fprintf(file, " \n"); + } + std::string instances; + for (size_t object_id = 0; object_id < model->objects.size(); ++ object_id) { + ModelObject *object = model->objects[object_id]; + fprintf(file, " \n", object_id); + for (const std::string &key : object->config.keys()) + fprintf(file, " %s\n", key.c_str(), object->config.serialize(key)); + if (! object->name.empty()) + fprintf(file, " %s\n", object->name.c_str()); + std::vector layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector(); + if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) { + // Store the layer height profile as a single semicolon separated list. + fprintf(file, " "); + fprintf(file, "%f", layer_height_profile.front()); + for (size_t i = 1; i < layer_height_profile.size(); ++ i) + fprintf(file, ",%f", layer_height_profile[i]); + fprintf(file, "\n \n"); + } + //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) + fprintf(file, " \n"); + fprintf(file, " \n"); + std::vector vertices_offsets; + int num_vertices = 0; + for (ModelVolume *volume : object->volumes) { + vertices_offsets.push_back(num_vertices); + if (! volume->mesh.repaired) + CONFESS("store_amf() requires repair()"); + auto &stl = volume->mesh.stl; + if (stl.v_shared == nullptr) + stl_generate_shared_vertices(&stl); + for (size_t i = 0; i < stl.stats.shared_vertices; ++ i) { + fprintf(file, " \n"); + fprintf(file, " \n"); + fprintf(file, " %s\n", stl.v_shared[i].x); + fprintf(file, " %s\n", stl.v_shared[i].x); + fprintf(file, " %s\n", stl.v_shared[i].x); + fprintf(file, " \n"); + fprintf(file, " \n"); + } + num_vertices += stl.stats.shared_vertices; + } + fprintf(file, " \n"); + for (size_t i_volume = 0; i_volume < object->volumes.size(); ++ i_volume) { + ModelVolume *volume = object->volumes[i_volume]; + int vertices_offset = vertices_offsets[i_volume]; + if (volume->material_id().empty()) + fprintf(file, " \n"); + else + fprintf(file, " \n", volume->material_id().c_str()); + for (const std::string &key : volume->config.keys()) + fprintf(file, " %s\n", key.c_str(), volume->config.serialize(key)); + if (! volume->name.empty()) + fprintf(file, " %s\n", volume->name.c_str()); + if (volume->modifier) + fprintf(file, " 1\n"); + for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++ i) { + fprintf(file, " \n"); + for (int j = 0; j < 3; ++ j) + fprintf(file, " %d\n", j, volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset, j); + fprintf(file, " \n"); + } + fprintf(file, " \n"); + } + fprintf(file, " \n"); + fprintf(file, " \n"); + if (! object->instances.empty()) { + for (ModelInstance *instance : object->instances) { + char buf[512]; + sprintf(buf, + " \n" + " %s\n" + " %s\n" + " %s\n" + " \n", + object_id, + instance->offset.x, + instance->offset.x, + instance->rotation); + //FIXME missing instance->scaling_factor + instances.append(buf); + } + } + } + if (! instances.empty()) { + fprintf(file, " \n"); + fwrite(instances.data(), instances.size(), 1, file); + fprintf(file, " \n"); + } + fprintf(file, "\n"); + fclose(file); + return true; +} + +}; // namespace Slic3r diff --git a/xs/src/libslic3r/Format/AMF.hpp b/xs/src/libslic3r/Format/AMF.hpp new file mode 100644 index 000000000..e58ce2f0f --- /dev/null +++ b/xs/src/libslic3r/Format/AMF.hpp @@ -0,0 +1,15 @@ +#ifndef slic3r_Format_AMF_hpp_ +#define slic3r_Format_AMF_hpp_ + +namespace Slic3r { + +class Model; + +// Load an AMF file into a provided model. +extern bool load_amf(const char *path, Model *model); + +extern bool store_amf(const char *path, Model *model); + +}; // namespace Slic3r + +#endif /* slic3r_Format_AMF_hpp_ */ \ No newline at end of file diff --git a/xs/src/libslic3r/Format/OBJ.cpp b/xs/src/libslic3r/Format/OBJ.cpp new file mode 100644 index 000000000..b79587d88 --- /dev/null +++ b/xs/src/libslic3r/Format/OBJ.cpp @@ -0,0 +1,79 @@ +#include "../libslic3r.h" +#include "../Model.hpp" +#include "../TriangleMesh.hpp" + +#include "OBJ.hpp" +#include "objparser.hpp" + +#include + +#ifdef _WIN32 +#define DIR_SEPARATOR '\\' +#else +#define DIR_SEPARATOR '/' +#endif + +namespace Slic3r { + +bool load_obj(const char *path, Model *model, const char *object_name_in) +{ + // Parse the OBJ file. + ObjParser::ObjData data; + if (! ObjParser::objparse(path, data)) { +// die "Failed to parse $file\n" if !-e $path; + return false; + } + + // Count the faces and verify, that all faces are triangular. + size_t num_faces = 0; + for (size_t i = 0; i < data.vertices.size(); ) { + size_t j = i; + for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ; + if (i == j) + continue; + if (j - i != 3) { + // Non-triangular faces are not supported as of now. + return false; + } + num_faces ++; + i = j; + } + + // Convert ObjData into STL. + TriangleMesh mesh; + stl_file &stl = mesh.stl; + stl.stats.type = inmemory; + stl.stats.number_of_facets = num_faces; + stl.stats.original_num_facets = num_faces; + stl_allocate(&stl); + size_t i_face = 0; + for (size_t i = 0; i < data.vertices.size(); ++ i) { + if (data.vertices[i].coordIdx == -1) + continue; + stl_facet &facet = stl.facet_start[i_face ++]; + for (unsigned int v = 0; v < 3; ++ v) { + const ObjParser::ObjVertex &vertex = data.vertices[i++]; + memcpy(&facet.vertex[v].x, &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float)); + if (vertex.normalIdx != -1) + memcpy(&facet.normal.x, &data.normals[vertex.normalIdx*3], 3 * sizeof(float)); + } + } + stl_get_size(&stl); + mesh.repair(); + if (mesh.facets_count() == 0) { + // die "This STL file couldn't be read because it's empty.\n" + return false; + } + + std::string object_name; + if (object_name_in == nullptr) { + const char *last_slash = strrchr(path, DIR_SEPARATOR); + object_name.assign((last_slash == nullptr) ? path : last_slash + 1); + } else + object_name.assign(object_name_in); + + model->add_object(object_name.c_str(), path, std::move(mesh)); + return true; +} + +}; // namespace Slic3r diff --git a/xs/src/libslic3r/Format/OBJ.hpp b/xs/src/libslic3r/Format/OBJ.hpp new file mode 100644 index 000000000..9a8790bff --- /dev/null +++ b/xs/src/libslic3r/Format/OBJ.hpp @@ -0,0 +1,14 @@ +#ifndef slic3r_Format_OBJ_hpp_ +#define slic3r_Format_OBJ_hpp_ + +namespace Slic3r { + +class TriangleMesh; +class Model; + +// Load an OBJ file into a provided model. +extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr); + +}; // namespace Slic3r + +#endif /* slic3r_Format_OBJ_hpp_ */ diff --git a/xs/src/libslic3r/Format/PRUS.cpp b/xs/src/libslic3r/Format/PRUS.cpp new file mode 100644 index 000000000..d21a5537d --- /dev/null +++ b/xs/src/libslic3r/Format/PRUS.cpp @@ -0,0 +1,306 @@ +#ifdef SLIC3R_PRUS + +#include + +#include +#include +#include + +#include "../libslic3r.h" +#include "../Model.hpp" + +#include "PRUS.hpp" + +#if 0 +// Enable debugging and assert in this file. +#define DEBUG +#define _DEBUG +#undef NDEBUG +#endif + +#include + +namespace Slic3r +{ + +struct StlHeader +{ + char comment[80]; + uint32_t nTriangles; +}; + +// Buffered line reader for the wxInputStream. +class LineReader +{ +public: + LineReader(wxInputStream &input_stream, const char *initial_data, int initial_len) : + 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() { + for (;;) { + // Skip empty lines. + while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')) + ++ m_pos; + 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. + 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. + int end = m_pos + 1; + while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n') + ++ end; + if (end == m_len && ! m_input_stream.Eof() && m_len < m_bufsize) { + // 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_buffer[end] = 0; + if (m_pos >= m_len) { + m_pos = 0; + m_len = 0; + } + return ptr_out; + } + } + + int next_line_scanf(char *format, ...) + { + const char *line = next_line(); + if (line == nullptr) + return -1; + int result; + va_list arglist; + va_start(arglist, format); + result = vsscanf(line, format, arglist); + va_end(arglist); + return result; + } + +private: + wxInputStream &m_input_stream; + static const int m_bufsize = 4096; + char m_buffer[m_bufsize]; + int m_pos = 0; + int m_len = 0; +}; + +// Load a PrusaControl project file into a provided model. +bool load_prus(const char *path, Model *model) +{ + // To receive the content of the zipped 'scene.xml' file. + std::vector scene_xml_data; + wxFFileInputStream in(path); + wxZipInputStream zip(in); + std::unique_ptr entry; + size_t num_models = 0; + 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 += 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. + const wxScopedCharBuffer name_utf8 = name.ToUTF8(); + char model_name_tag[1024]; + sprintf(model_name_tag, "", name_utf8.data()); + const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); + float trafo[3][4] = { 0 }; + bool trafo_set = false; + if (model_xml != nullptr) { + model_xml += strlen(model_name_tag); + const char *position_tag = ""; + const char *position_xml = strstr(model_xml, position_tag); + const char *rotation_tag = ""; + const char *rotation_xml = strstr(model_xml, rotation_tag); + const char *scale_tag = ""; + const char *scale_xml = strstr(model_xml, scale_tag); + float position[3], rotation[3][3], scale[3][3]; + if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && + sscanf(position_xml+strlen(position_tag), + "[%f, %f, %f]", position, position+1, position+2) == 3 && + sscanf(rotation_xml+strlen(rotation_tag), + "[[%f, %f, %f], [%f, %f, %f], [%f, %f, %f]]", + rotation[0], rotation[0]+1, rotation[0]+2, + rotation[1], rotation[1]+1, rotation[1]+2, + rotation[2], rotation[2]+1, rotation[2]+2) == 9 && + sscanf(scale_xml+strlen(scale_tag), + "[[%f, %f, %f], [%f, %f, %f], [%f, %f, %f]]", + scale[0], scale[0]+1, scale[0]+2, + scale[1], scale[1]+1, scale[1]+2, + scale[2], scale[2]+1, scale[2]+2) == 9) { + for (size_t r = 0; r < 3; ++ r) { + for (size_t c = 0; c < 3; ++ c) { + for (size_t i = 0; i < 3; ++ i) + trafo[r][c] += rotation[r][i]*scale[i][c]; + } + trafo[r][3] = position[r]; + } + trafo_set = true; + } + } + if (trafo_set) { + // Extract the STL. + StlHeader header; + bool stl_ascii = false; + if (!zip.Read((void*)&header, sizeof(StlHeader)).Eof()) { + if (strncmp(header.comment, "solid ", 6) == 0) + stl_ascii = true; + else { + // Header has been extracted. Now read the faces. + TriangleMesh mesh; + stl_file &stl = mesh.stl; + stl.error = 0; + stl.stats.type = inmemory; + stl.stats.number_of_facets = header.nTriangles; + stl.stats.original_num_facets = header.nTriangles; + stl_allocate(&stl); + if (zip.ReadAll((void*)stl.facet_start, 50 * header.nTriangles)) { + // All the faces have been read. + stl_get_size(&stl); + mesh.repair(); + // Transform the model. + stl_transform(&stl, &trafo[0][0]); + // Add a mesh to a model. + if (mesh.facets_count() > 0) { + model->add_object(name_utf8.data(), path, std::move(mesh)); + ++ num_models; + } + } + } + } else + stl_ascii = true; + if (stl_ascii) { + // Try to parse ASCII STL. + char normal_buf[3][32]; + stl_facet facet; + std::vector facets; + LineReader line_reader(zip, (char*)&header, zip.LastRead()); + std::string solid_name; + facet.extra[0] = facet.extra[1] = 0; + for (;;) { + const char *line = line_reader.next_line(); + if (line == nullptr) + // End of file. + break; + if (strncmp(line, "solid", 5) == 0) { + // Opening the "solid" block. + if (! solid_name.empty()) { + // Error, solid block is already open. + facets.clear(); + break; + } + solid_name = line + 5; + if (solid_name.empty()) + solid_name = "unknown"; + continue; + } + if (strncmp(line, "endsolid", 8) == 0) { + // Closing the "solid" block. + if (solid_name.empty()) { + // Error, no solid block is open. + facets.clear(); + break; + } + solid_name.clear(); + continue; + } + // Line has to start with the word solid. + int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); + assert(res_normal == 3); + int res_outer_loop = line_reader.next_line_scanf(" outer loop"); + assert(res_outer_loop == 0); + int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z); + assert(res_vertex1 == 3); + int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z); + assert(res_vertex2 == 3); + int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z); + assert(res_vertex3 == 3); + int res_endloop = line_reader.next_line_scanf(" endloop"); + assert(res_endloop == 0); + int res_endfacet = line_reader.next_line_scanf(" endfacet"); + if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { + // perror("Something is syntactically very wrong with this ASCII STL!"); + facets.clear(); + break; + } + // 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.x) != 1 || + sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 || + sscanf(normal_buf[2], "%f", &facet.normal.z) != 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)); + } + facets.emplace_back(facet); + } + if (! facets.empty() && solid_name.empty()) { + TriangleMesh mesh; + stl_file &stl = mesh.stl; + stl.stats.type = inmemory; + stl.stats.number_of_facets = facets.size(); + stl.stats.original_num_facets = facets.size(); + stl_allocate(&stl); + memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); + stl_get_size(&stl); + mesh.repair(); + // Transform the model. + stl_transform(&stl, &trafo[0][0]); + // Add a mesh to a model. + if (mesh.facets_count() > 0) { + model->add_object(name_utf8.data(), path, std::move(mesh)); + ++ num_models; + } + } + } + } + } + } + return num_models > 0; +} + +}; // namespace Slic3r + +#endif /* SLIC3R_PRUS */ diff --git a/xs/src/libslic3r/Format/PRUS.hpp b/xs/src/libslic3r/Format/PRUS.hpp new file mode 100644 index 000000000..8559a70d6 --- /dev/null +++ b/xs/src/libslic3r/Format/PRUS.hpp @@ -0,0 +1,14 @@ +#if defined(SLIC3R_PRUS) && ! defined(slic3r_Format_PRUS_hpp_) +#define slic3r_Format_PRUS_hpp_ + +namespace Slic3r { + +class TriangleMesh; +class Model; + +// Load a PrusaControl project file into a provided model. +extern bool load_prus(const char *path, Model *model); + +}; // namespace Slic3r + +#endif /* SLIC3R_PRUS && ! defined(slic3r_Format_PRUS_hpp_) */ diff --git a/xs/src/libslic3r/Format/STL.cpp b/xs/src/libslic3r/Format/STL.cpp new file mode 100644 index 000000000..99e2ff193 --- /dev/null +++ b/xs/src/libslic3r/Format/STL.cpp @@ -0,0 +1,58 @@ +#include "../libslic3r.h" +#include "../Model.hpp" +#include "../TriangleMesh.hpp" + +#include "STL.hpp" + +#include + +#ifdef _WIN32 +#define DIR_SEPARATOR '\\' +#else +#define DIR_SEPARATOR '/' +#endif + +namespace Slic3r { + +bool load_stl(const char *path, Model *model, const char *object_name_in) +{ + TriangleMesh mesh; + mesh.ReadSTLFile(path); + if (mesh.stl.error) { +// die "Failed to open $file\n" if !-e $path; + return false; + } + mesh.repair(); + if (mesh.facets_count() == 0) { + // die "This STL file couldn't be read because it's empty.\n" + return false; + } + + std::string object_name; + if (object_name_in == nullptr) { + const char *last_slash = strrchr(path, DIR_SEPARATOR); + object_name.assign((last_slash == nullptr) ? path : last_slash + 1); + } else + object_name.assign(object_name_in); + + model->add_object(object_name.c_str(), path, std::move(mesh)); + return true; +} + +bool store_stl(const char *path, TriangleMesh *mesh, bool binary) +{ + if (binary) + mesh->write_binary(path); + else + mesh->write_ascii(path); + //FIXME returning false even if write failed. + return true; +} + +bool store_stl(const char *path, ModelObject *model_object, bool binary) +{ + TriangleMesh mesh = model_object->mesh(); + return store_stl(path, &mesh, binary); +} + +}; // namespace Slic3r diff --git a/xs/src/libslic3r/Format/STL.hpp b/xs/src/libslic3r/Format/STL.hpp new file mode 100644 index 000000000..aecb45183 --- /dev/null +++ b/xs/src/libslic3r/Format/STL.hpp @@ -0,0 +1,17 @@ +#ifndef slic3r_Format_STL_hpp_ +#define slic3r_Format_STL_hpp_ + +namespace Slic3r { + +class TriangleMesh; +class ModelObject; + +// Load an STL file into a provided model. +extern bool load_stl(const char *path, Model *model, const char *object_name = nullptr); + +extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary); +extern bool store_stl(const char *path, ModelObject *model_object); + +}; // namespace Slic3r + +#endif /* slic3r_Format_STL_hpp_ */ diff --git a/xs/src/libslic3r/Format/objparser.cpp b/xs/src/libslic3r/Format/objparser.cpp new file mode 100644 index 000000000..de622799e --- /dev/null +++ b/xs/src/libslic3r/Format/objparser.cpp @@ -0,0 +1,537 @@ +#include + +#include "objparser.hpp" + +namespace ObjParser { + +static bool obj_parseline(const char *line, ObjData &data) +{ +#define EATWS() while (*line == ' ' || *line == '\t') ++ line + + if (*line == 0) + return true; + + // Ignore whitespaces at the beginning of the line. + //FIXME is this a good idea? + EATWS(); + + char c1 = *line ++; + switch (c1) { + case '#': + // Comment, ignore the rest of the line. + break; + case 'v': + { + // Parse vertex geometry (position, normal, texture coordinates) + char c2 = *line ++; + switch (c2) { + case 't': + { + // vt - vertex texture parameter + // u v [w], w == 0 (or w == 1) + char c2 = *line ++; + if (c2 != ' ' && c2 != '\t') + return false; + EATWS(); + char *endptr = 0; + double u = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) + return false; + line = endptr; + EATWS(); + double v = 0; + if (*line != 0) { + v = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + } + double w = 0; + if (*line != 0) { + w = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + } + if (*line != 0) + return false; + data.textureCoordinates.push_back((float)u); + data.textureCoordinates.push_back((float)v); + data.textureCoordinates.push_back((float)w); + break; + } + case 'n': + { + // vn - vertex normal + // x y z + char c2 = *line ++; + if (c2 != ' ' && c2 != '\t') + return false; + EATWS(); + char *endptr = 0; + double x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) + return false; + line = endptr; + EATWS(); + double y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) + return false; + line = endptr; + EATWS(); + double z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + if (*line != 0) + return false; + data.normals.push_back((float)x); + data.normals.push_back((float)y); + data.normals.push_back((float)z); + break; + } + case 'p': + { + // vp - vertex parameter + char c2 = *line ++; + if (c2 != ' ' && c2 != '\t') + return false; + EATWS(); + char *endptr = 0; + double u = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + double v = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + double w = 0; + if (*line != 0) { + w = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + } + if (*line != 0) + return false; + data.parameters.push_back((float)u); + data.parameters.push_back((float)v); + data.parameters.push_back((float)w); + break; + } + default: + { + // v - vertex geometry + if (c2 != ' ' && c2 != '\t') + return false; + EATWS(); + char *endptr = 0; + double x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) + return false; + line = endptr; + EATWS(); + double y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) + return false; + line = endptr; + EATWS(); + double z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + double w = 1.0; + if (*line != 0) { + w = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + } + if (*line != 0) + return false; + data.coordinates.push_back((float)x); + data.coordinates.push_back((float)y); + data.coordinates.push_back((float)z); + data.coordinates.push_back((float)w); + break; + } + } + break; + } + case 'f': + { + // face + EATWS(); + if (*line == 0) + return false; + // number of vertices of this face + int n = 0; + // current vertex to be parsed + ObjVertex vertex; + char *endptr = 0; + while (*line != 0) { + // Parse a single vertex reference. + vertex.coordIdx = 0; + vertex.normalIdx = 0; + vertex.textureCoordIdx = 0; + vertex.coordIdx = strtol(line, &endptr, 10); + // Coordinate has to be defined + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0)) + return false; + line = endptr; + if (*line == '/') { + ++ line; + // Texture coordinate index may be missing after a 1st slash, but then the normal index has to be present. + if (*line != '/') { + // Parse the texture coordinate index. + vertex.textureCoordIdx = strtol(line, &endptr, 10); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0)) + return false; + line = endptr; + } + if (*line == '/') { + // Parse normal index. + ++ line; + vertex.normalIdx = strtol(line, &endptr, 10); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + } + } + if (vertex.coordIdx < 0) + vertex.coordIdx += data.coordinates.size() / 4; + else + -- vertex.coordIdx; + if (vertex.normalIdx < 0) + vertex.normalIdx += data.normals.size() / 3; + else + -- vertex.normalIdx; + if (vertex.textureCoordIdx < 0) + vertex.textureCoordIdx += data.textureCoordinates.size() / 3; + else + -- vertex.textureCoordIdx; + data.vertices.push_back(vertex); + EATWS(); + } + vertex.coordIdx = -1; + vertex.normalIdx = -1; + vertex.textureCoordIdx = -1; + data.vertices.push_back(vertex); + break; + } + case 'm': + { + if (*(line ++) != 't' || + *(line ++) != 'l' || + *(line ++) != 'l' || + *(line ++) != 'i' || + *(line ++) != 'b') + return false; + // mtllib [external .mtl file name] + // printf("mtllib %s\r\n", line); + EATWS(); + data.mtllibs.push_back(std::string(line)); + break; + } + case 'u': + { + if (*(line ++) != 's' || + *(line ++) != 'e' || + *(line ++) != 'm' || + *(line ++) != 't' || + *(line ++) != 'l') + return false; + // usemtl [material name] + // printf("usemtl %s\r\n", line); + EATWS(); + ObjUseMtl usemtl; + usemtl.vertexIdxFirst = data.vertices.size(); + usemtl.name = line; + data.usemtls.push_back(usemtl); + break; + } + case 'o': + { + // o [object name] + EATWS(); + const char *name = line; + while (*line != ' ' && *line != '\t' && *line != 0) + ++ line; + // copy name to line. + EATWS(); + if (*line != 0) + return false; + ObjObject object; + object.vertexIdxFirst = data.vertices.size(); + object.name = line; + data.objects.push_back(object); + break; + } + case 'g': + { + // g [group name] + // printf("group %s\r\n", line); + ObjGroup group; + group.vertexIdxFirst = data.vertices.size(); + group.name = line; + data.groups.push_back(group); + break; + } + case 's': + { + // s 1 / off + char c2 = *line ++; + if (c2 != ' ' && c2 != '\t') + return false; + EATWS(); + char *endptr = 0; + long g = strtol(line, &endptr, 10); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + if (*line != 0) + return false; + ObjSmoothingGroup group; + group.vertexIdxFirst = data.vertices.size(); + group.smoothingGroupID = g; + data.smoothingGroups.push_back(group); + break; + } + default: + printf("ObjParser: Unknown command: %c\r\n", c1); + break; + } + + return true; +} + +bool objparse(const char *path, ObjData &data) +{ + FILE *pFile = ::fopen(path, "rt"); + if (pFile == 0) + return false; + + try { + char buf[65536 * 2]; + size_t len = 0; + size_t lenPrev = 0; + while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) { + len += lenPrev; + size_t lastLine = 0; + for (size_t i = 0; i < len; ++ i) + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = 0; + char *c = buf + lastLine; + while (*c == ' ' || *c == '\t') + ++ c; + obj_parseline(c, data); + lastLine = i + 1; + } + lenPrev = len - lastLine; + memmove(buf, buf + lastLine, lenPrev); + } + } catch (std::bad_alloc &ex) { + printf("Out of memory\r\n"); + } + ::fclose(pFile); + + printf("vertices: %d\r\n", data.vertices.size() / 4); + printf("coords: %d\r\n", data.coordinates.size()); + return true; +} + +template +bool savevector(FILE *pFile, const std::vector &v) +{ + size_t cnt = v.size(); + ::fwrite(&cnt, 1, sizeof(cnt), pFile); + //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type. + if (! v.empty()) + ::fwrite(&v.front(), 1, sizeof(T) * cnt, pFile); + return true; +} + +bool savevector(FILE *pFile, const std::vector &v) +{ + size_t cnt = v.size(); + ::fwrite(&cnt, 1, sizeof(cnt), pFile); + for (size_t i = 0; i < cnt; ++ i) { + size_t len = v[i].size(); + ::fwrite(&len, 1, sizeof(cnt), pFile); + ::fwrite(v[i].c_str(), 1, len, pFile); + } + return true; +} + +template +bool savevectornameidx(FILE *pFile, const std::vector &v) +{ + size_t cnt = v.size(); + ::fwrite(&cnt, 1, sizeof(cnt), pFile); + for (size_t i = 0; i < cnt; ++ i) { + ::fwrite(&v[i].vertexIdxFirst, 1, sizeof(int), pFile); + size_t len = v[i].name.size(); + ::fwrite(&len, 1, sizeof(cnt), pFile); + ::fwrite(v[i].name.c_str(), 1, len, pFile); + } + return true; +} + +template +bool loadvector(FILE *pFile, std::vector &v) +{ + v.clear(); + size_t cnt = 0; + if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) + return false; + //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type. + if (cnt != 0) { + v.assign(cnt, T()); + if (::fread(&v.front(), sizeof(T), cnt, pFile) != cnt) + return false; + } + return true; +} + +bool loadvector(FILE *pFile, std::vector &v) +{ + v.clear(); + size_t cnt = 0; + if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) + return false; + v.reserve(cnt); + for (size_t i = 0; i < cnt; ++ i) { + size_t len = 0; + if (::fread(&len, sizeof(len), 1, pFile) != 1) + return false; + std::string s(" ", len); + if (::fread(const_cast(s.c_str()), 1, len, pFile) != len) + return false; + v.push_back(std::move(s)); + } + return true; +} + +template +bool loadvectornameidx(FILE *pFile, std::vector &v) +{ + v.clear(); + size_t cnt = 0; + if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) + return false; + v.assign(cnt, T()); + for (size_t i = 0; i < cnt; ++ i) { + if (::fread(&v[i].vertexIdxFirst, sizeof(int), 1, pFile) != 1) + return false; + size_t len = 0; + if (::fread(&len, sizeof(len), 1, pFile) != 1) + return false; + v[i].name.assign(" ", len); + if (::fread(const_cast(v[i].name.c_str()), 1, len, pFile) != len) + return false; + } + return true; +} + +bool objbinsave(const char *path, const ObjData &data) +{ + FILE *pFile = ::fopen(path, "wb"); + if (pFile == 0) + return false; + + size_t version = 1; + ::fwrite(&version, 1, sizeof(version), pFile); + + bool result = + savevector(pFile, data.coordinates) && + savevector(pFile, data.textureCoordinates) && + savevector(pFile, data.normals) && + savevector(pFile, data.parameters) && + savevector(pFile, data.mtllibs) && + savevectornameidx(pFile, data.usemtls) && + savevectornameidx(pFile, data.objects) && + savevectornameidx(pFile, data.groups) && + savevector(pFile, data.smoothingGroups) && + savevector(pFile, data.vertices); + + ::fclose(pFile); + return result; +} + +bool objbinload(const char *path, ObjData &data) +{ + FILE *pFile = ::fopen(path, "rb"); + if (pFile == 0) + return false; + + data.version = 0; + if (::fread(&data.version, sizeof(data.version), 1, pFile) != 1) + return false; + if (data.version != 1) + return false; + + bool result = + loadvector(pFile, data.coordinates) && + loadvector(pFile, data.textureCoordinates) && + loadvector(pFile, data.normals) && + loadvector(pFile, data.parameters) && + loadvector(pFile, data.mtllibs) && + loadvectornameidx(pFile, data.usemtls) && + loadvectornameidx(pFile, data.objects) && + loadvectornameidx(pFile, data.groups) && + loadvector(pFile, data.smoothingGroups) && + loadvector(pFile, data.vertices); + + ::fclose(pFile); + return result; +} + +template +bool vectorequal(const std::vector &v1, const std::vector &v2) +{ + if (v1.size() != v2.size()) + return false; + for (size_t i = 0; i < v1.size(); ++ i) + if (! (v1[i] == v2[i])) + return false; + return true; +} + +bool vectorequal(const std::vector &v1, const std::vector &v2) +{ + if (v1.size() != v2.size()) + return false; + for (size_t i = 0; i < v1.size(); ++ i) + if (v1[i].compare(v2[i]) != 0) + return false; + return true; +} + +extern bool objequal(const ObjData &data1, const ObjData &data2) +{ + //FIXME ignore version number + // version; + + return + vectorequal(data1.coordinates, data2.coordinates) && + vectorequal(data1.textureCoordinates, data2.textureCoordinates) && + vectorequal(data1.normals, data2.normals) && + vectorequal(data1.parameters, data2.parameters) && + vectorequal(data1.mtllibs, data2.mtllibs) && + vectorequal(data1.usemtls, data2.usemtls) && + vectorequal(data1.objects, data2.objects) && + vectorequal(data1.groups, data2.groups) && + vectorequal(data1.vertices, data2.vertices); +} + +} // namespace ObjParser diff --git a/xs/src/libslic3r/Format/objparser.hpp b/xs/src/libslic3r/Format/objparser.hpp new file mode 100644 index 000000000..8950122b2 --- /dev/null +++ b/xs/src/libslic3r/Format/objparser.hpp @@ -0,0 +1,109 @@ +#ifndef slic3r_Format_objparser_hpp_ +#define slic3r_Format_objparser_hpp_ + +#include +#include + +namespace ObjParser { + +struct ObjVertex +{ + int coordIdx; + int textureCoordIdx; + int normalIdx; +}; + +inline bool operator==(const ObjVertex &v1, const ObjVertex &v2) +{ + return + v1.coordIdx == v2.coordIdx && + v1.textureCoordIdx == v2.textureCoordIdx && + v1.normalIdx == v2.normalIdx; +} + +struct ObjUseMtl +{ + int vertexIdxFirst; + std::string name; +}; + +inline bool operator==(const ObjUseMtl &v1, const ObjUseMtl &v2) +{ + return + v1.vertexIdxFirst == v2.vertexIdxFirst && + v1.name.compare(v2.name) == 0; +} + +struct ObjObject +{ + int vertexIdxFirst; + std::string name; +}; + +inline bool operator==(const ObjObject &v1, const ObjObject &v2) +{ + return + v1.vertexIdxFirst == v2.vertexIdxFirst && + v1.name.compare(v2.name) == 0; +} + +struct ObjGroup +{ + int vertexIdxFirst; + std::string name; +}; + +inline bool operator==(const ObjGroup &v1, const ObjGroup &v2) +{ + return + v1.vertexIdxFirst == v2.vertexIdxFirst && + v1.name.compare(v2.name) == 0; +} + +struct ObjSmoothingGroup +{ + int vertexIdxFirst; + int smoothingGroupID; +}; + +inline bool operator==(const ObjSmoothingGroup &v1, const ObjSmoothingGroup &v2) +{ + return + v1.vertexIdxFirst == v2.vertexIdxFirst && + v1.smoothingGroupID == v2.smoothingGroupID; +} + +struct ObjData { + // Version of the data structure for load / store in the private binary format. + int version; + + // x, y, z, w + std::vector coordinates; + // u, v, w + std::vector textureCoordinates; + // x, y, z + std::vector normals; + // u, v, w + std::vector parameters; + + std::vector mtllibs; + std::vector usemtls; + std::vector objects; + std::vector groups; + std::vector smoothingGroups; + + // List of faces, delimited by an ObjVertex with all members set to -1. + std::vector vertices; +}; + +extern bool objparse(const char *path, ObjData &data); + +extern bool objbinsave(const char *path, const ObjData &data); + +extern bool objbinload(const char *path, ObjData &data); + +extern bool objequal(const ObjData &data1, const ObjData &data2); + +} // namespace ObjParser + +#endif /* slic3r_Format_objparser_hpp_ */ diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 91fc7a20b..d84c1cb29 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -5,6 +5,10 @@ #include "libslic3r/Model.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Slicing.hpp" +#include "libslic3r/Format/AMF.hpp" +#include "libslic3r/Format/OBJ.hpp" +#include "libslic3r/Format/PRUS.hpp" +#include "libslic3r/Format/STL.hpp" %} %name{Slic3r::Model} class Model { @@ -71,8 +75,68 @@ void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); -}; +%{ + +Model* +load_stl(CLASS, path, object_name) + char* CLASS; + char* path; + char* object_name; + CODE: + RETVAL = new Model(); + if (! load_stl(path, RETVAL, object_name)) { + delete RETVAL; + RETVAL = NULL; + } + OUTPUT: + RETVAL + +Model* +load_obj(CLASS, path, object_name) + char* CLASS; + char* path; + char* object_name; + CODE: + RETVAL = new Model(); + if (! load_obj(path, RETVAL, object_name)) { + delete RETVAL; + RETVAL = NULL; + } + OUTPUT: + RETVAL + +Model* +load_amf(CLASS, path) + char* CLASS; + char* path; + CODE: + RETVAL = new Model(); + if (! load_amf(path, RETVAL)) { + delete RETVAL; + RETVAL = NULL; + } + OUTPUT: + RETVAL + +Model* +load_prus(CLASS, path) + char* CLASS; + char* path; + CODE: +#ifdef SLIC3R_PRUS + RETVAL = new Model(); + if (! load_prus(path, RETVAL)) { + delete RETVAL; + RETVAL = NULL; + } +#endif + OUTPUT: + RETVAL + +%} + +}; %name{Slic3r::Model::Material} class ModelMaterial { Ref model()