diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm
index 139e6774b..93ae53d6c 100644
--- a/lib/Slic3r/GUI.pm
+++ b/lib/Slic3r/GUI.pm
@@ -59,17 +59,19 @@ use Wx::Event qw(EVT_IDLE EVT_COMMAND);
use base 'Wx::App';
use constant FILE_WILDCARDS => {
- known => 'Known files (*.stl, *.obj, *.amf, *.xml)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML',
+ known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.3mf)|*.3mf;*.3MF;*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML',
stl => 'STL files (*.stl)|*.stl;*.STL',
obj => 'OBJ files (*.obj)|*.obj;*.OBJ',
amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML',
+ tmf => '3MF files (*.3mf)|*.3mf;*.3MF',
ini => 'INI files *.ini|*.ini;*.INI',
gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC',
svg => 'SVG files *.svg|*.svg;*.SVG',
};
-use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf)};
+use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf tmf)};
use constant STL_MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(stl)};
use constant AMF_MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(amf)};
+use constant TMF_MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(tmf)};
our $datadir;
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
@@ -397,7 +399,7 @@ sub open_model {
|| $Slic3r::GUI::Settings->{recent}{config_directory}
|| '';
- my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF):', $dir, "",
+ my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/3MF):', $dir, "",
MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm
index 1ef787e70..d333b4b28 100644
--- a/lib/Slic3r/GUI/BedShapeDialog.pm
+++ b/lib/Slic3r/GUI/BedShapeDialog.pm
@@ -281,7 +281,7 @@ sub _init_shape_options_page {
sub _load_stl {
my ($self) = @_;
- my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+ my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF/3MF):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index aac9b85d3..cbce03093 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -124,7 +124,7 @@ sub _init_menubar {
# File menu
my $fileMenu = Wx::Menu->new;
{
- wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF…\tCtrl+O", 'Open a model', sub {
+ wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF/3MF…\tCtrl+O", 'Open a model', sub {
$self->{plater}->add if $self->{plater};
}, undef, 'brick_add.png');
wxTheApp->append_menu_item($fileMenu, "Open 2.5D TIN mesh…", 'Import a 2.5D TIN mesh', sub {
@@ -211,6 +211,9 @@ sub _init_menubar {
wxTheApp->append_menu_item($self->{plater_menu}, "Export plate with modifiers as AMF...", 'Export current plate as AMF, including all modifier meshes', sub {
$plater->export_amf;
}, undef, 'brick_go.png');
+ wxTheApp->append_menu_item($self->{plater_menu}, "Export plate with modifiers as 3MF...", 'Export current plate as 3MF, including all modifier meshes', sub {
+ $plater->export_tmf;
+ }, undef, 'brick_go.png');
$self->{object_menu} = $self->{plater}->object_menu;
$self->on_plater_object_list_changed(0);
$self->on_plater_selection_changed(0);
@@ -358,7 +361,7 @@ sub quick_slice {
my $input_file;
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
if (!$params{reslice}) {
- my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+ my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/3MF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 15eded1be..947cfa609 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -1078,8 +1078,12 @@ sub increase {
for my $i (1..$copies) {
$instance = $model_object->add_instance(
offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}),
+ z_translation => $instance->z_translation,
scaling_factor => $instance->scaling_factor,
+ scaling_vector => $instance->scaling_vector,
rotation => $instance->rotation,
+ x_rotation => $instance->x_rotation,
+ y_rotation => $instance->y_rotation,
);
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
}
@@ -1977,6 +1981,23 @@ sub export_object_amf {
$self->statusbar->SetStatusText("AMF file exported to $output_file");
}
+# Export function for a single 3MF output
+sub export_object_tmf {
+ my $self = shift;
+
+ my ($obj_idx, $object) = $self->selected_object;
+ return if !defined $obj_idx;
+
+ my $local_model = Slic3r::Model->new;
+ my $model_object = $self->{model}->objects->[$obj_idx];
+ # copy model_object -> local_model
+ $local_model->add_object($model_object);
+
+ my $output_file = $self->_get_export_file('TMF') or return;
+ $local_model->write_tmf($output_file);
+ $self->statusbar->SetStatusText("3MF file exported to $output_file");
+}
+
sub export_amf {
my $self = shift;
@@ -1987,11 +2008,21 @@ sub export_amf {
$self->statusbar->SetStatusText("AMF file exported to $output_file");
}
+sub export_tmf {
+ my $self = shift;
+
+ return if !@{$self->{objects}};
+
+ my $output_file = $self->_get_export_file('TMF') or return;
+ $self->{model}->write_tmf($output_file);
+ $self->statusbar->SetStatusText("3MF file exported to $output_file");
+}
+
sub _get_export_file {
my $self = shift;
my ($format) = @_;
- my $suffix = $format eq 'STL' ? '.stl' : '.amf';
+ my $suffix = $format eq 'STL' ? '.stl' : ( $format eq 'AMF' ? '.amf' : '.3mf');
my $output_file = $main::opt{output};
{
@@ -2006,6 +2037,10 @@ sub _get_export_file {
basename($output_file), &Slic3r::GUI::AMF_MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT)
if $format eq 'AMF';
+ $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
+ basename($output_file), &Slic3r::GUI::TMF_MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT)
+ if $format eq 'TMF';
+
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return undef;
@@ -2521,6 +2556,9 @@ sub object_menu {
wxTheApp->append_menu_item($menu, "Export object and modifiers as AMF…", 'Export this single object and all associated modifiers as AMF file', sub {
$self->export_object_amf;
}, undef, 'brick_go.png');
+ wxTheApp->append_menu_item($menu, "Export object and modifiers as 3MF…", 'Export this single object and all associated modifiers as 3MF file', sub {
+ $self->export_object_tmf;
+ }, undef, 'brick_go.png');
return $menu;
}
@@ -2602,6 +2640,12 @@ sub make_thumbnail {
$self->thumbnail->clear;
my $mesh = $model->objects->[$obj_idx]->raw_mesh;
+ # Apply x, y rotations and scaling vector in case of reading a 3MF model object.
+ my $model_instance = $model->objects->[$obj_idx]->instances->[0];
+ $mesh->rotate_x($model_instance->x_rotation);
+ $mesh->rotate_y($model_instance->y_rotation);
+ $mesh->scale_xyz($model_instance->scaling_vector);
+
if ($mesh->facets_count <= 5000) {
# remove polygons with area <= 1mm
my $area_threshold = Slic3r::Geometry::scale 1;
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index cba64323a..8f75e91ab 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -128,10 +128,18 @@ sub add_instance {
$new_instance->set_rotation($args{rotation})
if defined $args{rotation};
+ $new_instance->set_x_rotation($args{x_rotation})
+ if defined $args{x_rotation};
+ $new_instance->set_y_rotation($args{y_rotation})
+ if defined $args{y_rotation};
$new_instance->set_scaling_factor($args{scaling_factor})
if defined $args{scaling_factor};
+ $new_instance->set_scaling_vector($args{scaling_vector})
+ if defined $args{scaling_vector};
$new_instance->set_offset($args{offset})
if defined $args{offset};
+ $new_instance->set_z_translation($args{z_translation})
+ if defined $args{z_translation};
return $new_instance;
}
diff --git a/slic3r.pl b/slic3r.pl
old mode 100755
new mode 100644
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1e761a750..c6c61c2fc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -63,6 +63,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/Geometry.cpp
${LIBDIR}/libslic3r/IO.cpp
${LIBDIR}/libslic3r/IO/AMF.cpp
+ ${LIBDIR}/libslic3r/IO/TMF.cpp
${LIBDIR}/libslic3r/Layer.cpp
${LIBDIR}/libslic3r/LayerRegion.cpp
${LIBDIR}/libslic3r/LayerRegionFill.cpp
@@ -85,6 +86,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/SurfaceCollection.cpp
${LIBDIR}/libslic3r/SVG.cpp
${LIBDIR}/libslic3r/TriangleMesh.cpp
+ ${LIBDIR}/libslic3r/Zip/ZipArchive.cpp
)
add_library(admesh STATIC
diff --git a/src/slic3r.cpp b/src/slic3r.cpp
index e245caeea..8899639b7 100644
--- a/src/slic3r.cpp
+++ b/src/slic3r.cpp
@@ -134,6 +134,17 @@ main(int argc, char **argv)
print.slice();
print.write_svg(outfile);
boost::nowide::cout << "SVG file exported to " << outfile << std::endl;
+ } else if (cli_config.export_3mf) {
+ std::string outfile = cli_config.output.value;
+ if (outfile.empty()) outfile = model.objects.front()->input_file;
+ // Check if the file is already a 3mf.
+ if(outfile.substr(outfile.find_first_of('.'), outfile.length()) == ".3mf")
+ outfile = outfile.substr(0, outfile.find_first_of('.')) + "_2" + ".3mf";
+ else
+ // Remove the previous extension and add .3mf extention.
+ outfile = outfile.substr(0, outfile.find_first_of('.')) + ".3mf";
+ IO::TMF::write(model, outfile);
+ boost::nowide::cout << "File file exported to " << outfile << std::endl;
} else if (cli_config.cut_x > 0 || cli_config.cut_y > 0 || cli_config.cut > 0) {
model.repair();
model.translate(0, 0, -model.bounding_box().min.z);
diff --git a/xs/MANIFEST b/xs/MANIFEST
index b9e98e0a6..3d36de159 100644
--- a/xs/MANIFEST
+++ b/xs/MANIFEST
@@ -97,6 +97,8 @@ src/libslic3r/Geometry.hpp
src/libslic3r/IO.cpp
src/libslic3r/IO.hpp
src/libslic3r/IO/AMF.cpp
+src/libslic3r/IO/TMF.hpp
+src/libslic3r/IO/TMF.cpp
src/libslic3r/Layer.cpp
src/libslic3r/Layer.hpp
src/libslic3r/LayerRegion.cpp
@@ -140,6 +142,9 @@ src/libslic3r/SVG.hpp
src/libslic3r/TriangleMesh.cpp
src/libslic3r/TriangleMesh.hpp
src/libslic3r/utils.cpp
+src/libslic3r/Zip/ZipArchive.cpp
+src/libslic3r/Zip/ZipArchive.hpp
+src/miniz/miniz.h
src/perlglue.cpp
src/poly2tri/common/shapes.cc
src/poly2tri/common/shapes.h
@@ -183,6 +188,12 @@ t/19_model.t
t/20_print.t
t/21_gcode.t
t/22_exception.t
+t/23_3mf.t
+t/models/3mf/box.3mf
+t/models/3mf/chess.3mf
+t/models/3mf/gimblekeychain.3mf
+t/models/amf/FaceColors.amf.xml
+t/models/stl/spikey_top.stl
t/inc/22_config_bad_config_options.ini
xsp/BoundingBox.xsp
xsp/BridgeDetector.xsp
diff --git a/xs/src/libslic3r/IO.hpp b/xs/src/libslic3r/IO.hpp
index 5d02b171d..9a5c527ca 100644
--- a/xs/src/libslic3r/IO.hpp
+++ b/xs/src/libslic3r/IO.hpp
@@ -39,6 +39,13 @@ class POV
static bool write(TriangleMesh& mesh, std::string output_file);
};
+class TMF
+{
+ public:
+ static bool read(std::string input_file, Model* model);
+ static bool write(Model& model, std::string output_file);
+};
+
} }
#endif
diff --git a/xs/src/libslic3r/IO/TMF.cpp b/xs/src/libslic3r/IO/TMF.cpp
new file mode 100644
index 000000000..35d0d684c
--- /dev/null
+++ b/xs/src/libslic3r/IO/TMF.cpp
@@ -0,0 +1,891 @@
+#include "TMF.hpp"
+
+namespace Slic3r { namespace IO {
+
+bool
+TMFEditor::write_types()
+{
+ // Create a new .[Content_Types].xml file to add to the zip file later.
+ boost::nowide::ofstream fout(".[Content_Types].xml", std::ios::out | std::ios::trunc);
+ if(!fout.is_open())
+ return false;
+
+ // Write 3MF Types.
+ fout << " \n";
+ fout << "\n";
+ fout << "\n";
+ fout << "\n";
+ fout << "\n";
+ fout.close();
+
+ // Create [Content_Types].xml in the zip archive.
+ if(!zip_archive->add_entry("[Content_Types].xml", ".[Content_Types].xml"))
+ return false;
+
+ // Remove the created .[Content_Types].xml file.
+ if (remove(".[Content_Types].xml") != 0)
+ return false;
+
+ return true;
+}
+
+bool
+TMFEditor::write_relationships()
+{
+ // Create a new .rels file to add to the zip file later.
+ boost::nowide::ofstream fout(".rels", std::ios::out | std::ios::trunc);
+ if(!fout.is_open())
+ return false;
+
+ // Write the primary 3dmodel relationship.
+ fout << " \n"
+ << "\n\n";
+ fout.close();
+
+ // Create .rels in "_rels" folder in the zip archive.
+ if(!zip_archive->add_entry("_rels/.rels", ".rels"))
+ return false;
+
+ // Remove the created .rels file.
+ if (remove(".rels") != 0)
+ return false;
+
+ return true;
+}
+
+bool
+TMFEditor::write_model()
+{
+ // Create a new .3dmodel.model file to add to the zip file later.
+ boost::nowide::ofstream fout(".3dmodel.model", std::ios::out | std::ios::trunc);
+ if(!fout.is_open())
+ return false;
+
+ // Add the XML document header.
+ fout << "\n";
+
+ // Write the model element. Append any necessary namespaces.
+ fout << " \n";
+
+ // Write metadata.
+ write_metadata(fout);
+
+ // Write resources.
+ fout << " \n";
+
+ // Write Object
+ int object_index = 0;
+ for(const auto object : model->objects)
+ write_object(fout, object, object_index++);
+
+ // Close resources
+ fout << " \n";
+
+ // Write build element.
+ write_build(fout);
+
+ // Close the model element.
+ fout << "\n";
+ fout.close();
+
+ // Create .3dmodel.model in "3D" folder in the zip archive.
+ if(!zip_archive->add_entry("3D/3dmodel.model", ".3dmodel.model"))
+ return false;
+
+ // Remove the created .rels file.
+ if (remove(".3dmodel.model") != 0)
+ return false;
+
+ return true;
+}
+
+bool
+TMFEditor::write_metadata(boost::nowide::ofstream& fout)
+{
+ // Write the model metadata.
+ for (const auto metadata : model->metadata){
+ fout << " " << metadata.second << "\n";
+ }
+
+ // Write Slic3r metadata carrying the version number.
+ fout << " \n";
+
+ return true;
+}
+
+bool
+TMFEditor::write_object(boost::nowide::ofstream& fout, const ModelObject* object, int index)
+{
+ // Create the new object element.
+ fout << " \n";
+
+ return true;
+}
+
+bool
+TMFEditor::write_build(boost::nowide::ofstream& fout)
+{
+ // Create build element.
+ fout << " \n";
+
+ // Write ModelInstances for each ModelObject.
+ int object_id = 0;
+ for(const auto object : model->objects){
+ for (const auto instance : object->instances){
+ fout << " - scaling_factor,
+ cosine_rz = cos(instance->rotation),
+ sine_rz = sin(instance->rotation),
+ cosine_ry = cos(instance->y_rotation),
+ sine_ry = sin(instance->y_rotation),
+ cosine_rx = cos(instance->x_rotation),
+ sine_rx = sin(instance->x_rotation),
+ tx = instance->offset.x + object->origin_translation.x ,
+ ty = instance->offset.y + object->origin_translation.y,
+ tz = instance->z_translation;
+
+ // Add the transform
+ fout << " transform=\""
+ << (cosine_ry * cosine_rz * sc * instance->scaling_vector.x)
+ << " "
+ << (cosine_ry * sine_rz * sc) << " "
+ << (-1 * sine_ry * sc) << " "
+ << ((sine_rx * sine_ry * cosine_rz -1 * cosine_rx * sine_rz) * sc) << " "
+ << ((sine_rx * sine_ry * sine_rz + cosine_rx *cosine_rz) * sc * instance->scaling_vector.y) << " "
+ << (sine_rx * cosine_ry * sc) << " "
+ << ((cosine_rx * sine_ry * cosine_rz + sine_rx * sine_rz) * sc) << " "
+ << ((sine_rx * sine_ry * sine_rz - sine_rx * cosine_rz) * sc) << " "
+ << (cosine_rx * cosine_ry * sc * instance->scaling_vector.z) << " "
+ << (tx) << " "
+ << (ty) << " "
+ << (tz)
+ << "\"/>\n";
+
+ }
+ object_id++;
+ }
+ fout << "
\n";
+ return true;
+}
+
+bool
+TMFEditor::read_model()
+{
+ // Extract 3dmodel.model entry.
+ if(!zip_archive->extract_entry("3D/3dmodel.model", "3dmodel.model"))
+ return false;
+
+ // Read 3D/3dmodel.model file.
+ XML_Parser parser = XML_ParserCreate(NULL);
+ if (! parser) {
+ std::cout << ("Couldn't allocate memory for parser\n");
+ return false;
+ }
+
+ boost::nowide::ifstream fin("3dmodel.model", std::ios::in);
+ if (!fin.is_open()) {
+ boost::nowide::cerr << "Cannot open file: " << "3dmodel.model" << std::endl;
+ return false;
+ }
+
+ // Create model parser.
+ TMFParserContext ctx(parser, model);
+ XML_SetUserData(parser, (void*)&ctx);
+ XML_SetElementHandler(parser, TMFParserContext::startElement, TMFParserContext::endElement);
+ XML_SetCharacterDataHandler(parser, TMFParserContext::characters);
+
+ char buff[8192];
+ bool result = false;
+ while (!fin.eof()) {
+ fin.read(buff, sizeof(buff));
+ if (fin.bad()) {
+ printf("3MF model parser: Read error\n");
+ break;
+ }
+ if (XML_Parse(parser, buff, fin.gcount(), fin.eof()) == XML_STATUS_ERROR) {
+ printf("3MF model parser: Parse error at line %lu:\n%s\n",
+ XML_GetCurrentLineNumber(parser),
+ XML_ErrorString(XML_GetErrorCode(parser)));
+ break;
+ }
+ if (fin.eof()) {
+ result = true;
+ break;
+ }
+ }
+
+ // Free the parser and close the file.
+ XML_ParserFree(parser);
+ fin.close();
+
+ // Remove the extracted 3dmodel.model file.
+ if (remove("3dmodel.model") != 0)
+ return false;
+
+ if (result)
+ ctx.endDocument();
+ return result;
+}
+
+bool
+TMFEditor::produce_TMF()
+{
+ // Create a new zip archive object.
+ zip_archive = new ZipArchive(this->zip_name, 'W');
+
+ // Check it's successfully initialized.
+ if(zip_archive->z_stats() == 0) return false;
+
+ // Prepare the 3MF Zip archive by writing the relationships.
+ if(!write_relationships())
+ return false;
+
+ // Prepare the 3MF Zip archive by writing the types.
+ if(!write_types())
+ return false;
+
+ // Write the model.
+ if(!write_model()) return false;
+
+ // Finalize the archive and end writing.
+ zip_archive->finalize();
+ return true;
+}
+
+bool
+TMFEditor::consume_TMF()
+{
+ // Open the 3MF package.
+ zip_archive = new ZipArchive(this->zip_name, 'R');
+
+ // Check it's successfully initialized.
+ if(zip_archive->z_stats() == 0) return false;
+
+ // Read model.
+ if(!read_model())
+ return false;
+
+ // Close zip archive.
+ zip_archive->finalize();
+ return true;
+}
+
+TMFEditor::~TMFEditor(){
+ delete zip_archive;
+}
+
+bool
+TMF::write(Model& model, std::string output_file)
+{
+ TMFEditor tmf_writer(std::move(output_file), &model);
+ return tmf_writer.produce_TMF();
+}
+
+bool
+TMF::read(std::string input_file, Model* model)
+{
+ if(!model) return false;
+ TMFEditor tmf_reader(std::move(input_file), model);
+ return tmf_reader.consume_TMF();
+}
+
+TMFParserContext::TMFParserContext(XML_Parser parser, Model *model):
+ m_parser(parser),
+ m_path(std::vector()),
+ m_model(*model),
+ m_object(nullptr),
+ m_objects_indices(std::map()),
+ m_output_objects(std::vector()),
+ m_object_vertices(std::vector()),
+ m_volume(nullptr),
+ m_volume_facets(std::vector())
+{
+ m_path.reserve(9);
+ m_value[0] = m_value[1] = m_value[2] = "";
+}
+
+void XMLCALL
+TMFParserContext::startElement(void *userData, const char *name, const char **atts){
+ TMFParserContext *ctx = (TMFParserContext*) userData;
+ ctx->startElement(name, atts);
+}
+
+void XMLCALL
+TMFParserContext::endElement(void *userData, const char *name)
+{
+ TMFParserContext *ctx = (TMFParserContext*)userData;
+ ctx->endElement();
+}
+
+void XMLCALL
+TMFParserContext::characters(void *userData, const XML_Char *s, int len)
+{
+ TMFParserContext *ctx = (TMFParserContext*)userData;
+ ctx->characters(s, len);
+}
+
+const char*
+TMFParserContext::get_attribute(const char **atts, const char *id) {
+ if (atts == NULL)
+ return NULL;
+ while (*atts != NULL) {
+ if (strcmp(*(atts ++), id) == 0)
+ return *atts;
+ ++ atts;
+ }
+ return NULL;
+}
+
+void
+TMFParserContext::startElement(const char *name, const char **atts)
+{
+ TMFNodeType node_type_new = NODE_TYPE_UNKNOWN;
+ switch (m_path.size()){
+ case 0:
+ // Must be tag.
+ if (strcmp(name, "model") != 0)
+ this->stop();
+ node_type_new = NODE_TYPE_MODEL;
+ break;
+ case 1:
+ if (strcmp(name, "metadata") == 0) {
+ const char* metadata_name = this->get_attribute(atts, "name");
+ // Name is required if it's not found stop parsing.
+ if (!metadata_name)
+ this->stop();
+ m_value[0] = metadata_name;
+ node_type_new = NODE_TYPE_METADATA;
+ } else if (strcmp(name, "resources") == 0) {
+ node_type_new = NODE_TYPE_RESOURCES;
+ } else if (strcmp(name, "build") == 0) {
+ node_type_new = NODE_TYPE_BUILD;
+ }
+ break;
+ case 2:
+ if (strcmp(name, "object") == 0){
+ const char* object_id = get_attribute(atts, "id");
+ if (!object_id)
+ this->stop();
+
+ if(!m_object_vertices.empty())
+ this->stop();
+
+ // Create a new object in the model. This object should be included in another object if
+ // it's a component in another object.
+ m_object = m_model.add_object();
+ m_objects_indices[object_id] = int(m_model.objects.size()) - 1;
+ m_output_objects.push_back(1); // default value 1 means: it's must not be an output.
+
+ // Add part number.
+ const char* part_number = get_attribute(atts, "partnumber");
+ m_object->part_number = (!part_number) ? -1 : atoi(part_number);
+
+ // Add object name.
+ const char* object_name = get_attribute(atts, "name");
+ m_object->name = (!object_name) ? "" : object_name;
+
+ node_type_new = NODE_TYPE_OBJECT;
+ } else if (strcmp(name, "item") == 0){
+ // Get object id.
+ const char* object_id = get_attribute(atts, "objectid");
+ if(!object_id)
+ this->stop();
+ // Mark object as output.
+ m_output_objects[m_objects_indices[object_id]] = 0;
+
+ // Add instance.
+ ModelInstance* instance = m_model.objects[m_objects_indices[object_id]]->add_instance();
+
+ // Apply transformation if supplied.
+ const char* transformation_matrix = get_attribute(atts, "transform");
+ if(transformation_matrix){
+ // Decompose the affine matrix.
+ std::vector transformations;
+ if(!get_transformations(transformation_matrix, transformations))
+ this->stop();
+
+ if(transformations.size() != 9)
+ this->stop();
+
+ apply_transformation(instance, transformations);
+ }
+
+ node_type_new = NODE_TYPE_ITEM;
+ }
+ break;
+ case 3:
+ if (strcmp(name, "mesh") == 0){
+ // Create a new model volume.
+ if(m_volume)
+ this->stop();
+ node_type_new = NODE_TYPE_MESH;
+ } else if (strcmp(name, "components") == 0) {
+ node_type_new = NODE_TYPE_COMPONENTS;
+ } else if (strcmp(name, "slic3r:object") == 0) {
+ // Create a config option.
+ DynamicPrintConfig *config = nullptr;
+ if(m_path.back() == NODE_TYPE_OBJECT && m_object)
+ config = &m_object->config;
+
+ // Get the config key type.
+ const char *key = get_attribute(atts, "type");
+
+ if (config && (print_config_def.options.find(key) != print_config_def.options.end())) {
+ // Get the key config string.
+ const char *config_value = get_attribute(atts, "config");
+
+ config->set_deserialize(key, config_value);
+ }
+ node_type_new = NODE_TYPE_SLIC3R_OBJECT_CONFIG;
+ }
+ break;
+ case 4:
+ if (strcmp(name, "vertices") == 0) {
+ node_type_new = NODE_TYPE_VERTICES;
+ } else if (strcmp(name, "triangles") == 0) {
+ node_type_new = NODE_TYPE_TRIANGLES;
+ } else if (strcmp(name, "component") == 0) {
+ // Read the object id.
+ const char* object_id = get_attribute(atts, "objectid");
+ if(!object_id)
+ this->stop();
+ ModelObject* component_object = m_model.objects[m_objects_indices[object_id]];
+ // Append it to the parent (current m_object) as a mesh since Slic3r doesn't support an object inside another.
+ // after applying 3d matrix transformation if found.
+ TriangleMesh component_mesh;
+ const char* transformation_matrix = get_attribute(atts, "transform");
+ if(transformation_matrix){
+ // Decompose the affine matrix.
+ std::vector transformations;
+ if(!get_transformations(transformation_matrix, transformations))
+ this->stop();
+
+ if( transformations.size() != 9)
+ this->stop();
+
+ // Create a copy of the current object.
+ ModelObject* object_copy = m_model.add_object(*component_object, true);
+
+ apply_transformation(object_copy, transformations);
+
+ // Get the mesh of this instance object.
+ component_mesh = object_copy->raw_mesh();
+
+ // Delete the copy of the object.
+ m_model.delete_object(m_model.objects.size() - 1);
+
+ } else {
+ component_mesh = component_object->raw_mesh();
+ }
+ ModelVolume* volume = m_object->add_volume(component_mesh);
+ if(!volume)
+ this->stop();
+ node_type_new =NODE_TYPE_COMPONENT;
+ } else if (strcmp(name, "slic3r:volumes") == 0) {
+ node_type_new = NODE_TYPE_SLIC3R_VOLUMES;
+ }
+ break;
+ case 5:
+ if (strcmp(name, "vertex") == 0) {
+ const char* x = get_attribute(atts, "x");
+ const char* y = get_attribute(atts, "y");
+ const char* z = get_attribute(atts, "z");
+ if ( !x || !y || !z)
+ this->stop();
+ m_object_vertices.push_back(atof(x));
+ m_object_vertices.push_back(atof(y));
+ m_object_vertices.push_back(atof(z));
+ node_type_new = NODE_TYPE_VERTEX;
+ } else if (strcmp(name, "triangle") == 0) {
+ const char* v1 = get_attribute(atts, "v1");
+ const char* v2 = get_attribute(atts, "v2");
+ const char* v3 = get_attribute(atts, "v3");
+ if (!v1 || !v2 || !v3)
+ this->stop();
+ // Add it to the volume facets.
+ m_volume_facets.push_back(atoi(v1));
+ m_volume_facets.push_back(atoi(v2));
+ m_volume_facets.push_back(atoi(v3));
+ node_type_new = NODE_TYPE_TRIANGLE;
+ } else if (strcmp(name, "slic3r:volume") == 0) {
+ // Read start offset of the triangles.
+ m_value[0] = get_attribute(atts, "ts");
+ m_value[1] = get_attribute(atts, "te");
+ m_value[2] = get_attribute(atts, "modifier");
+ if( m_value[0].empty() || m_value[1].empty() || m_value[2].empty())
+ this->stop();
+ // Add a new volume to the current object.
+ if(!m_object)
+ this->stop();
+ m_volume = add_volume(stoi(m_value[0])*3, stoi(m_value[1]) * 3 + 2, static_cast(stoi(m_value[2])));
+ if(!m_volume)
+ this->stop();
+ node_type_new = NODE_TYPE_SLIC3R_VOLUME;
+ }
+ break;
+ case 6:
+ if( strcmp(name, "slic3r:metadata") == 0){
+ // Create a config option.
+ DynamicPrintConfig *config = nullptr;
+ if(!m_volume)
+ this->stop();
+ config = &m_volume->config;
+ const char *key = get_attribute(atts, "type");
+ if( config && (print_config_def.options.find(key) != print_config_def.options.end())){
+ const char *config_value = get_attribute(atts, "config");
+ config->set_deserialize(key, config_value);
+ }
+ node_type_new = NODE_TYPE_SLIC3R_METADATA;
+ }
+ default:
+ break;
+ }
+
+ m_path.push_back(node_type_new);
+}
+
+void
+TMFParserContext::endElement()
+{
+ switch (m_path.back()){
+ case NODE_TYPE_METADATA:
+ if( m_path.size() == 2) {
+ m_model.metadata[m_value[0]] = m_value[1];
+ m_value[1].clear();
+ }
+ break;
+ case NODE_TYPE_MESH:
+ // Add the object volume if no there are no added volumes in slic3r:volumes.
+ if(m_object->volumes.size() == 0) {
+ if(!m_object)
+ this->stop();
+ m_volume = add_volume(0, int(m_volume_facets.size()) - 1, 0);
+ if (!m_volume)
+ this->stop();
+ m_volume = nullptr;
+ }
+ break;
+ case NODE_TYPE_OBJECT:
+ if(!m_object)
+ this->stop();
+ m_object_vertices.clear();
+ m_volume_facets.clear();
+ m_object = nullptr;
+ break;
+ case NODE_TYPE_MODEL:
+ {
+ size_t deleted_objects_count = 0;
+ // According to 3MF spec. we must output objects found in item.
+ for (size_t i = 0; i < m_output_objects.size(); i++) {
+ if (m_output_objects[i]) {
+ m_model.delete_object(i - deleted_objects_count);
+ deleted_objects_count++;
+ }
+ }
+ }
+ break;
+ case NODE_TYPE_SLIC3R_VOLUME:
+ m_volume = nullptr;
+ m_value[0].clear();
+ m_value[1].clear();
+ m_value[2].clear();
+ break;
+ default:
+ break;
+ }
+
+ m_path.pop_back();
+}
+
+void
+TMFParserContext::characters(const XML_Char *s, int len)
+{
+ switch (m_path.back()) {
+ case NODE_TYPE_METADATA:
+ if(m_path.size() == 2)
+ m_value[1].append(s, len);
+ break;
+ default:
+ break;
+ }
+}
+
+void
+TMFParserContext::endDocument()
+{
+
+}
+
+void
+TMFParserContext::stop()
+{
+ XML_StopParser(m_parser, 0);
+}
+
+bool
+TMFParserContext::get_transformations(std::string matrix, std::vector &transformations)
+{
+ // Get the values.
+ double m[12];
+ int k = 0;
+ std::string tmp = "";
+ for (size_t i= 0; i < matrix.size(); i++)
+ if ((matrix[i] == ' ' && !tmp.empty()) || (i == matrix.size() - 1 && !tmp.empty())) {
+ m[k++] = std::stof(tmp);
+ tmp = "";
+ }else
+ tmp += matrix[i];
+ if(tmp != "")
+ m[k++] = std::stof(tmp);
+
+ if(k != 12)
+ return false;
+
+ // Get the translation (x,y,z) value. Remember the matrix in 3mf is a row major not a column major.
+ transformations.push_back(m[9]);
+ transformations.push_back(m[10]);
+ transformations.push_back(m[11]);
+
+ // Get the scale values.
+ double sx = sqrt( m[0] * m[0] + m[1] * m[1] + m[2] * m[2]),
+ sy = sqrt( m[3] * m[3] + m[4] * m[4] + m[5] * m[5]),
+ sz = sqrt( m[6] * m[6] + m[7] * m[7] + m[8] * m[8]);
+ transformations.push_back(sx);
+ transformations.push_back(sy);
+ transformations.push_back(sz);
+
+ // Get the rotation values.
+ // Normalize scale from the rotation matrix.
+ m[0] /= sx; m[1] /= sy; m[2] /= sz;
+ m[3] /= sx; m[4] /= sy; m[5] /= sz;
+ m[6] /= sx; m[7] /= sy; m[8] /= sz;
+
+ // Get quaternion values
+ double q_w = sqrt(std::max(0.0, 1.0 + m[0] + m[4] + m[8])) / 2,
+ q_x = sqrt(std::max(0.0, 1.0 + m[0] - m[4] - m[8])) / 2,
+ q_y = sqrt(std::max(0.0, 1.0 - m[0] + m[4] - m[8])) / 2,
+ q_z = sqrt(std::max(0.0, 1.0 - m[0] - m[4] + m[8])) / 2;
+
+ q_x *= ((q_x * (m[5] - m[7])) <= 0 ? -1 : 1);
+ q_y *= ((q_y * (m[6] - m[2])) <= 0 ? -1 : 1);
+ q_z *= ((q_z * (m[1] - m[3])) <= 0 ? -1 : 1);
+
+ // Normalize quaternion values.
+ double q_magnitude = sqrt(q_w * q_w + q_x * q_x + q_y * q_y + q_z * q_z);
+ q_w /= q_magnitude;
+ q_x /= q_magnitude;
+ q_y /= q_magnitude;
+ q_z /= q_magnitude;
+
+ double test = q_x * q_y + q_z * q_w;
+ double result_x, result_y, result_z;
+ // singularity at north pole
+ if (test > 0.499)
+ {
+ result_x = 0;
+ result_y = 2 * atan2(q_x, q_w);
+ result_z = PI / 2;
+ }
+ // singularity at south pole
+ else if (test < -0.499)
+ {
+ result_x = 0;
+ result_y = -2 * atan2(q_x, q_w);
+ result_z = -PI / 2;
+ }
+ else
+ {
+ result_x = atan2(2 * q_x * q_w - 2 * q_y * q_z, 1 - 2 * q_x * q_x - 2 * q_z * q_z);
+ result_y = atan2(2 * q_y * q_w - 2 * q_x * q_z, 1 - 2 * q_y * q_y - 2 * q_z * q_z);
+ result_z = asin(2 * q_x * q_y + 2 * q_z * q_w);
+
+ if (result_x < 0) result_x += 2 * PI;
+ if (result_y < 0) result_y += 2 * PI;
+ if (result_z < 0) result_z += 2 * PI;
+ }
+ transformations.push_back(result_x);
+ transformations.push_back(result_y);
+ transformations.push_back(result_z);
+
+ return true;
+}
+
+void
+TMFParserContext::apply_transformation(ModelObject *object, std::vector &transformations)
+{
+ // Apply scale.
+ Pointf3 vec(transformations[3], transformations[4], transformations[5]);
+ object->scale(vec);
+
+ // Apply x, y & z rotation.
+ object->rotate(transformations[6], X);
+ object->rotate(transformations[7], Y);
+ object->rotate(transformations[8], Z);
+
+ // Apply translation.
+ object->translate(transformations[0], transformations[1], transformations[2]);
+ return;
+}
+
+void
+TMFParserContext::apply_transformation(ModelInstance *instance, std::vector &transformations)
+{
+ // Apply scale.
+ instance->scaling_vector = Pointf3(transformations[3], transformations[4], transformations[5]);;
+
+ // Apply x, y & z rotation.
+ instance->rotation = transformations[8];
+ instance->x_rotation = transformations[6];
+ instance->y_rotation = transformations[7];
+
+ // Apply translation.
+ instance->offset.x = transformations[0];
+ instance->offset.y = transformations[1];
+ instance->z_translation = transformations[2];
+ return;
+}
+
+ModelVolume*
+TMFParserContext::add_volume(int start_offset, int end_offset, bool modifier)
+{
+ ModelVolume* m_volume = nullptr;
+
+ // Add a new volume.
+ m_volume = m_object->add_volume(TriangleMesh());
+ if(!m_volume || (end_offset < start_offset)) return nullptr;
+
+ // Add the triangles.
+ stl_file &stl = m_volume->mesh.stl;
+ stl.stats.type = inmemory;
+ stl.stats.number_of_facets = (1 + end_offset - start_offset) / 3;
+ stl.stats.original_num_facets = stl.stats.number_of_facets;
+ stl_allocate(&stl);
+ int i_facet = 0;
+ for (int i = start_offset; i <= end_offset ;) {
+ stl_facet &facet = stl.facet_start[i_facet / 3];
+ for (unsigned int v = 0; v < 3; ++v) {
+ memcpy(&facet.vertex[v].x, &m_object_vertices[m_volume_facets[i++] * 3], 3 * sizeof(float));
+ i_facet++;
+ }
+ }
+ stl_get_size(&stl);
+ m_volume->mesh.repair();
+ m_volume->modifier = modifier;
+
+ return m_volume;
+}
+
+} }
diff --git a/xs/src/libslic3r/IO/TMF.hpp b/xs/src/libslic3r/IO/TMF.hpp
new file mode 100644
index 000000000..bd0a35933
--- /dev/null
+++ b/xs/src/libslic3r/IO/TMF.hpp
@@ -0,0 +1,171 @@
+#ifndef SLIC3R_TMF_H
+#define SLIC3R_TMF_H
+
+#include "../IO.hpp"
+#include "../Zip/ZipArchive.hpp"
+#include
+#include
+#include
+#include