mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-07 04:11:50 +03:00
update
This commit is contained in:
3278
src/libslic3r/Format/3mf.cpp
Normal file
3278
src/libslic3r/Format/3mf.cpp
Normal file
File diff suppressed because it is too large
Load Diff
64
src/libslic3r/Format/3mf.hpp
Normal file
64
src/libslic3r/Format/3mf.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef slic3r_Format_3mf_hpp_
|
||||
#define slic3r_Format_3mf_hpp_
|
||||
#include <expat.h>
|
||||
|
||||
namespace Slic3r {
|
||||
// PrusaFileParser is used to check 3mf file is from Prusa
|
||||
class PrusaFileParser
|
||||
{
|
||||
public:
|
||||
PrusaFileParser() {}
|
||||
~PrusaFileParser() {}
|
||||
|
||||
bool check_3mf_from_prusa(const std::string filename);
|
||||
void _start_element_handler(const char *name, const char **attributes);
|
||||
void _characters_handler(const XML_Char *s, int len);
|
||||
|
||||
private:
|
||||
const char *get_attribute_value_charptr(const char **attributes, unsigned int attributes_size, const char *attribute_key);
|
||||
std::string get_attribute_value_string(const char **attributes, unsigned int attributes_size, const char *attribute_key);
|
||||
|
||||
static void XMLCALL start_element_handler(void *userData, const char *name, const char **attributes);
|
||||
static void XMLCALL characters_handler(void *userData, const XML_Char *s, int len);
|
||||
private:
|
||||
bool m_from_prusa = false;
|
||||
bool m_is_application_key = false;
|
||||
XML_Parser m_parser;
|
||||
};
|
||||
|
||||
/* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used.
|
||||
* Examples of the Slic3r_PE_sla_support_points.txt for historically used versions:
|
||||
|
||||
* version 0 : object_id=1|-12.055421 -2.658771 10.000000
|
||||
object_id=2|-14.051745 -3.570338 5.000000
|
||||
// no header and x,y,z positions of the points)
|
||||
|
||||
* version 1 : ThreeMF_support_points_version=1
|
||||
object_id=1|-12.055421 -2.658771 10.000000 0.4 0.0
|
||||
object_id=2|-14.051745 -3.570338 5.000000 0.6 1.0
|
||||
// introduced header with version number; x,y,z,head_size,is_new_island)
|
||||
*/
|
||||
|
||||
enum {
|
||||
support_points_format_version = 1
|
||||
};
|
||||
|
||||
enum {
|
||||
drain_holes_format_version = 1
|
||||
};
|
||||
|
||||
class Model;
|
||||
struct ConfigSubstitutionContext;
|
||||
class DynamicPrintConfig;
|
||||
struct ThumbnailData;
|
||||
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version);
|
||||
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr, bool zip64 = true);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_3mf_hpp_ */
|
||||
1394
src/libslic3r/Format/AMF.cpp
Normal file
1394
src/libslic3r/Format/AMF.cpp
Normal file
File diff suppressed because it is too large
Load Diff
19
src/libslic3r/Format/AMF.hpp
Normal file
19
src/libslic3r/Format/AMF.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef slic3r_Format_AMF_hpp_
|
||||
#define slic3r_Format_AMF_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
// Load the content of an amf file into the given model and configuration.
|
||||
extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool* use_inches);
|
||||
|
||||
//QDS: remove amf export
|
||||
// Save the given model and the config data into an amf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
//extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_AMF_hpp_ */
|
||||
19
src/libslic3r/Format/ModelIO.hpp
Normal file
19
src/libslic3r/Format/ModelIO.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
/**
|
||||
* Uses ModelIO to convert supported model types to a temporary STL
|
||||
* that can then be consumed by the existing STL loader
|
||||
* @param input_file The File to load
|
||||
* @return Path to the temporary file, or an empty string if conversion failed
|
||||
*/
|
||||
std::string make_temp_stl_with_modelio(const std::string &input_file);
|
||||
|
||||
/**
|
||||
* Convenience function to delete the file.
|
||||
* No return value since success isn't required
|
||||
* @param temp_file File path to delete
|
||||
*/
|
||||
void delete_temp_file(const std::string &temp_file);
|
||||
}
|
||||
|
||||
27
src/libslic3r/Format/ModelIO.mm
Normal file
27
src/libslic3r/Format/ModelIO.mm
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "ModelIO.hpp"
|
||||
#import <ModelIO/ModelIO.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::string make_temp_stl_with_modelio(const std::string &input_file)
|
||||
{
|
||||
NSURL *input_url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:input_file.c_str()]];
|
||||
MDLAsset *asset = [[MDLAsset alloc] initWithURL:input_url];
|
||||
|
||||
NSString *tmp_file_name = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"stl"];
|
||||
NSURL *tmp_file_url = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tmp_file_name]];
|
||||
|
||||
if ([asset exportAssetToURL:tmp_file_url]) {
|
||||
std::string output_file = std::string([[tmp_file_url path] UTF8String]);
|
||||
return output_file;
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
void delete_temp_file(const std::string &temp_file)
|
||||
{
|
||||
NSString *file_path = [NSString stringWithUTF8String:temp_file.c_str()];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:file_path error:NULL];
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
249
src/libslic3r/Format/OBJ.cpp
Normal file
249
src/libslic3r/Format/OBJ.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
#include "OBJ.hpp"
|
||||
#include "objparser.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_SEPARATOR '\\'
|
||||
#else
|
||||
#define DIR_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
//Translation
|
||||
#include "I18N.hpp"
|
||||
#define _L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::string &message)
|
||||
{
|
||||
if (meshptr == nullptr)
|
||||
return false;
|
||||
// Parse the OBJ file.
|
||||
ObjParser::ObjData data;
|
||||
ObjParser::MtlData mtl_data;
|
||||
if (! ObjParser::objparse(path, data)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path;
|
||||
message = _L("load_obj: failed to parse");
|
||||
return false;
|
||||
}
|
||||
bool exist_mtl = false;
|
||||
if (data.mtllibs.size() > 0) { // read mtl
|
||||
for (auto mtl_name : data.mtllibs) {
|
||||
if (mtl_name.size() == 0){
|
||||
continue;
|
||||
}
|
||||
exist_mtl = true;
|
||||
bool mtl_name_is_path = false;
|
||||
boost::filesystem::path mtl_abs_path(mtl_name);
|
||||
if (boost::filesystem::exists(mtl_abs_path)) {
|
||||
mtl_name_is_path = true;
|
||||
}
|
||||
boost::filesystem::path mtl_path;
|
||||
if (!mtl_name_is_path) {
|
||||
boost::filesystem::path full_path(path);
|
||||
std::string dir = full_path.parent_path().string();
|
||||
auto mtl_file = dir + "/" + mtl_name;
|
||||
boost::filesystem::path temp_mtl_path(mtl_file);
|
||||
mtl_path = temp_mtl_path;
|
||||
}
|
||||
auto _mtl_path = mtl_name_is_path ? mtl_abs_path.string().c_str() : mtl_path.string().c_str();
|
||||
if (boost::filesystem::exists(mtl_name_is_path ? mtl_abs_path : mtl_path)) {
|
||||
if (!ObjParser::mtlparse(_mtl_path, mtl_data)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj:load_mtl: failed to parse " << _mtl_path;
|
||||
message = _L("load mtl in obj: failed to parse");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to load mtl_path:" << _mtl_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Count the faces and verify, that all faces are triangular.
|
||||
size_t num_faces = 0;
|
||||
size_t num_quads = 0;
|
||||
for (size_t i = 0; i < data.vertices.size(); ++ i) {
|
||||
// Find the end of face.
|
||||
size_t j = i;
|
||||
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
|
||||
if (size_t num_face_vertices = j - i; num_face_vertices > 0) {
|
||||
if (num_face_vertices > 4) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with more than 4 vertices.";
|
||||
message = _L("The file contains polygons with more than 4 vertices.");
|
||||
return false;
|
||||
} else if (num_face_vertices < 3) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with less than 2 vertices.";
|
||||
message = _L("The file contains polygons with less than 2 vertices.");
|
||||
return false;
|
||||
}
|
||||
if (num_face_vertices == 4)
|
||||
++ num_quads;
|
||||
++ num_faces;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
// Convert ObjData into indexed triangle set.
|
||||
indexed_triangle_set its;
|
||||
size_t num_vertices = data.coordinates.size() / OBJ_VERTEX_LENGTH;
|
||||
its.vertices.reserve(num_vertices);
|
||||
its.indices.reserve(num_faces + num_quads);
|
||||
if (exist_mtl) {
|
||||
obj_info.is_single_mtl = data.usemtls.size() == 1 && mtl_data.new_mtl_unmap.size() == 1;
|
||||
obj_info.face_colors.reserve(num_faces + num_quads);
|
||||
}
|
||||
bool has_color = data.has_vertex_color;
|
||||
for (size_t i = 0; i < num_vertices; ++ i) {
|
||||
size_t j = i * OBJ_VERTEX_LENGTH;
|
||||
its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]);
|
||||
if (data.has_vertex_color) {
|
||||
RGBA color{std::clamp(data.coordinates[j + 3], 0.f, 1.f), std::clamp(data.coordinates[j + 4], 0.f, 1.f), std::clamp(data.coordinates[j + 5], 0.f, 1.f),
|
||||
std::clamp(data.coordinates[j + 6], 0.f, 1.f)};
|
||||
obj_info.vertex_colors.emplace_back(color);
|
||||
}
|
||||
}
|
||||
int indices[ONE_FACE_SIZE];
|
||||
int uvs[ONE_FACE_SIZE];
|
||||
for (size_t i = 0; i < data.vertices.size();)
|
||||
if (data.vertices[i].coordIdx == -1)
|
||||
++ i;
|
||||
else {
|
||||
int cnt = 0;
|
||||
while (i < data.vertices.size())
|
||||
if (const ObjParser::ObjVertex &vertex = data.vertices[i ++]; vertex.coordIdx == -1) {
|
||||
break;
|
||||
} else {
|
||||
assert(cnt < OBJ_VERTEX_LENGTH);
|
||||
if (vertex.coordIdx < 0 || vertex.coordIdx >= int(its.vertices.size())) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains invalid vertex index.";
|
||||
message = _L("The file contains invalid vertex index.");
|
||||
return false;
|
||||
}
|
||||
indices[cnt] = vertex.coordIdx;
|
||||
uvs[cnt] = vertex.textureCoordIdx;
|
||||
cnt++;
|
||||
}
|
||||
if (cnt) {
|
||||
assert(cnt == 3 || cnt == 4);
|
||||
// Insert one or two faces (triangulate a quad).
|
||||
its.indices.emplace_back(indices[0], indices[1], indices[2]);
|
||||
int face_index =its.indices.size() - 1;
|
||||
RGBA face_color;
|
||||
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) {
|
||||
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
|
||||
bool is_merge_ka_kd = true;
|
||||
for (size_t n = 0; n < 3; n++) {
|
||||
if (float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]) > 1.0) {
|
||||
is_merge_ka_kd=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (size_t n = 0; n < 3; n++) {
|
||||
if (is_merge_ka_kd) {
|
||||
face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f);
|
||||
}
|
||||
else {
|
||||
face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f);
|
||||
}
|
||||
}
|
||||
face_color[3] = mtl_data.new_mtl_unmap[mtl_name]->Tr; // alpha
|
||||
if (mtl_data.new_mtl_unmap[mtl_name]->map_Kd.size() > 0) {
|
||||
auto png_name = mtl_data.new_mtl_unmap[mtl_name]->map_Kd;
|
||||
obj_info.has_uv_png = true;
|
||||
if (obj_info.pngs.find(png_name) == obj_info.pngs.end()) { obj_info.pngs[png_name] = false; }
|
||||
obj_info.uv_map_pngs[face_index] = png_name;
|
||||
}
|
||||
if (data.textureCoordinates.size() > 0) {
|
||||
Vec2f uv0(data.textureCoordinates[uvs[0] * 2], data.textureCoordinates[uvs[0] * 2 + 1]);
|
||||
Vec2f uv1(data.textureCoordinates[uvs[1] * 2], data.textureCoordinates[uvs[1] * 2 + 1]);
|
||||
Vec2f uv2(data.textureCoordinates[uvs[2] * 2], data.textureCoordinates[uvs[2] * 2 + 1]);
|
||||
std::array<Vec2f, 3> uv_array{uv0, uv1, uv2};
|
||||
obj_info.uvs.emplace_back(uv_array);
|
||||
}
|
||||
obj_info.face_colors.emplace_back(face_color);
|
||||
}
|
||||
};
|
||||
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) {
|
||||
if (data.usemtls.size() == 1) {
|
||||
set_face_color(face_index, data.usemtls[0].name);
|
||||
} else {
|
||||
for (size_t k = 0; k < data.usemtls.size(); k++) {
|
||||
auto mtl = data.usemtls[k];
|
||||
if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
|
||||
set_face_color(face_index, data.usemtls[k].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (exist_mtl) {
|
||||
set_face_color_by_mtl(face_index);
|
||||
}
|
||||
if (cnt == 4) {
|
||||
its.indices.emplace_back(indices[0], indices[2], indices[3]);
|
||||
int face_index = its.indices.size() - 1;
|
||||
if (exist_mtl) {
|
||||
set_face_color_by_mtl(face_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*meshptr = TriangleMesh(std::move(its));
|
||||
if (meshptr->empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: This OBJ file couldn't be read because it's empty. " << path;
|
||||
message = _L("This OBJ file couldn't be read because it's empty.");
|
||||
return false;
|
||||
}
|
||||
if (meshptr->volume() < 0)
|
||||
meshptr->flip_triangles();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_obj(const char *path, Model *model, ObjInfo& obj_info, std::string &message, const char *object_name_in)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
|
||||
bool ret = load_obj(path, &mesh, obj_info, message);
|
||||
|
||||
if (ret) {
|
||||
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 ret;
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, TriangleMesh *mesh)
|
||||
{
|
||||
//FIXME returning false even if write failed.
|
||||
mesh->WriteOBJFile(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, ModelObject *model_object)
|
||||
{
|
||||
TriangleMesh mesh = model_object->mesh();
|
||||
return store_obj(path, &mesh);
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, Model *model)
|
||||
{
|
||||
TriangleMesh mesh = model->mesh();
|
||||
return store_obj(path, &mesh);
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
32
src/libslic3r/Format/OBJ.hpp
Normal file
32
src/libslic3r/Format/OBJ.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef slic3r_Format_OBJ_hpp_
|
||||
#define slic3r_Format_OBJ_hpp_
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include <unordered_map>
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class Model;
|
||||
class ModelObject;
|
||||
typedef std::function<void(std::vector<RGBA> &input_colors, bool is_single_color, std::vector<unsigned char> &filament_ids, unsigned char &first_extruder_id)> ObjImportColorFn;
|
||||
// Load an OBJ file into a provided model.
|
||||
struct ObjInfo {
|
||||
std::vector<RGBA> vertex_colors;
|
||||
std::vector<RGBA> face_colors;
|
||||
bool is_single_mtl{false};
|
||||
std::vector<std::array<Vec2f,3>> uvs;
|
||||
std::string obj_dircetory;
|
||||
std::map<std::string,bool> pngs;
|
||||
std::unordered_map<int, std::string> uv_map_pngs;
|
||||
bool has_uv_png{false};
|
||||
|
||||
};
|
||||
extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_colors, std::string &message);
|
||||
extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr);
|
||||
|
||||
extern bool store_obj(const char *path, TriangleMesh *mesh);
|
||||
extern bool store_obj(const char *path, ModelObject *model);
|
||||
extern bool store_obj(const char *path, Model *model);
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_OBJ_hpp_ */
|
||||
518
src/libslic3r/Format/SL1.cpp
Normal file
518
src/libslic3r/Format/SL1.cpp
Normal file
@@ -0,0 +1,518 @@
|
||||
#include "SL1.hpp"
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/Time.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
||||
#include "libslic3r/MarchingSquares.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/SLA/RasterBase.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace marchsq {
|
||||
|
||||
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
|
||||
using Rst = Slic3r::png::ImageGreyscale;
|
||||
|
||||
// The type of pixel cell in the raster
|
||||
using ValueType = uint8_t;
|
||||
|
||||
// Value at a given position
|
||||
static uint8_t get(const Rst &rst, size_t row, size_t col)
|
||||
{
|
||||
return rst.get(row, col);
|
||||
}
|
||||
|
||||
// Number of rows and cols of the raster
|
||||
static size_t rows(const Rst &rst) { return rst.rows; }
|
||||
static size_t cols(const Rst &rst) { return rst.cols; }
|
||||
};
|
||||
|
||||
} // namespace marchsq
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
struct PNGBuffer { std::vector<uint8_t> buf; std::string fname; };
|
||||
struct ArchiveData {
|
||||
boost::property_tree::ptree profile, config;
|
||||
std::vector<PNGBuffer> images;
|
||||
};
|
||||
|
||||
static const constexpr char *CONFIG_FNAME = "config.ini";
|
||||
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
||||
|
||||
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive & zip)
|
||||
{
|
||||
std::string buf(size_t(entry.m_uncomp_size), '\0');
|
||||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
boost::property_tree::ptree tree;
|
||||
std::stringstream ss(buf);
|
||||
boost::property_tree::read_ini(ss, tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive & zip,
|
||||
const std::string & name)
|
||||
{
|
||||
std::vector<uint8_t> buf(entry.m_uncomp_size);
|
||||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
|
||||
}
|
||||
|
||||
ArchiveData extract_sla_archive(const std::string &zipfname,
|
||||
const std::string &exclude)
|
||||
{
|
||||
ArchiveData arch;
|
||||
|
||||
// Little RAII
|
||||
struct Arch: public MZ_Archive {
|
||||
Arch(const std::string &fname) {
|
||||
if (!open_zip_reader(&arch, fname))
|
||||
throw Slic3r::FileIOError(get_errorstr());
|
||||
}
|
||||
|
||||
~Arch() { close_zip_reader(&arch); }
|
||||
} zip (zipfname);
|
||||
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
|
||||
|
||||
for (mz_uint i = 0; i < num_entries; ++i)
|
||||
{
|
||||
mz_zip_archive_file_stat entry;
|
||||
|
||||
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
|
||||
{
|
||||
std::string name = entry.m_filename;
|
||||
boost::algorithm::to_lower(name);
|
||||
|
||||
if (boost::algorithm::contains(name, exclude)) continue;
|
||||
|
||||
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
|
||||
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
|
||||
|
||||
if (boost::filesystem::path(name).extension().string() == ".png") {
|
||||
auto it = std::lower_bound(
|
||||
arch.images.begin(), arch.images.end(), PNGBuffer{{}, name},
|
||||
[](const PNGBuffer &r1, const PNGBuffer &r2) {
|
||||
return std::less<std::string>()(r1.fname, r2.fname);
|
||||
});
|
||||
|
||||
arch.images.insert(it, read_png(entry, zip, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arch;
|
||||
}
|
||||
|
||||
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
|
||||
double px_w, double px_h)
|
||||
{
|
||||
auto polys = reserve_vector<ExPolygon>(rings.size());
|
||||
|
||||
for (const marchsq::Ring &ring : rings) {
|
||||
Polygon poly; Points &pts = poly.points;
|
||||
pts.reserve(ring.size());
|
||||
|
||||
for (const marchsq::Coord &crd : ring)
|
||||
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
|
||||
|
||||
polys.emplace_back(poly);
|
||||
}
|
||||
|
||||
// TODO: Is a union necessary?
|
||||
return union_ex(polys);
|
||||
}
|
||||
|
||||
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
|
||||
{
|
||||
for (auto &p : poly.contour.points) fn(p);
|
||||
for (auto &h : poly.holes)
|
||||
for (auto &p : h.points) fn(p);
|
||||
}
|
||||
|
||||
void invert_raster_trafo(ExPolygons & expolys,
|
||||
const sla::RasterBase::Trafo &trafo,
|
||||
coord_t width,
|
||||
coord_t height)
|
||||
{
|
||||
if (trafo.flipXY) std::swap(height, width);
|
||||
|
||||
for (auto &expoly : expolys) {
|
||||
if (trafo.mirror_y)
|
||||
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
|
||||
|
||||
if (trafo.mirror_x)
|
||||
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
|
||||
|
||||
expoly.translate(-trafo.center_x, -trafo.center_y);
|
||||
|
||||
if (trafo.flipXY)
|
||||
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
|
||||
|
||||
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
|
||||
expoly.contour.reverse();
|
||||
for (auto &h : expoly.holes) h.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RasterParams {
|
||||
sla::RasterBase::Trafo trafo; // Raster transformations
|
||||
coord_t width, height; // scaled raster dimensions (not resolution)
|
||||
double px_h, px_w; // pixel dimesions
|
||||
marchsq::Coord win; // marching squares window size
|
||||
};
|
||||
|
||||
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
|
||||
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
|
||||
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
|
||||
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
|
||||
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
|
||||
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
|
||||
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
|
||||
|
||||
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
|
||||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
|
||||
throw MissingProfileError("Invalid SL1 / SL1S file");
|
||||
|
||||
RasterParams rstp;
|
||||
|
||||
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
|
||||
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
|
||||
|
||||
rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ?
|
||||
sla::RasterBase::roLandscape :
|
||||
sla::RasterBase::roPortrait,
|
||||
{opt_mirror_x->value, opt_mirror_y->value}};
|
||||
|
||||
rstp.height = scaled(opt_disp_h->value);
|
||||
rstp.width = scaled(opt_disp_w->value);
|
||||
|
||||
return rstp;
|
||||
}
|
||||
|
||||
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
|
||||
|
||||
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
|
||||
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
||||
|
||||
if (!opt_layerh || !opt_init_layerh)
|
||||
throw MissingProfileError("Invalid SL1 / SL1S file");
|
||||
|
||||
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> extract_slices_from_sla_archive(
|
||||
ArchiveData & arch,
|
||||
const RasterParams & rstp,
|
||||
std::function<bool(int)> progr)
|
||||
{
|
||||
auto jobdir = arch.config.get<std::string>("jobDir");
|
||||
for (auto &c : jobdir) c = std::tolower(c);
|
||||
|
||||
std::vector<ExPolygons> slices(arch.images.size());
|
||||
|
||||
struct Status
|
||||
{
|
||||
double incr, val, prev;
|
||||
bool stop = false;
|
||||
tbb::spin_mutex mutex = {};
|
||||
} st {100. / slices.size(), 0., 0.};
|
||||
|
||||
tbb::parallel_for(size_t(0), arch.images.size(),
|
||||
[&arch, &slices, &st, &rstp, progr](size_t i) {
|
||||
// Status indication guarded with the spinlock
|
||||
{
|
||||
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
|
||||
if (st.stop) return;
|
||||
|
||||
st.val += st.incr;
|
||||
double curr = std::round(st.val);
|
||||
if (curr > st.prev) {
|
||||
st.prev = curr;
|
||||
st.stop = !progr(int(curr));
|
||||
}
|
||||
}
|
||||
|
||||
png::ImageGreyscale img;
|
||||
png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
|
||||
if (!png::decode_png(rb, img)) return;
|
||||
|
||||
uint8_t isoval = 128;
|
||||
auto rings = marchsq::execute(img, isoval, rstp.win);
|
||||
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
|
||||
|
||||
// Invert the raster transformations indicated in the profile metadata
|
||||
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
|
||||
|
||||
slices[i] = std::move(expolys);
|
||||
});
|
||||
|
||||
if (st.stop) slices = {};
|
||||
|
||||
return slices;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||
{
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
||||
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
}
|
||||
|
||||
// If the profile is missing from the archive (older PS versions did not have
|
||||
// it), profile_out's initial value will be used as fallback. profile_out will be empty on
|
||||
// function return if the archive did not contain any profile.
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
DynamicPrintConfig & profile_out,
|
||||
std::function<bool(int)> progr)
|
||||
{
|
||||
// Ensure minimum window size for marching squares
|
||||
windowsize.x() = std::max(2, windowsize.x());
|
||||
windowsize.y() = std::max(2, windowsize.y());
|
||||
|
||||
std::string exclude_entries{"thumbnail"};
|
||||
ArchiveData arch = extract_sla_archive(zipfname, exclude_entries);
|
||||
DynamicPrintConfig profile_in, profile_use;
|
||||
ConfigSubstitutions config_substitutions =
|
||||
profile_in.load(arch.profile,
|
||||
ForwardCompatibilitySubstitutionRule::Enable);
|
||||
|
||||
if (profile_in.empty()) { // missing profile... do guess work
|
||||
// try to recover the layer height from the config.ini which was
|
||||
// present in all versions of sl1 files.
|
||||
if (auto lh_opt = arch.config.find("layerHeight");
|
||||
lh_opt != arch.config.not_found())
|
||||
{
|
||||
auto lh_str = lh_opt->second.data();
|
||||
|
||||
size_t pos;
|
||||
double lh = string_to_double_decimal_point(lh_str, &pos);
|
||||
if (pos) { // TODO: verify that pos is 0 when parsing fails
|
||||
profile_out.set("layer_height", lh);
|
||||
profile_out.set("initial_layer_height", lh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the archive contains an empty profile, use the one that was passed as output argument
|
||||
// then replace it with the readed profile to report that it was empty.
|
||||
profile_use = profile_in.empty() ? profile_out : profile_in;
|
||||
profile_out = profile_in;
|
||||
|
||||
RasterParams rstp = get_raster_params(profile_use);
|
||||
rstp.win = {windowsize.y(), windowsize.x()};
|
||||
|
||||
SliceParams slicp = get_slice_params(profile_use);
|
||||
|
||||
std::vector<ExPolygons> slices =
|
||||
extract_slices_from_sla_archive(arch, rstp, progr);
|
||||
|
||||
if (!slices.empty())
|
||||
out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
|
||||
|
||||
return config_substitutions;
|
||||
}
|
||||
|
||||
using ConfMap = std::map<std::string, std::string>;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string to_ini(const ConfMap &m)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto ¶m : m) ret += param.first + " = " + param.second + "\n";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
if (cfg.has(key)) {
|
||||
auto opt = cfg.option(key);
|
||||
if (opt) ret = opt->serialize();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fill_iniconf(ConfMap &m, const SLAPrint &print)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter; // for to_string
|
||||
auto &cfg = print.full_print_config();
|
||||
m["layerHeight"] = get_cfg_value(cfg, "layer_height");
|
||||
m["expTime"] = get_cfg_value(cfg, "exposure_time");
|
||||
m["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time");
|
||||
m["expUserProfile"] = get_cfg_value(cfg, "material_print_speed") == "slow" ? "1" : "0";
|
||||
m["materialName"] = get_cfg_value(cfg, "sla_material_settings_id");
|
||||
m["printerModel"] = get_cfg_value(cfg, "printer_model");
|
||||
m["printerVariant"] = get_cfg_value(cfg, "printer_variant");
|
||||
m["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
|
||||
m["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
|
||||
m["fileCreationTimestamp"] = Utils::utc_timestamp();
|
||||
m["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
|
||||
|
||||
SLAPrintStatistics stats = print.print_statistics();
|
||||
// Set statistics values to the printer
|
||||
|
||||
double used_material = (stats.objects_used_material +
|
||||
stats.support_used_material) / 1000;
|
||||
|
||||
int num_fade = print.default_object_config().faded_layers.getInt();
|
||||
num_fade = num_fade >= 0 ? num_fade : 0;
|
||||
|
||||
m["usedMaterial"] = std::to_string(used_material);
|
||||
m["numFade"] = std::to_string(num_fade);
|
||||
m["numSlow"] = std::to_string(stats.slow_layers_count);
|
||||
m["numFast"] = std::to_string(stats.fast_layers_count);
|
||||
m["printTime"] = std::to_string(stats.estimated_print_time);
|
||||
|
||||
bool hollow_en = false;
|
||||
auto it = print.objects().begin();
|
||||
while (!hollow_en && it != print.objects().end())
|
||||
hollow_en = (*it++)->config().hollowing_enable;
|
||||
|
||||
m["hollow"] = hollow_en ? "1" : "0";
|
||||
|
||||
m["action"] = "print";
|
||||
}
|
||||
|
||||
void fill_slicerconf(ConfMap &m, const SLAPrint &print)
|
||||
{
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
// Sorted list of config keys, which shall not be stored into the ini.
|
||||
static constexpr auto banned_keys = {
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv
|
||||
};
|
||||
|
||||
assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
|
||||
auto is_banned = [](const std::string &key) {
|
||||
return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
|
||||
};
|
||||
|
||||
auto &cfg = print.full_print_config();
|
||||
for (const std::string &key : cfg.keys())
|
||||
if (! is_banned(key) && ! cfg.option(key)->is_nil())
|
||||
m[key] = cfg.opt_serialize(key);
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<sla::RasterBase> SL1Archive::create_raster() const
|
||||
{
|
||||
sla::RasterBase::Resolution res;
|
||||
sla::RasterBase::PixelDim pxdim;
|
||||
std::array<bool, 2> mirror;
|
||||
|
||||
double w = m_cfg.display_width.getFloat();
|
||||
double h = m_cfg.display_height.getFloat();
|
||||
auto pw = size_t(m_cfg.display_pixels_x.getInt());
|
||||
auto ph = size_t(m_cfg.display_pixels_y.getInt());
|
||||
|
||||
mirror[X] = m_cfg.display_mirror_x.getBool();
|
||||
mirror[Y] = m_cfg.display_mirror_y.getBool();
|
||||
|
||||
auto ro = m_cfg.display_orientation.getInt();
|
||||
sla::RasterBase::Orientation orientation =
|
||||
ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait :
|
||||
sla::RasterBase::roLandscape;
|
||||
|
||||
if (orientation == sla::RasterBase::roPortrait) {
|
||||
std::swap(w, h);
|
||||
std::swap(pw, ph);
|
||||
}
|
||||
|
||||
res = sla::RasterBase::Resolution{pw, ph};
|
||||
pxdim = sla::RasterBase::PixelDim{w / pw, h / ph};
|
||||
sla::RasterBase::Trafo tr{orientation, mirror};
|
||||
|
||||
double gamma = m_cfg.gamma_correction.getFloat();
|
||||
|
||||
return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr);
|
||||
}
|
||||
|
||||
sla::RasterEncoder SL1Archive::get_encoder() const
|
||||
{
|
||||
return sla::PNGRasterEncoder{};
|
||||
}
|
||||
|
||||
void SL1Archive::export_print(Zipper& zipper,
|
||||
const SLAPrint &print,
|
||||
const std::string &prjname)
|
||||
{
|
||||
std::string project =
|
||||
prjname.empty() ?
|
||||
boost::filesystem::path(zipper.get_filename()).stem().string() :
|
||||
prjname;
|
||||
|
||||
ConfMap iniconf, slicerconf;
|
||||
fill_iniconf(iniconf, print);
|
||||
|
||||
iniconf["jobDir"] = project;
|
||||
|
||||
fill_slicerconf(slicerconf, print);
|
||||
|
||||
try {
|
||||
zipper.add_entry("config.ini");
|
||||
zipper << to_ini(iniconf);
|
||||
zipper.add_entry("prusaslicer.ini");
|
||||
zipper << to_ini(slicerconf);
|
||||
|
||||
size_t i = 0;
|
||||
for (const sla::EncodedRaster &rst : m_layers) {
|
||||
|
||||
std::string imgname = project + string_printf("%.5d", i++) + "." +
|
||||
rst.extension();
|
||||
|
||||
zipper.add_entry(imgname.c_str(), rst.data(), rst.size());
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
64
src/libslic3r/Format/SL1.hpp
Normal file
64
src/libslic3r/Format/SL1.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef ARCHIVETRAITS_HPP
|
||||
#define ARCHIVETRAITS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SL1Archive: public SLAArchive {
|
||||
SLAPrinterConfig m_cfg;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<sla::RasterBase> create_raster() const override;
|
||||
sla::RasterEncoder get_encoder() const override;
|
||||
|
||||
public:
|
||||
|
||||
SL1Archive() = default;
|
||||
explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {}
|
||||
explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {}
|
||||
|
||||
void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "");
|
||||
void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "")
|
||||
{
|
||||
Zipper zipper(fname);
|
||||
export_print(zipper, print, projectname);
|
||||
}
|
||||
|
||||
void apply(const SLAPrinterConfig &cfg) override
|
||||
{
|
||||
auto diff = m_cfg.diff(cfg);
|
||||
if (!diff.empty()) {
|
||||
m_cfg.apply_only(cfg, diff);
|
||||
m_layers = {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
DynamicPrintConfig & profile,
|
||||
std::function<bool(int)> progr = [](int) { return true; });
|
||||
|
||||
inline ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
std::function<bool(int)> progr = [](int) { return true; })
|
||||
{
|
||||
DynamicPrintConfig profile;
|
||||
return import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||
}
|
||||
|
||||
class MissingProfileError : public RuntimeError { using RuntimeError::RuntimeError; };
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
||||
#endif // ARCHIVETRAITS_HPP
|
||||
391
src/libslic3r/Format/STEP.cpp
Normal file
391
src/libslic3r/Format/STEP.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
#include "STEP.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_SEPARATOR '\\'
|
||||
#else
|
||||
#define DIR_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
#include "STEPCAFControl_Reader.hxx"
|
||||
#include "BRepMesh_IncrementalMesh.hxx"
|
||||
#include "Interface_Static.hxx"
|
||||
#include "XCAFDoc_DocumentTool.hxx"
|
||||
#include "XCAFDoc_ShapeTool.hxx"
|
||||
#include "XCAFApp_Application.hxx"
|
||||
#include "TopoDS_Solid.hxx"
|
||||
#include "TopoDS_Compound.hxx"
|
||||
#include "TopoDS_Builder.hxx"
|
||||
#include "TopoDS.hxx"
|
||||
#include "TDataStd_Name.hxx"
|
||||
#include "BRepBuilderAPI_Transform.hxx"
|
||||
#include "TopExp_Explorer.hxx"
|
||||
#include "TopExp_Explorer.hxx"
|
||||
#include "BRep_Tool.hxx"
|
||||
|
||||
const double STEP_TRANS_CHORD_ERROR = 0.003;
|
||||
const double STEP_TRANS_ANGLE_RES = 0.5;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool StepPreProcessor::preprocess(const char* path, std::string &output_path)
|
||||
{
|
||||
boost::nowide::ifstream infile(path);
|
||||
if (!infile.good()) {
|
||||
throw Slic3r::RuntimeError(std::string("Load step file failed.\nCannot open file for reading.\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::filesystem::path temp_path(temporary_dir());
|
||||
std::string temp_step_path = temp_path.string() + "/temp.step";
|
||||
boost::nowide::remove(temp_step_path.c_str());
|
||||
boost::nowide::ofstream temp_file(temp_step_path, std::ios::app);
|
||||
std::string temp_line;
|
||||
while (std::getline(infile, temp_line)) {
|
||||
if (m_encode_type == EncodedType::UTF8) {
|
||||
//QDS: continue to judge whether is other type
|
||||
if (isUtf8(temp_line)) {
|
||||
//QDS: do nothing, but must be checked before checking whether is GBK
|
||||
}
|
||||
//QDS: not utf8, then maybe GBK
|
||||
else if (isGBK(temp_line)) {
|
||||
m_encode_type = EncodedType::GBK;
|
||||
}
|
||||
//QDS: not UTF8 and not GBK, then maybe some kind of special encoded type which we can't handle
|
||||
// Load the step as UTF and user will see garbage characters in slicer but we have no solution at the moment
|
||||
else {
|
||||
m_encode_type = EncodedType::OTHER;
|
||||
}
|
||||
}
|
||||
if (m_encode_type == EncodedType::GBK)
|
||||
//QDS: transform to UTF8 format if is GBK
|
||||
//todo: use gbkToUtf8 function to replace
|
||||
temp_file << decode_path(temp_line.c_str()) << std::endl;
|
||||
else
|
||||
temp_file << temp_line.c_str() << std::endl;
|
||||
}
|
||||
temp_file.close();
|
||||
infile.close();
|
||||
if (m_encode_type == EncodedType::GBK) {
|
||||
output_path = temp_step_path;
|
||||
} else {
|
||||
boost::nowide::remove(temp_step_path.c_str());
|
||||
output_path = std::string(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StepPreProcessor::isUtf8File(const char* path)
|
||||
{
|
||||
boost::nowide::ifstream infile(path);
|
||||
if (!infile.good()) {
|
||||
throw Slic3r::RuntimeError(std::string("Load step file failed.\nCannot open file for reading.\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string temp_line;
|
||||
while (std::getline(infile, temp_line)) {
|
||||
if (!isUtf8(temp_line)) {
|
||||
infile.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
infile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StepPreProcessor::isUtf8(const std::string str)
|
||||
{
|
||||
size_t num = 0;
|
||||
int i = 0;
|
||||
while (i < str.length()) {
|
||||
if ((str[i] & 0x80) == 0x00) {
|
||||
i++;
|
||||
} else if ((num = preNum(str[i])) > 2) {
|
||||
i++;
|
||||
for (int j = 0; j < num - 1; j++) {
|
||||
if ((str[i] & 0xc0) != 0x80)
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StepPreProcessor::isGBK(const std::string str) {
|
||||
size_t i = 0;
|
||||
while (i < str.length()) {
|
||||
if (str[i] <= 0x7f) {
|
||||
i++;
|
||||
continue;
|
||||
} else {
|
||||
if (str[i] >= 0x81 &&
|
||||
str[i] <= 0xfe &&
|
||||
str[i + 1] >= 0x40 &&
|
||||
str[i + 1] <= 0xfe &&
|
||||
str[i + 1] != 0xf7) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int StepPreProcessor::preNum(const unsigned char byte) {
|
||||
unsigned char mask = 0x80;
|
||||
int num = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((byte & mask) == mask) {
|
||||
mask = mask >> 1;
|
||||
num++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
struct NamedSolid {
|
||||
NamedSolid(const TopoDS_Shape& s,
|
||||
const std::string& n) : solid{s}, name{n} {}
|
||||
const TopoDS_Shape solid;
|
||||
const std::string name;
|
||||
};
|
||||
|
||||
static void getNamedSolids(const TopLoc_Location& location, const std::string& prefix,
|
||||
unsigned int& id, const Handle(XCAFDoc_ShapeTool) shapeTool,
|
||||
const TDF_Label label, std::vector<NamedSolid>& namedSolids) {
|
||||
TDF_Label referredLabel{label};
|
||||
if (shapeTool->IsReference(label))
|
||||
shapeTool->GetReferredShape(label, referredLabel);
|
||||
|
||||
std::string name;
|
||||
Handle(TDataStd_Name) shapeName;
|
||||
if (referredLabel.FindAttribute(TDataStd_Name::GetID(), shapeName))
|
||||
name = TCollection_AsciiString(shapeName->Get()).ToCString();
|
||||
|
||||
if (name == "")
|
||||
name = std::to_string(id++);
|
||||
std::string fullName{name};
|
||||
|
||||
TopLoc_Location localLocation = location * shapeTool->GetLocation(label);
|
||||
TDF_LabelSequence components;
|
||||
if (shapeTool->GetComponents(referredLabel, components)) {
|
||||
for (Standard_Integer compIndex = 1; compIndex <= components.Length(); ++compIndex) {
|
||||
getNamedSolids(localLocation, fullName, id, shapeTool, components.Value(compIndex), namedSolids);
|
||||
}
|
||||
} else {
|
||||
TopoDS_Shape shape;
|
||||
shapeTool->GetShape(referredLabel, shape);
|
||||
TopAbs_ShapeEnum shape_type = shape.ShapeType();
|
||||
BRepBuilderAPI_Transform transform(shape, localLocation, Standard_True);
|
||||
switch (shape_type) {
|
||||
case TopAbs_COMPOUND:
|
||||
namedSolids.emplace_back(TopoDS::Compound(transform.Shape()), fullName);
|
||||
break;
|
||||
case TopAbs_COMPSOLID:
|
||||
namedSolids.emplace_back(TopoDS::CompSolid(transform.Shape()), fullName);
|
||||
break;
|
||||
case TopAbs_SOLID:
|
||||
namedSolids.emplace_back(TopoDS::Solid(transform.Shape()), fullName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn)
|
||||
{
|
||||
bool cb_cancel = false;
|
||||
if (stepFn) {
|
||||
stepFn(LOAD_STEP_STAGE_READ_FILE, 0, 1, cb_cancel);
|
||||
is_cancel = cb_cancel;
|
||||
if (cb_cancel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!StepPreProcessor::isUtf8File(path) && isUtf8Fn)
|
||||
isUtf8Fn(false);
|
||||
std::string file_after_preprocess = std::string(path);
|
||||
|
||||
std::vector<NamedSolid> namedSolids;
|
||||
Handle(TDocStd_Document) document;
|
||||
Handle(XCAFApp_Application) application = XCAFApp_Application::GetApplication();
|
||||
application->NewDocument(file_after_preprocess.c_str(), document);
|
||||
STEPCAFControl_Reader reader;
|
||||
reader.SetNameMode(true);
|
||||
//QDS: Todo, read file is slow which cause the progress_bar no update and gui no response
|
||||
IFSelect_ReturnStatus stat = reader.ReadFile(file_after_preprocess.c_str());
|
||||
if (stat != IFSelect_RetDone || !reader.Transfer(document)) {
|
||||
application->Close(document);
|
||||
throw std::logic_error{ std::string{"Could not read '"} + path + "'" };
|
||||
return false;
|
||||
}
|
||||
Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(document->Main());
|
||||
TDF_LabelSequence topLevelShapes;
|
||||
shapeTool->GetFreeShapes(topLevelShapes);
|
||||
|
||||
unsigned int id{1};
|
||||
Standard_Integer topShapeLength = topLevelShapes.Length() + 1;
|
||||
auto stage_unit2 = topShapeLength / LOAD_STEP_STAGE_UNIT_NUM + 1;
|
||||
|
||||
for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) {
|
||||
if (stepFn) {
|
||||
if ((iLabel % stage_unit2) == 0) {
|
||||
stepFn(LOAD_STEP_STAGE_GET_SOLID, iLabel, topShapeLength, cb_cancel);
|
||||
is_cancel = cb_cancel;
|
||||
}
|
||||
if (cb_cancel) {
|
||||
shapeTool.reset(nullptr);
|
||||
application->Close(document);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
getNamedSolids(TopLoc_Location{}, "", id, shapeTool, topLevelShapes.Value(iLabel), namedSolids);
|
||||
}
|
||||
|
||||
std::vector<stl_file> stl;
|
||||
stl.resize(namedSolids.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, namedSolids.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true);
|
||||
// QDS: calculate total number of the nodes and triangles
|
||||
int aNbNodes = 0;
|
||||
int aNbTriangles = 0;
|
||||
for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc);
|
||||
if (!aTriangulation.IsNull()) {
|
||||
aNbNodes += aTriangulation->NbNodes();
|
||||
aNbTriangles += aTriangulation->NbTriangles();
|
||||
}
|
||||
}
|
||||
|
||||
if (aNbTriangles == 0 || aNbNodes == 0)
|
||||
// QDS: No triangulation on the shape.
|
||||
continue;
|
||||
|
||||
stl[i].stats.type = inmemory;
|
||||
stl[i].stats.number_of_facets = (uint32_t) aNbTriangles;
|
||||
stl[i].stats.original_num_facets = stl[i].stats.number_of_facets;
|
||||
stl_allocate(&stl[i]);
|
||||
|
||||
std::vector<Vec3f> points;
|
||||
points.reserve(aNbNodes);
|
||||
// QDS: count faces missing triangulation
|
||||
Standard_Integer aNbFacesNoTri = 0;
|
||||
// QDS: fill temporary triangulation
|
||||
Standard_Integer aNodeOffset = 0;
|
||||
Standard_Integer aTriangleOffet = 0;
|
||||
for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
const TopoDS_Shape &aFace = anExpSF.Current();
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc);
|
||||
if (aTriangulation.IsNull()) {
|
||||
++aNbFacesNoTri;
|
||||
continue;
|
||||
}
|
||||
// QDS: copy nodes
|
||||
gp_Trsf aTrsf = aLoc.Transformation();
|
||||
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
|
||||
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
|
||||
aPnt.Transform(aTrsf);
|
||||
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
|
||||
}
|
||||
// QDS: copy triangles
|
||||
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
|
||||
Standard_Integer anId[3];
|
||||
for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) {
|
||||
Poly_Triangle aTri = aTriangulation->Triangle(aTriIter);
|
||||
|
||||
aTri.Get(anId[0], anId[1], anId[2]);
|
||||
if (anOrientation == TopAbs_REVERSED)
|
||||
std::swap(anId[1], anId[2]);
|
||||
// QDS: save triangles facets
|
||||
stl_facet facet;
|
||||
facet.vertex[0] = points[anId[0] + aNodeOffset - 1].cast<float>();
|
||||
facet.vertex[1] = points[anId[1] + aNodeOffset - 1].cast<float>();
|
||||
facet.vertex[2] = points[anId[2] + aNodeOffset - 1].cast<float>();
|
||||
facet.extra[0] = 0;
|
||||
facet.extra[1] = 0;
|
||||
stl_normal normal;
|
||||
stl_calculate_normal(normal, &facet);
|
||||
stl_normalize_vector(normal);
|
||||
facet.normal = normal;
|
||||
stl[i].facet_start[aTriangleOffet + aTriIter - 1] = facet;
|
||||
}
|
||||
|
||||
aNodeOffset += aTriangulation->NbNodes();
|
||||
aTriangleOffet += aTriangulation->NbTriangles();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ModelObject *new_object = model->add_object();
|
||||
const char * last_slash = strrchr(path, DIR_SEPARATOR);
|
||||
new_object->name.assign((last_slash == nullptr) ? path : last_slash + 1);
|
||||
new_object->input_file = path;
|
||||
|
||||
auto stage_unit3 = stl.size() / LOAD_STEP_STAGE_UNIT_NUM + 1;
|
||||
for (size_t i = 0; i < stl.size(); i++) {
|
||||
if (stepFn) {
|
||||
if ((i % stage_unit3) == 0) {
|
||||
stepFn(LOAD_STEP_STAGE_GET_MESH, i, stl.size(), cb_cancel);
|
||||
is_cancel = cb_cancel;
|
||||
}
|
||||
if (cb_cancel) {
|
||||
model->delete_object(new_object);
|
||||
shapeTool.reset(nullptr);
|
||||
application->Close(document);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//QDS: maybe mesh is empty from step file. Don't add
|
||||
if (stl[i].stats.number_of_facets > 0) {
|
||||
TriangleMesh triangle_mesh;
|
||||
triangle_mesh.from_stl(stl[i]);
|
||||
ModelVolume* new_volume = new_object->add_volume(std::move(triangle_mesh));
|
||||
new_volume->name = namedSolids[i].name;
|
||||
new_volume->source.input_file = path;
|
||||
new_volume->source.object_idx = (int)model->objects.size() - 1;
|
||||
new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
shapeTool.reset(nullptr);
|
||||
application->Close(document);
|
||||
|
||||
//QDS: no valid shape from the step, delete the new object as well
|
||||
if (new_object->volumes.size() == 0) {
|
||||
model->delete_object(new_object);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
49
src/libslic3r/Format/STEP.hpp
Normal file
49
src/libslic3r/Format/STEP.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef slic3r_Format_STEP_hpp_
|
||||
#define slic3r_Format_STEP_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class ModelObject;
|
||||
|
||||
// load step stage
|
||||
const int LOAD_STEP_STAGE_READ_FILE = 0;
|
||||
const int LOAD_STEP_STAGE_GET_SOLID = 1;
|
||||
const int LOAD_STEP_STAGE_GET_MESH = 2;
|
||||
const int LOAD_STEP_STAGE_NUM = 3;
|
||||
const int LOAD_STEP_STAGE_UNIT_NUM = 5;
|
||||
|
||||
typedef std::function<void(int load_stage, int current, int total, bool& cancel)> ImportStepProgressFn;
|
||||
typedef std::function<void(bool isUtf8)> StepIsUtf8Fn;
|
||||
|
||||
//QDS: Load an step file into a provided model.
|
||||
extern bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn proFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
|
||||
|
||||
//QDS: Used to detect what kind of encoded type is used in name field of step
|
||||
// If is encoded in UTF8, the file don't need to be handled, then return the original path directly.
|
||||
// If is encoded in GBK, then translate to UTF8 and generate a new temporary step file.
|
||||
// If is encoded in Other type, we can't handled, then treat as UTF8. In this case, the name is garbage
|
||||
// characters.
|
||||
// By preprocessing, at least we can avoid garbage characters if the name field is encoded by GBK.
|
||||
class StepPreProcessor {
|
||||
enum class EncodedType : unsigned char
|
||||
{
|
||||
UTF8,
|
||||
GBK,
|
||||
OTHER
|
||||
};
|
||||
|
||||
public:
|
||||
bool preprocess(const char* path, std::string &output_path);
|
||||
static bool isUtf8File(const char* path);
|
||||
private:
|
||||
static bool isUtf8(const std::string str);
|
||||
static bool isGBK(const std::string str);
|
||||
static int preNum(const unsigned char byte);
|
||||
//QDS: default is UTF8 for most step file.
|
||||
EncodedType m_encode_type = EncodedType::UTF8;
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_STEP_hpp_ */
|
||||
64
src/libslic3r/Format/STL.cpp
Normal file
64
src/libslic3r/Format/STL.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
#include "STL.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#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, ImportstlProgressFn stlFn, int custom_header_length)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
std::string design_id;
|
||||
|
||||
if (!mesh.ReadSTLFile(path, true, stlFn, custom_header_length)) {
|
||||
// die "Failed to open $file\n" if !-e $path;
|
||||
return false;
|
||||
}
|
||||
if (mesh.empty()) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
bool store_stl(const char *path, Model *model, bool binary)
|
||||
{
|
||||
TriangleMesh mesh = model->mesh();
|
||||
return store_stl(path, &mesh, binary);
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
21
src/libslic3r/Format/STL.hpp
Normal file
21
src/libslic3r/Format/STL.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef slic3r_Format_STL_hpp_
|
||||
#define slic3r_Format_STL_hpp_
|
||||
|
||||
#include <admesh/stl.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
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, ImportstlProgressFn stlFn = nullptr, int custom_header_length = 80);
|
||||
|
||||
extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary);
|
||||
extern bool store_stl(const char *path, ModelObject *model_object, bool binary);
|
||||
extern bool store_stl(const char *path, Model *model, bool binary);
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_STL_hpp_ */
|
||||
865
src/libslic3r/Format/objparser.cpp
Normal file
865
src/libslic3r/Format/objparser.cpp
Normal file
@@ -0,0 +1,865 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
#include "objparser.hpp"
|
||||
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
|
||||
namespace ObjParser {
|
||||
#define EATWS() while (*line == ' ' || *line == '\t') ++line
|
||||
static bool obj_parseline(const char *line, ObjData &data)
|
||||
{
|
||||
if (*line == 0)
|
||||
return true;
|
||||
assert(Slic3r::is_decimal_separator_point());
|
||||
// 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 color_x = 0.0, color_y = 0.0, color_z = 0.0, color_w = 0.0;//undefine color
|
||||
if (*line != 0) {
|
||||
if (!data.has_vertex_color) {
|
||||
data.has_vertex_color = true;
|
||||
}
|
||||
color_x = strtod(line, &endptr);
|
||||
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
|
||||
return false;
|
||||
line = endptr;
|
||||
EATWS();
|
||||
color_y = strtod(line, &endptr);
|
||||
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
|
||||
return false;
|
||||
line = endptr;
|
||||
EATWS();
|
||||
color_z = strtod(line, &endptr);
|
||||
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
|
||||
return false;
|
||||
line = endptr;
|
||||
EATWS();
|
||||
color_w = 1.0;//default define alpha = 1.0
|
||||
if (*line != 0) {
|
||||
color_w = strtod(line, &endptr);
|
||||
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false;
|
||||
line = endptr;
|
||||
EATWS();
|
||||
}
|
||||
}
|
||||
// the following check is commented out because there may be obj files containing extra data, as those generated by Meshlab,
|
||||
// see https://dev.prusa3d.com/browse/SPE-1019 for an example,
|
||||
// and this would lead to a crash because no vertex would be stored
|
||||
// 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) color_x);
|
||||
data.coordinates.push_back((float) color_y);
|
||||
data.coordinates.push_back((float) color_z);
|
||||
data.coordinates.push_back((float) color_w);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'f':
|
||||
{
|
||||
// face
|
||||
EATWS();
|
||||
if (*line == 0)
|
||||
return false;
|
||||
|
||||
// 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 += (int) data.coordinates.size() / OBJ_VERTEX_LENGTH;
|
||||
else
|
||||
-- vertex.coordIdx;
|
||||
if (vertex.normalIdx < 0)
|
||||
vertex.normalIdx += (int)data.normals.size() / 3;
|
||||
else
|
||||
-- vertex.normalIdx;
|
||||
if (vertex.textureCoordIdx < 0)
|
||||
vertex.textureCoordIdx += (int)data.textureCoordinates.size() / 3;
|
||||
else
|
||||
-- vertex.textureCoordIdx;
|
||||
data.vertices.push_back(vertex);
|
||||
EATWS();
|
||||
}
|
||||
if (data.usemtls.size() > 0) {
|
||||
data.usemtls.back().vertexIdxEnd = (int) data.vertices.size();
|
||||
}
|
||||
if (data.usemtls.size() > 0) {
|
||||
int face_index_count = 0;
|
||||
for (int i = data.vertices.size() - 1; i >= 0; i--) {
|
||||
if (data.vertices[i].coordIdx == -1) {
|
||||
break;
|
||||
}
|
||||
face_index_count++;
|
||||
}
|
||||
if (face_index_count == 3) {//tri
|
||||
data.usemtls.back().face_end++;
|
||||
} else if (face_index_count == 4) {//quad
|
||||
data.usemtls.back().face_end++;
|
||||
data.usemtls.back().face_end++;
|
||||
}
|
||||
}
|
||||
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();
|
||||
if (data.usemtls.size()>0) {
|
||||
data.usemtls.back().vertexIdxEnd = (int) data.vertices.size();
|
||||
}
|
||||
ObjUseMtl usemtl;
|
||||
usemtl.vertexIdxFirst = (int)data.vertices.size();
|
||||
usemtl.name = line;
|
||||
data.usemtls.push_back(usemtl);
|
||||
if (data.usemtls.size() == 1) {
|
||||
data.usemtls.back().face_start = 0;
|
||||
}
|
||||
else {//>=2
|
||||
auto count = data.usemtls.size();
|
||||
auto& last_usemtl = data.usemtls[count-1];
|
||||
auto& last_last_usemtl = data.usemtls[count - 2];
|
||||
last_usemtl.face_start = last_last_usemtl.face_end + 1;
|
||||
}
|
||||
data.usemtls.back().face_end = data.usemtls.back().face_start - 1;
|
||||
break;
|
||||
}
|
||||
case 'o':
|
||||
{
|
||||
// o [object name]
|
||||
EATWS();
|
||||
while (*line != ' ' && *line != '\t' && *line != 0)
|
||||
++ line;
|
||||
// copy name to line.
|
||||
EATWS();
|
||||
if (*line != 0)
|
||||
return false;
|
||||
ObjObject object;
|
||||
object.vertexIdxFirst = (int)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 = (int)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 = (int)data.vertices.size();
|
||||
group.smoothingGroupID = g;
|
||||
data.smoothingGroups.push_back(group);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Unknown command: " << c1;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
static std::string cur_mtl_name = "";
|
||||
static bool mtl_parseline(const char *line, MtlData &data)
|
||||
{
|
||||
if (*line == 0) return true;
|
||||
assert(Slic3r::is_decimal_separator_point());
|
||||
// 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 'n': {
|
||||
if (*(line++) != 'e' || *(line++) != 'w' || *(line++) != 'm' || *(line++) != 't' || *(line++) != 'l')
|
||||
return false;
|
||||
EATWS();
|
||||
ObjNewMtl new_mtl;
|
||||
cur_mtl_name = line;
|
||||
data.new_mtl_unmap[cur_mtl_name] = std::make_shared<ObjNewMtl>();
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
if (*(line++) != 'a' || *(line++) != 'p' || *(line++) != '_' || *(line++) != 'K' || *(line++) != 'd') return false;
|
||||
EATWS();
|
||||
if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->map_Kd = line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'N': {
|
||||
char cur_char = *(line++);
|
||||
if (cur_char == 's') {
|
||||
EATWS();
|
||||
char * endptr = 0;
|
||||
double ns = strtod(line, &endptr);
|
||||
if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ns = (float) ns;
|
||||
}
|
||||
} else if (cur_char == 'i') {
|
||||
EATWS();
|
||||
char * endptr = 0;
|
||||
double ni = strtod(line, &endptr);
|
||||
if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ni = (float) ni;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'K': {
|
||||
char cur_char = *(line++);
|
||||
if (cur_char == 'a') {
|
||||
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 (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ka[0] = x;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ka[1] = y;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ka[2] = z;
|
||||
}
|
||||
} else if (cur_char == 'd') {
|
||||
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 (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Kd[0] = x;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Kd[1] = y;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Kd[2] = z;
|
||||
}
|
||||
} else if (cur_char == 's') {
|
||||
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 (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ks[0] = x;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ks[1] = y;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ks[2] = z;
|
||||
}
|
||||
} else if (cur_char == 'e') {
|
||||
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 (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ke[0] = x;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ke[1] = y;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Ke[2] = z;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'i': {
|
||||
if (*(line++) != 'l' || *(line++) != 'l' || *(line++) != 'u' || *(line++) != 'm')
|
||||
return false;
|
||||
EATWS();
|
||||
char * endptr = 0;
|
||||
double illum = strtod(line, &endptr);
|
||||
if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->illum = (float) illum;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
EATWS();
|
||||
char * endptr = 0;
|
||||
double d = strtod(line, &endptr);
|
||||
if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->d = (float) d;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'T': {
|
||||
char cur_char = *(line++);
|
||||
if (cur_char == 'r') {
|
||||
EATWS();
|
||||
char * endptr = 0;
|
||||
double tr = strtod(line, &endptr);
|
||||
if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Tr = (float) tr;
|
||||
}
|
||||
break;
|
||||
} else if (cur_char == 'f') {
|
||||
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 (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->Tf[0] = x;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Tf[1] = y;
|
||||
data.new_mtl_unmap[cur_mtl_name]->Tf[2] = z;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool objparse(const char *path, ObjData &data)
|
||||
{
|
||||
Slic3r::CNumericLocalesSetter locales_setter;
|
||||
|
||||
FILE *pFile = boost::nowide::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;
|
||||
//FIXME check the return value and exit on error?
|
||||
// Will it break parsing of some obj files?
|
||||
obj_parseline(c, data);
|
||||
lastLine = i + 1;
|
||||
}
|
||||
lenPrev = len - lastLine;
|
||||
if (lenPrev > 65536) {
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Excessive line length";
|
||||
::fclose(pFile);
|
||||
return false;
|
||||
}
|
||||
memmove(buf, buf + lastLine, lenPrev);
|
||||
}
|
||||
}
|
||||
catch (std::bad_alloc&) {
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Out of memory";
|
||||
}
|
||||
::fclose(pFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mtlparse(const char *path, MtlData &data)
|
||||
{
|
||||
Slic3r::CNumericLocalesSetter locales_setter;
|
||||
|
||||
FILE *pFile = boost::nowide::fopen(path, "rt");
|
||||
if (pFile == 0) return false;
|
||||
cur_mtl_name = "";
|
||||
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;
|
||||
// FIXME check the return value and exit on error?
|
||||
// Will it break parsing of some obj files?
|
||||
mtl_parseline(c, data);
|
||||
lastLine = i + 1;
|
||||
}
|
||||
lenPrev = len - lastLine;
|
||||
if (lenPrev > 65536) {
|
||||
BOOST_LOG_TRIVIAL(error) << "MtlParser: Excessive line length";
|
||||
::fclose(pFile);
|
||||
return false;
|
||||
}
|
||||
memmove(buf, buf + lastLine, lenPrev);
|
||||
}
|
||||
} catch (std::bad_alloc &) {
|
||||
BOOST_LOG_TRIVIAL(error) << "MtlParser: Out of memory";
|
||||
}
|
||||
::fclose(pFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool objparse(std::istream &stream, ObjData &data)
|
||||
{
|
||||
Slic3r::CNumericLocalesSetter locales_setter;
|
||||
|
||||
try {
|
||||
char buf[65536 * 2];
|
||||
size_t len = 0;
|
||||
size_t lenPrev = 0;
|
||||
while ((len = size_t(stream.read(buf + lenPrev, 65536).gcount())) != 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&) {
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Out of memory";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool savevector(FILE *pFile, const std::vector<T> &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<std::string> &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<typename T>
|
||||
bool savevectornameidx(FILE *pFile, const std::vector<T> &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<typename T>
|
||||
bool loadvector(FILE *pFile, std::vector<T> &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<std::string> &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(s.data(), 1, len, pFile) != len)
|
||||
return false;
|
||||
v.push_back(std::move(s));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool loadvectornameidx(FILE *pFile, std::vector<T> &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(v[i].name.data(), 1, len, pFile) != len)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool objbinsave(const char *path, const ObjData &data)
|
||||
{
|
||||
FILE *pFile = boost::nowide::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 = boost::nowide::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<typename T>
|
||||
bool vectorequal(const std::vector<T> &v1, const std::vector<T> &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<std::string> &v1, const std::vector<std::string> &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
|
||||
138
src/libslic3r/Format/objparser.hpp
Normal file
138
src/libslic3r/Format/objparser.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#ifndef slic3r_Format_objparser_hpp_
|
||||
#define slic3r_Format_objparser_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <istream>
|
||||
|
||||
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;
|
||||
int vertexIdxEnd{-1};
|
||||
int face_start;
|
||||
int face_end{-1};
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct ObjNewMtl
|
||||
{
|
||||
std::string name;
|
||||
float Ns;
|
||||
float Ni;
|
||||
float d;
|
||||
float illum;
|
||||
float Tr{1.0f}; // Transmission
|
||||
std::array<float, 3> Tf;
|
||||
std::array<float, 3> Ka;
|
||||
std::array<float, 3> Kd;
|
||||
std::array<float, 3> Ks;
|
||||
std::array<float, 3> Ke;
|
||||
std::string map_Kd;//defalut png
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
#define OBJ_VERTEX_COLOR_ALPHA 6
|
||||
#define OBJ_VERTEX_LENGTH 7 // x, y, z, color_x,color_y,color_z,color_w
|
||||
#define ONE_FACE_SIZE 4//ONE_FACE format: f 8/4/6 7/3/6 6/2/6 -1/-1/-1
|
||||
struct ObjData {
|
||||
// Version of the data structure for load / store in the private binary format.
|
||||
int version;
|
||||
|
||||
// x, y, z, color_x,color_y,color_z,color_w
|
||||
std::vector<float> coordinates;
|
||||
bool has_vertex_color{false};
|
||||
// u, v, w
|
||||
std::vector<float> textureCoordinates;
|
||||
// x, y, z
|
||||
std::vector<float> normals;
|
||||
// u, v, w
|
||||
std::vector<float> parameters;
|
||||
|
||||
std::vector<std::string> mtllibs;
|
||||
std::vector<ObjUseMtl> usemtls;
|
||||
std::vector<ObjObject> objects;
|
||||
std::vector<ObjGroup> groups;
|
||||
std::vector<ObjSmoothingGroup> smoothingGroups;
|
||||
|
||||
// List of faces, delimited by an ObjVertex with all members set to -1.
|
||||
std::vector<ObjVertex> vertices;
|
||||
};
|
||||
|
||||
struct MtlData
|
||||
{
|
||||
// Version of the data structure for load / store in the private binary format.
|
||||
int version;
|
||||
std::unordered_map<std::string, std::shared_ptr<ObjNewMtl>> new_mtl_unmap;
|
||||
};
|
||||
extern bool objparse(const char *path, ObjData &data);
|
||||
extern bool mtlparse(const char *path, MtlData &data);
|
||||
extern bool objparse(std::istream &stream, 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_ */
|
||||
8275
src/libslic3r/Format/qds_3mf.cpp
Normal file
8275
src/libslic3r/Format/qds_3mf.cpp
Normal file
File diff suppressed because it is too large
Load Diff
298
src/libslic3r/Format/qds_3mf.hpp
Normal file
298
src/libslic3r/Format/qds_3mf.hpp
Normal file
@@ -0,0 +1,298 @@
|
||||
#ifndef QDS_3MF_hpp_
|
||||
#define QDS_3MF_hpp_
|
||||
|
||||
#include "../GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r {
|
||||
class Model;
|
||||
class ModelObject;
|
||||
struct ConfigSubstitutionContext;
|
||||
class DynamicPrintConfig;
|
||||
class Preset;
|
||||
struct FilamentInfo;
|
||||
struct ThumbnailData;
|
||||
|
||||
|
||||
#define PLATE_THUMBNAIL_SMALL_WIDTH 128
|
||||
#define PLATE_THUMBNAIL_SMALL_HEIGHT 128
|
||||
|
||||
#define GCODE_FILE_FORMAT "Metadata/plate_%1%.gcode"
|
||||
#define THUMBNAIL_FILE_FORMAT "Metadata/plate_%1%.png"
|
||||
#define NO_LIGHT_THUMBNAIL_FILE_FORMAT "Metadata/plate_no_light_%1%.png"
|
||||
#define TOP_FILE_FORMAT "Metadata/top_%1%.png"
|
||||
#define PICK_FILE_FORMAT "Metadata/pick_%1%.png"
|
||||
//#define PATTERN_FILE_FORMAT "Metadata/plate_%1%_pattern_layer_0.png"
|
||||
#define PATTERN_CONFIG_FILE_FORMAT "Metadata/plate_%1%.json"
|
||||
#define EMBEDDED_PRINT_FILE_FORMAT "Metadata/process_settings_%1%.config"
|
||||
#define EMBEDDED_FILAMENT_FILE_FORMAT "Metadata/filament_settings_%1%.config"
|
||||
#define EMBEDDED_PRINTER_FILE_FORMAT "Metadata/machine_settings_%1%.config"
|
||||
|
||||
#define QDT_DESIGNER_MODEL_TITLE_TAG "Title"
|
||||
#define QDT_DESIGNER_PROFILE_ID_TAG "DesignProfileId"
|
||||
#define QDT_DESIGNER_PROFILE_TITLE_TAG "ProfileTitle"
|
||||
#define QDT_DESIGNER_MODEL_ID_TAG "DesignModelId"
|
||||
|
||||
|
||||
//QDS: define assistant struct to store temporary variable during exporting 3mf
|
||||
class PackingTemporaryData
|
||||
{
|
||||
public:
|
||||
std::string _3mf_thumbnail;
|
||||
std::string _3mf_printer_thumbnail_middle;
|
||||
std::string _3mf_printer_thumbnail_small;
|
||||
|
||||
PackingTemporaryData() {}
|
||||
};
|
||||
|
||||
|
||||
//QDS: define plate data list related structures
|
||||
struct PlateData
|
||||
{
|
||||
PlateData(int plate_id, std::set<std::pair<int, int>> &obj_to_inst_list, bool lock_state) : plate_index(plate_id), locked(lock_state)
|
||||
{
|
||||
objects_and_instances.clear();
|
||||
for (std::set<std::pair<int, int>>::iterator it = obj_to_inst_list.begin(); it != obj_to_inst_list.end(); ++it)
|
||||
objects_and_instances.emplace_back(it->first, it->second);
|
||||
}
|
||||
PlateData() : plate_index(-1), locked(false)
|
||||
{
|
||||
objects_and_instances.clear();
|
||||
}
|
||||
~PlateData()
|
||||
{
|
||||
objects_and_instances.clear();
|
||||
}
|
||||
|
||||
void parse_filament_info(GCodeProcessorResult *result);
|
||||
|
||||
int plate_index;
|
||||
std::vector<std::pair<int, int>> objects_and_instances;
|
||||
std::map<int, std::pair<int, int>> obj_inst_map;
|
||||
std::string printer_model_id;
|
||||
std::string nozzle_diameters;
|
||||
std::string gcode_file;
|
||||
std::string gcode_file_md5;
|
||||
std::string thumbnail_file;
|
||||
std::string no_light_thumbnail_file;
|
||||
ThumbnailData plate_thumbnail;
|
||||
std::string top_file;
|
||||
std::string pick_file;
|
||||
//ThumbnailData pattern_thumbnail;
|
||||
//std::string pattern_file;
|
||||
std::string pattern_bbox_file;
|
||||
std::string gcode_prediction;
|
||||
std::string gcode_weight;
|
||||
std::string plate_name;
|
||||
std::vector<FilamentInfo> slice_filaments_info;
|
||||
std::vector<size_t> skipped_objects;
|
||||
DynamicPrintConfig config;
|
||||
bool is_support_used {false};
|
||||
bool is_sliced_valid = false;
|
||||
bool toolpath_outside {false};
|
||||
bool is_label_object_enabled {false};
|
||||
int timelapse_warning_code = 0; // 1<<0 sprial vase, 1<<1 by object
|
||||
|
||||
std::vector<GCodeProcessorResult::SliceWarning> warnings;
|
||||
|
||||
std::string get_gcode_prediction_str() {
|
||||
return gcode_prediction;
|
||||
}
|
||||
|
||||
std::string get_gcode_weight_str() {
|
||||
return gcode_weight;
|
||||
}
|
||||
bool locked;
|
||||
};
|
||||
|
||||
// QDS: encrypt
|
||||
enum class SaveStrategy
|
||||
{
|
||||
Default = 0,
|
||||
FullPathSources = 1,
|
||||
Zip64 = 1 << 1,
|
||||
ProductionExt = 1 << 2,
|
||||
SecureContentExt = 1 << 3,
|
||||
WithGcode = 1 << 4,
|
||||
Silence = 1 << 5,
|
||||
SkipStatic = 1 << 6,
|
||||
SkipModel = 1 << 7,
|
||||
WithSliceInfo = 1 << 8,
|
||||
SkipAuxiliary = 1 << 9,
|
||||
UseLoadedId = 1 << 10,
|
||||
ShareMesh = 1 << 11,
|
||||
|
||||
SplitModel = 0x1000 | ProductionExt,
|
||||
Encrypted = SecureContentExt | SplitModel,
|
||||
Backup = 0x10000 | WithGcode | Silence | SkipStatic | SplitModel,
|
||||
};
|
||||
|
||||
inline SaveStrategy operator | (SaveStrategy lhs, SaveStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <SaveStrategy>;
|
||||
return static_cast<SaveStrategy>(static_cast<T>(lhs) | static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
inline bool operator & (SaveStrategy & lhs, SaveStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <SaveStrategy>;
|
||||
return ((static_cast<T>(lhs) & static_cast<T>(rhs))) == static_cast<T>(rhs);
|
||||
}
|
||||
|
||||
enum class LoadStrategy
|
||||
{
|
||||
Default = 0,
|
||||
AddDefaultInstances = 1,
|
||||
CheckVersion = 2,
|
||||
LoadModel = 4,
|
||||
LoadConfig = 8,
|
||||
LoadAuxiliary = 16,
|
||||
Silence = 32,
|
||||
ImperialUnits = 64,
|
||||
|
||||
Restore = 0x10000 | LoadModel | LoadConfig | LoadAuxiliary | Silence,
|
||||
};
|
||||
|
||||
inline LoadStrategy operator | (LoadStrategy lhs, LoadStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <LoadStrategy>;
|
||||
return static_cast<LoadStrategy>(static_cast<T>(lhs) | static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
inline bool operator & (LoadStrategy & lhs, LoadStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <LoadStrategy>;
|
||||
return (static_cast<T>(lhs) & static_cast<T>(rhs)) == static_cast<T>(rhs);
|
||||
}
|
||||
|
||||
const int EXPORT_STAGE_OPEN_3MF = 0;
|
||||
const int EXPORT_STAGE_CONTENT_TYPES = 1;
|
||||
const int EXPORT_STAGE_ADD_THUMBNAILS = 2;
|
||||
const int EXPORT_STAGE_ADD_RELATIONS = 3;
|
||||
const int EXPORT_STAGE_ADD_MODELS = 4;
|
||||
const int EXPORT_STAGE_ADD_LAYER_RANGE = 5;
|
||||
const int EXPORT_STAGE_ADD_SUPPORT = 6;
|
||||
const int EXPORT_STAGE_ADD_CUSTOM_GCODE = 7;
|
||||
const int EXPORT_STAGE_ADD_PRINT_CONFIG = 8;
|
||||
const int EXPORT_STAGE_ADD_PROJECT_CONFIG = 9;
|
||||
const int EXPORT_STAGE_ADD_CONFIG_FILE = 10;
|
||||
const int EXPORT_STAGE_ADD_SLICE_INFO = 11;
|
||||
const int EXPORT_STAGE_ADD_GCODE = 12;
|
||||
const int EXPORT_STAGE_ADD_AUXILIARIES = 13;
|
||||
const int EXPORT_STAGE_FINISH = 14;
|
||||
|
||||
const int IMPORT_STAGE_RESTORE = 0;
|
||||
const int IMPORT_STAGE_OPEN = 1;
|
||||
const int IMPORT_STAGE_READ_FILES = 2;
|
||||
const int IMPORT_STAGE_EXTRACT = 3;
|
||||
const int IMPORT_STAGE_LOADING_OBJECTS = 4;
|
||||
const int IMPORT_STAGE_LOADING_PLATES = 5;
|
||||
const int IMPORT_STAGE_FINISH = 6;
|
||||
const int IMPORT_STAGE_ADD_INSTANCE = 7;
|
||||
const int IMPORT_STAGE_UPDATE_GCODE = 8;
|
||||
const int IMPORT_STAGE_CHECK_MODE_GCODE = 9;
|
||||
const int UPDATE_GCODE_RESULT = 10;
|
||||
const int IMPORT_LOAD_CONFIG = 11;
|
||||
const int IMPORT_LOAD_MODEL_OBJECTS = 12;
|
||||
const int IMPORT_STAGE_MAX = 13;
|
||||
|
||||
//QDS export 3mf progress
|
||||
typedef std::function<void(int export_stage, int current, int total, bool& cancel)> Export3mfProgressFn;
|
||||
typedef std::function<void(int import_stage, int current, int total, bool& cancel)> Import3mfProgressFn;
|
||||
|
||||
typedef std::vector<PlateData*> PlateDataPtrs;
|
||||
|
||||
typedef std::map<int, PlateData*> PlateDataMaps;
|
||||
|
||||
struct StoreParams
|
||||
{
|
||||
const char* path;
|
||||
Model* model = nullptr;
|
||||
PlateDataPtrs plate_data_list;
|
||||
int export_plate_idx = -1;
|
||||
std::vector<Preset*> project_presets;
|
||||
DynamicPrintConfig* config;
|
||||
std::vector<ThumbnailData*> thumbnail_data;
|
||||
std::vector<ThumbnailData*> no_light_thumbnail_data;
|
||||
std::vector<ThumbnailData*> top_thumbnail_data;
|
||||
std::vector<ThumbnailData*> pick_thumbnail_data;
|
||||
std::vector<ThumbnailData*> calibration_thumbnail_data;
|
||||
SaveStrategy strategy = SaveStrategy::Zip64;
|
||||
Export3mfProgressFn proFn = nullptr;
|
||||
std::vector<PlateBBoxData*> id_bboxes;
|
||||
QDTProject* project = nullptr;
|
||||
QDTProfile* profile = nullptr;
|
||||
|
||||
StoreParams() {}
|
||||
};
|
||||
|
||||
|
||||
//QDS: add plate data list related logic
|
||||
// add restore logic
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_qds_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector<Preset*>* project_presets,
|
||||
bool* is_qdt_3mf, Semver* file_version, Import3mfProgressFn proFn = nullptr, LoadStrategy strategy = LoadStrategy::Default, QDTProject *project = nullptr, int plate_id = 0);
|
||||
|
||||
extern std::string qds_3mf_get_thumbnail(const char * path);
|
||||
|
||||
extern bool load_gcode_3mf_from_stream(std::istream & data, DynamicPrintConfig* config, Model* model, PlateDataPtrs* plate_data_list,
|
||||
Semver* file_version);
|
||||
|
||||
|
||||
//QDS: add plate data list related logic
|
||||
// add backup logic
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
/*
|
||||
extern bool store_qds_3mf(const char* path,
|
||||
Model* model,
|
||||
PlateDataPtrs& plate_data_list,
|
||||
std::vector<Preset*>& project_presets,
|
||||
const DynamicPrintConfig* config,
|
||||
bool fullpath_sources,
|
||||
const std::vector<ThumbnailData*>& thumbnail_data,
|
||||
bool zip64 = true,
|
||||
bool skip_static = false,
|
||||
Export3mfProgressFn proFn = nullptr,
|
||||
bool silence = true);
|
||||
*/
|
||||
|
||||
extern bool store_qds_3mf(StoreParams& store_params);
|
||||
|
||||
extern void release_PlateData_list(PlateDataPtrs& plate_data_list);
|
||||
|
||||
// backup & restore project
|
||||
|
||||
extern void save_object_mesh(ModelObject& object);
|
||||
|
||||
extern void delete_object_mesh(ModelObject& object);
|
||||
|
||||
extern void backup_soon();
|
||||
|
||||
extern void remove_backup(Model& model, bool removeAll);
|
||||
|
||||
extern void set_backup_interval(long interval);
|
||||
|
||||
extern void set_backup_callback(std::function<void(int)> callback);
|
||||
|
||||
extern void run_backup_ui_tasks();
|
||||
|
||||
extern bool has_restore_data(std::string & path, std::string & origin);
|
||||
|
||||
extern void put_other_changes();
|
||||
|
||||
extern void clear_other_changes(bool backup);
|
||||
|
||||
extern bool has_other_changes(bool backup);
|
||||
|
||||
class SaveObjectGaurd {
|
||||
public:
|
||||
SaveObjectGaurd(ModelObject& object);
|
||||
~SaveObjectGaurd();
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* QDS_3MF_hpp_ */
|
||||
402
src/libslic3r/Format/svg.cpp
Normal file
402
src/libslic3r/Format/svg.cpp
Normal file
@@ -0,0 +1,402 @@
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
#include "svg.hpp"
|
||||
#include "nanosvg/nanosvg.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "BRepBuilderAPI_MakeWire.hxx"
|
||||
#include "BRepBuilderAPI_MakeEdge.hxx"
|
||||
#include "BRepBuilderAPI_MakeFace.hxx"
|
||||
#include "BRepPrimAPI_MakePrism.hxx"
|
||||
#include "BRepBuilderAPI_Transform.hxx"
|
||||
#include "BRepMesh_IncrementalMesh.hxx"
|
||||
#include "TopoDS_Face.hxx"
|
||||
#include "TopExp_Explorer.hxx"
|
||||
#include "TopoDS.hxx"
|
||||
#include "BRepExtrema_SelfIntersection.hxx"
|
||||
#include "libslic3r/clipper.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
const double STEP_TRANS_CHORD_ERROR = 0.005;
|
||||
const double STEP_TRANS_ANGLE_RES = 1;
|
||||
|
||||
struct Element_Info
|
||||
{
|
||||
std::string name;
|
||||
unsigned int color;
|
||||
TopoDS_Shape shape;
|
||||
};
|
||||
|
||||
bool is_same_points(gp_Pnt pt1, gp_Pnt pt2) {
|
||||
return abs(pt1.X() - pt2.X()) < 0.001
|
||||
&& abs(pt1.Y() - pt2.Y()) < 0.001
|
||||
&& abs(pt1.Z() - pt2.Z()) < 0.001;
|
||||
}
|
||||
|
||||
struct Point_2D
|
||||
{
|
||||
Point_2D(float in_x, float in_y) : x(in_x), y(in_y) {}
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
void interp_v2_v2v2(float r[2], const float a[2], const float b[2], const float t)
|
||||
{
|
||||
const float s = 1.0f - t;
|
||||
|
||||
r[0] = s * a[0] + t * b[0];
|
||||
r[1] = s * a[1] + t * b[1];
|
||||
}
|
||||
|
||||
void interp_v2_v2v2v2v2_cubic(float p[2], const float v1[2], const float v2[2], const float v3[2], const float v4[2], const float u)
|
||||
{
|
||||
float q0[2], q1[2], q2[2], r0[2], r1[2];
|
||||
|
||||
interp_v2_v2v2(q0, v1, v2, u);
|
||||
interp_v2_v2v2(q1, v2, v3, u);
|
||||
interp_v2_v2v2(q2, v3, v4, u);
|
||||
|
||||
interp_v2_v2v2(r0, q0, q1, u);
|
||||
interp_v2_v2v2(r1, q1, q2, u);
|
||||
|
||||
interp_v2_v2v2(p, r0, r1, u);
|
||||
}
|
||||
|
||||
bool is_two_lines_interaction(gp_Pnt pL1, gp_Pnt pL2, gp_Pnt pR1, gp_Pnt pR2) {
|
||||
Vec3d point1(pL1.X(), pL1.Y(), 0);
|
||||
Vec3d point2(pL2.X(), pL2.Y(), 0);
|
||||
Vec3d point3(pR1.X(), pR1.Y(), 0);
|
||||
Vec3d point4(pR2.X(), pR2.Y(), 0);
|
||||
|
||||
Vec3d line1 = point2 - point1;
|
||||
Vec3d line2 = point4 - point3;
|
||||
|
||||
Vec3d line_pos1 = point1 - point3;
|
||||
Vec3d line_pos2 = point2 - point3;
|
||||
|
||||
Vec3d line_pos3 = point3 - point1;
|
||||
Vec3d line_pos4 = point4 - point1;
|
||||
|
||||
Vec3d cross_1 = line2.cross(line_pos1);
|
||||
Vec3d cross_2 = line2.cross(line_pos2);
|
||||
|
||||
Vec3d cross_3 = line1.cross(line_pos3);
|
||||
Vec3d cross_4 = line1.cross(line_pos4);
|
||||
|
||||
return (cross_1.dot(cross_2) < 0) && (cross_3.dot(cross_4) < 0);
|
||||
}
|
||||
|
||||
bool is_profile_self_interaction(std::vector<std::pair<gp_Pnt, gp_Pnt>> profile_line_points)
|
||||
{
|
||||
for (int i = 0; i < profile_line_points.size(); ++i) {
|
||||
for (int j = i + 2; j < profile_line_points.size(); ++j)
|
||||
if (is_two_lines_interaction(profile_line_points[i].first, profile_line_points[i].second, profile_line_points[j].first, profile_line_points[j].second))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
double get_profile_area(std::vector<std::pair<gp_Pnt, gp_Pnt>> profile_line_points)
|
||||
{
|
||||
double min_x = 0;
|
||||
for (auto line_points : profile_line_points) {
|
||||
if (line_points.first.X() < min_x) min_x = line_points.first.X();
|
||||
}
|
||||
|
||||
double area = 0;
|
||||
for (auto line_points : profile_line_points) {
|
||||
bool flag = true;
|
||||
if (line_points.second.Y() < line_points.first.Y()) flag = false;
|
||||
|
||||
area += (line_points.second.X() + line_points.first.X() - 2 * min_x) * (line_points.second.Y() - line_points.first.Y()) / 2;
|
||||
}
|
||||
|
||||
return abs(area);
|
||||
}
|
||||
|
||||
bool get_svg_profile(const char *path, std::vector<Element_Info> &element_infos, std::string& message)
|
||||
{
|
||||
NSVGimage *svg_data = nullptr;
|
||||
svg_data = nsvgParseFromFile(path, "mm", 96.0f);
|
||||
if (svg_data == nullptr) {
|
||||
message = "import svg failed: could not open svg.";
|
||||
return false;
|
||||
}
|
||||
if (svg_data->shapes == nullptr) {
|
||||
message = "import svg failed: could not parse imported svg data.";
|
||||
return false;
|
||||
}
|
||||
|
||||
int name_index = 1;
|
||||
for (NSVGshape *shape = svg_data->shapes; shape; shape = shape->next) {
|
||||
char * id = shape->id;
|
||||
|
||||
int interpolation_precision = 10; // Number of interpolation points
|
||||
float step = 1.0f / float(interpolation_precision - 1);
|
||||
|
||||
// get the path point
|
||||
std::vector<std::vector<std::vector<Point_2D>>> all_path_points; // paths<profiles<curves<points>>>
|
||||
for (NSVGpath *path = shape->paths; path; path = path->next) {
|
||||
std::vector<std::vector<Point_2D>> profile_points;
|
||||
int index = 0;
|
||||
for (int i = 0; i < path->npts - 1; i += 3) {
|
||||
float * p = &path->pts[i * 2];
|
||||
float a = 0.0f;
|
||||
std::vector<Point_2D> curve_points; // points on a curve
|
||||
for (int v = 0; v < interpolation_precision; v++) {
|
||||
float pt[2];
|
||||
|
||||
// get interpolation points of Bezier curve
|
||||
interp_v2_v2v2v2v2_cubic(pt, &p[0], &p[2], &p[4], &p[6], a);
|
||||
|
||||
Point_2D point(pt[0], -pt[1]);
|
||||
curve_points.push_back(point);
|
||||
a += step;
|
||||
}
|
||||
|
||||
profile_points.push_back(curve_points);
|
||||
|
||||
// keep the adjacent curves end-to-end
|
||||
if (profile_points.size() > 1) {
|
||||
profile_points[index - 1].back() = profile_points[index].front();
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!profile_points.empty())
|
||||
all_path_points.push_back(profile_points);
|
||||
}
|
||||
|
||||
// remove duplicate points and ensure the profile is closed
|
||||
std::vector<std::vector<std::pair<gp_Pnt, gp_Pnt>>> path_line_points;
|
||||
for (auto profile_points : all_path_points) {
|
||||
std::vector<std::pair<gp_Pnt, gp_Pnt>> profile_line_points;
|
||||
for (int i = 0; i < profile_points.size(); ++i) {
|
||||
for (int j = 0; j + 1 < profile_points[i].size(); j++) {
|
||||
gp_Pnt pt1(profile_points[i][j].x, profile_points[i][j].y, 0);
|
||||
gp_Pnt pt2(profile_points[i][j + 1].x, profile_points[i][j + 1].y, 0);
|
||||
if (is_same_points(pt1, pt2))
|
||||
continue;
|
||||
|
||||
profile_line_points.push_back({pt1, pt2});
|
||||
}
|
||||
}
|
||||
|
||||
if (profile_line_points.empty())
|
||||
continue;
|
||||
|
||||
// keep the start and end points of profile connected
|
||||
if (shape->fill.gradient != nullptr)
|
||||
profile_line_points.back().second = profile_line_points[0].first;
|
||||
|
||||
if (is_profile_self_interaction(profile_line_points))
|
||||
BOOST_LOG_TRIVIAL(warning) << "the profile is self interaction.";
|
||||
|
||||
path_line_points.push_back(profile_line_points);
|
||||
}
|
||||
|
||||
if (shape->fill.gradient == nullptr) {
|
||||
double scale_size = 1e6;
|
||||
std::vector<std::vector<std::pair<gp_Pnt, gp_Pnt>>> new_path_line_points;
|
||||
float stroke_width = shape->strokeWidth * scale_size;
|
||||
Polygons polygons;
|
||||
bool close_polygon = false;
|
||||
for (int i = 0; i < path_line_points.size(); ++i) {
|
||||
ClipperLib::Path pt_path;
|
||||
for (auto line_point : path_line_points[i]) {
|
||||
pt_path.push_back(ClipperLib::IntPoint(line_point.first.X() * scale_size, line_point.first.Y() * scale_size));
|
||||
}
|
||||
pt_path.push_back(ClipperLib::IntPoint(path_line_points[i].back().second.X() * scale_size, path_line_points[i].back().second.Y() * scale_size));
|
||||
|
||||
ClipperLib::Paths out_paths;
|
||||
ClipperLib::ClipperOffset co;
|
||||
if (pt_path.front() == pt_path.back()) {
|
||||
co.AddPath(pt_path, ClipperLib::jtMiter, ClipperLib::etClosedLine);
|
||||
close_polygon = true;
|
||||
} else {
|
||||
co.AddPath(pt_path, ClipperLib::jtMiter, ClipperLib::etOpenSquare);
|
||||
close_polygon = false;
|
||||
}
|
||||
co.Execute(out_paths, stroke_width / 2);
|
||||
|
||||
for (auto out_path : out_paths) {
|
||||
polygons.emplace_back(Polygon(out_path));
|
||||
}
|
||||
}
|
||||
|
||||
if (!close_polygon)
|
||||
polygons = union_(polygons);
|
||||
|
||||
std::vector<std::pair<gp_Pnt, gp_Pnt>> profile_line_points;
|
||||
for (auto polygon : polygons) {
|
||||
profile_line_points.clear();
|
||||
for (int i = 0; i < polygon.size() - 1; ++i) {
|
||||
gp_Pnt pt1(double(polygon[i][0] / scale_size), double(polygon[i][1] / scale_size), 0);
|
||||
gp_Pnt pt2(double(polygon[i + 1][0] / scale_size), double(polygon[i + 1][1] / scale_size), 0);
|
||||
profile_line_points.push_back({pt1, pt2});
|
||||
}
|
||||
gp_Pnt pt1(double(polygon.back()[0] / scale_size), double(polygon.back()[1] / scale_size), 0);
|
||||
gp_Pnt pt2(double(polygon.front()[0] / scale_size), double(polygon.front()[1] / scale_size), 0);
|
||||
profile_line_points.push_back({pt1, pt2});
|
||||
|
||||
new_path_line_points.push_back(profile_line_points);
|
||||
}
|
||||
|
||||
path_line_points = new_path_line_points;
|
||||
}
|
||||
|
||||
// generate all profile curves
|
||||
std::vector<TopoDS_Wire> wires;
|
||||
int index = 0;
|
||||
double max_area = 0;
|
||||
for (int i = 0; i < path_line_points.size(); ++i) {
|
||||
BRepBuilderAPI_MakeWire wire_build;
|
||||
for (auto point_item : path_line_points[i]) {
|
||||
TopoDS_Edge edge_build = BRepBuilderAPI_MakeEdge(point_item.first, point_item.second);
|
||||
wire_build.Add(edge_build);
|
||||
}
|
||||
TopoDS_Wire wire = wire_build.Wire();
|
||||
double profile_area = get_profile_area(path_line_points[i]);
|
||||
if (profile_area > max_area) {
|
||||
max_area = profile_area;
|
||||
index = i;
|
||||
}
|
||||
wires.emplace_back(wire);
|
||||
}
|
||||
|
||||
if (wires.empty())
|
||||
continue;
|
||||
|
||||
gp_Vec dir(0, 0, 10);
|
||||
BRepBuilderAPI_MakeFace face_make(wires[index]);
|
||||
for (int i = 0; i < wires.size(); ++i) {
|
||||
if (index == i)
|
||||
continue;
|
||||
face_make.Add(wires[i]);
|
||||
}
|
||||
|
||||
TopoDS_Face face = face_make.Face();
|
||||
TopoDS_Shape element_shape = BRepPrimAPI_MakePrism(face, dir, false, false).Shape();
|
||||
|
||||
Element_Info element_info;
|
||||
element_info.name = "part_" + std::to_string(name_index);
|
||||
element_info.color = shape->fill.color;
|
||||
element_info.shape = element_shape;
|
||||
element_infos.push_back(element_info);
|
||||
|
||||
name_index++;
|
||||
}
|
||||
|
||||
nsvgDelete(svg_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_svg(const char *path, Model *model, std::string &message)
|
||||
{
|
||||
std::vector<Element_Info> namedSolids;
|
||||
if (!get_svg_profile(path, namedSolids, message))
|
||||
return false;
|
||||
|
||||
std::vector<stl_file> stl;
|
||||
stl.resize(namedSolids.size());
|
||||
// todo: zhimin, Can be accelerated in parallel with tbb
|
||||
for (size_t i = 0 ; i < namedSolids.size(); i++) {
|
||||
BRepMesh_IncrementalMesh mesh(namedSolids[i].shape, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true);
|
||||
// QDS: calculate total number of the nodes and triangles
|
||||
int aNbNodes = 0;
|
||||
int aNbTriangles = 0;
|
||||
for (TopExp_Explorer anExpSF(namedSolids[i].shape, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc);
|
||||
if (!aTriangulation.IsNull()) {
|
||||
aNbNodes += aTriangulation->NbNodes();
|
||||
aNbTriangles += aTriangulation->NbTriangles();
|
||||
}
|
||||
}
|
||||
|
||||
if (aNbTriangles == 0 || aNbNodes == 0)
|
||||
// QDS: No triangulation on the shape.
|
||||
continue;
|
||||
|
||||
stl[i].stats.type = inmemory;
|
||||
stl[i].stats.number_of_facets = (uint32_t) aNbTriangles;
|
||||
stl[i].stats.original_num_facets = stl[i].stats.number_of_facets;
|
||||
stl_allocate(&stl[i]);
|
||||
|
||||
std::vector<Vec3f> points;
|
||||
points.reserve(aNbNodes);
|
||||
// QDS: count faces missing triangulation
|
||||
Standard_Integer aNbFacesNoTri = 0;
|
||||
// QDS: fill temporary triangulation
|
||||
Standard_Integer aNodeOffset = 0;
|
||||
Standard_Integer aTriangleOffet = 0;
|
||||
for (TopExp_Explorer anExpSF(namedSolids[i].shape, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
const TopoDS_Shape &aFace = anExpSF.Current();
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc);
|
||||
if (aTriangulation.IsNull()) {
|
||||
++aNbFacesNoTri;
|
||||
continue;
|
||||
}
|
||||
// QDS: copy nodes
|
||||
gp_Trsf aTrsf = aLoc.Transformation();
|
||||
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
|
||||
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
|
||||
aPnt.Transform(aTrsf);
|
||||
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
|
||||
}
|
||||
// QDS: copy triangles
|
||||
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
|
||||
Standard_Integer anId[3];
|
||||
for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) {
|
||||
Poly_Triangle aTri = aTriangulation->Triangle(aTriIter);
|
||||
|
||||
aTri.Get(anId[0], anId[1], anId[2]);
|
||||
if (anOrientation == TopAbs_REVERSED) std::swap(anId[1], anId[2]);
|
||||
// QDS: save triangles facets
|
||||
stl_facet facet;
|
||||
facet.vertex[0] = points[anId[0] + aNodeOffset - 1].cast<float>();
|
||||
facet.vertex[1] = points[anId[1] + aNodeOffset - 1].cast<float>();
|
||||
facet.vertex[2] = points[anId[2] + aNodeOffset - 1].cast<float>();
|
||||
facet.extra[0] = 0;
|
||||
facet.extra[1] = 0;
|
||||
stl_normal normal;
|
||||
stl_calculate_normal(normal, &facet);
|
||||
stl_normalize_vector(normal);
|
||||
facet.normal = normal;
|
||||
stl[i].facet_start[aTriangleOffet + aTriIter - 1] = facet;
|
||||
}
|
||||
|
||||
aNodeOffset += aTriangulation->NbNodes();
|
||||
aTriangleOffet += aTriangulation->NbTriangles();
|
||||
}
|
||||
}
|
||||
|
||||
ModelObject *new_object = model->add_object();
|
||||
// new_object->name ?
|
||||
new_object->input_file = path;
|
||||
auto stage_unit3 = stl.size() / LOAD_STEP_STAGE_UNIT_NUM + 1;
|
||||
for (size_t i = 0; i < stl.size(); i++) {
|
||||
// QDS: maybe mesh is empty from step file. Don't add
|
||||
if (stl[i].stats.number_of_facets > 0) {
|
||||
TriangleMesh triangle_mesh;
|
||||
triangle_mesh.from_stl(stl[i]);
|
||||
ModelVolume *new_volume = new_object->add_volume(std::move(triangle_mesh));
|
||||
new_volume->name = namedSolids[i].name;
|
||||
new_volume->source.input_file = path;
|
||||
new_volume->source.object_idx = (int) model->objects.size() - 1;
|
||||
new_volume->source.volume_idx = (int) new_object->volumes.size() - 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace Slic3r
|
||||
7
src/libslic3r/Format/svg.hpp
Normal file
7
src/libslic3r/Format/svg.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
namespace Slic3r {
|
||||
class Model;
|
||||
|
||||
extern bool load_svg(const char *path, Model *model, std::string &message);
|
||||
|
||||
}; // namespace Slic3r
|
||||
Reference in New Issue
Block a user