[GSoC 2017] Support 3MF Format Read/Write. (#4046)

* Include Miniz library in xs/src/miniz  and update CMakeLists.txt in /src

* Save porgress before merging from Slic3r upstream

* Add CLI --export-3mf option.

* Create the TMF Class.

* call IO::TMF:write function with the read model and the output file in slic3r.cpp
Fix miniz.c error:
 * Miniz source functions were included more than once (once during TMF.cpp and once during compiling miniz.c in /xs/src/miniz.
 * Miniz is a header file library.
 * Solution was to add #define MINIZ_HEADER_FILE_ONLY to include the header part of the single header library miniz.c.

* - Create a Zip archive contating a 3dmodel.model file conatining a basic string.
- Remove #define #define MINIZ_HEADER_FILE_ONLY in the previous commit ( It's not the solution)

* Write Model metadata element to the 3MF zip file.

* Renaming miniz.c to miniz.h,  if you want to include it in other file use #define MINIZ_HEADER_FILE_ONLY before including it.

* Adding zip lib as a wrapper

* Adding zip lib files

* Fix CMakeLists.txt zip library error:
Add zip lib to target_link_libraries.

* Add 3MF Read/Write Structure.
I think this makes TMF read/write more modular.

* Fix some errors in TMF.cpp

* Write ModelMaterial according to 3MF core specification.
fix some typos and variable names.

* Add 3MF Types and OPC relationships.
* Write types in "[Content_Types].xml" at /
* Create "_rels" folder at /

* Write for each Object its mesh vertices element in 3MF Document.

* Write Model Volumes of each object in the 3MF Document.
* write <triangles> element representing the volumes of each object.

* * Refactoring removed Camel Case style.
* Adding ModelObject part number attribute (Specific to the new 3MF format).

* Write 3MF Relationships in the zip archive.

* * Remove the first extension from the exported file name.

* Add partnumber to the constructor.

* Fix ContentTypes.xml file causing 3D Builder not to load the file.
* xmlns schema link added to < Types > element.

* * Write Slic3r metadata version.
* Write ModelMaterials custom config data.

* * Write ModelObject Slic3r custom configs data.
* Some Refactoring.

* * Write ModelVolume Sli3r custom configs to the 3MF Document.

* * Write ModelInstances to the 3MF Document and small refactoring

* * Fix ModelInstance transformation matrix in < build > element. The problem was that 3MF 3d matrices were row major matrices.

* Some Refactoring:
* Add namspaces as constant map<string, string> carrying name of namspace and the link to the namespace.

* *Fix std::map namespaces. I was using [] operator to access the map values but according to c++11 I must use .at(key) to compile.

* * Write colorgroup elements in Material Extension to 3MF core specs:
	* Created color_group attribute in Model class.
	* Created material_group attribute in ModelMaterial class. It's by default 0 whuch means basematerial or AMF read material.

* * Fix CMakeLists.txt zip target link libraries.

* Add data structure for 3MF Material Extension.

* Fix slic3r.cpp whitespace issues.

* Split TMFEditor class into TMF.hpp and TMF.cpp files

* Remove color groups attribute and put all material groups in the 3MF extension into one structure.

* Fix temprory Zip lib.
Modify TMFEditor class pivate and public memebers.

* Some Refactoring.
Make remove any extension not just the last one.

* Add a unit test case for write function.
Some refactoring

* Fix some issues found in the basic 3mf unit test case for TMF::write.

* Forget to change 2 lines in 23_3mf.t

* Some Refactoring

* Remove basename function in zip.c hoping the build pass.
Refactoring in the 23_3mf.t

* Add another test case for wrting a model containing  materials.

* Add SplitPyramid.amf test file in xs/t/amf

* Add basic TMFParserContext struct for reading 3MF XML model file.

* Add Expat handlers.

* Add member functions to TMFParserContext struc: startElement, endElement, characters and stop.

* Add 3mf extension to Model::read_from_file function
Ajust comments according to Doxygen syntax

* * ad model metadata
* Extract the 3dmodel.model file to disc.
* Read the extracted file using Expat XML er.

* Read basematerials found in the 3MF core specifications.
Fix some errors in reading metadata.
Add TMFEditor::read_model() function.

* Change material data structure to include all 3MF materials including (basematerials, m:colorgroups, etc)

* Read Object vertices.
Fix errors during reading basematerial.

* Read object mesh.

* Some Refactoring in TMFParserContext and TMF::Editor

* Read Objects (Objects with meshes, and objects containing other objects -components-)

* Read Build items (ModelInstances) without applying transformations yet.

* * Read scale and translations values from the transformation affine matrix found in the component tag.
* Some refactoring

* Get the rotaion Euler angles from the transformation matrix.

* Fix to get_transformation

* Fix metadata lost due to not copying them in the copy constructor.

* Read Slic3r custom config keys for ModelObjects.
Modify write Slic3r custom configs for ModelObject

* Read Slic3r custom configs of ModelVolumes.

* Refactor applying tranformation matri

* Fix in  reading 3mf format where wrong objects are deleted from the model

* Fix in 3MF read: When reading component tags.
When adding a component to the current object we first copy the object refered by objectid in component tag, then apply transformation to the copied object
then get the mesh of that object and finally delete this object copy and add that mesh to the current object.

* Write model metadata in descending order to make title appear first.

* Refactoring: Remove all assertions in 3MF read and replace it with TMFParserContext::stop() as if assertion fails the extracted 3dmodel.model file won't be deleted.

* Read basematerials in the original model materials map.
Remove the added model material groups.

* Fix fatal error in adding volume when reading 3MF model file.
The end offset should be equal to the size of facets minus one.

* Read color groups.

* some refactoring

* * Some fixes.

* Add model instance x, y rotations and (x,y,z) scaling.(Still working on them).

* Add translation z in the model instance.
Add x, y rotation to the model instance copy constructor.

* Fixes in writing slicer:volume tag

* Remove some todos.

* Improve write materials to adopt groups

* Some Fixes

* Some Fixes

* Read Slic3r materials custom print configs.

* Add basic test for 3mf read.

* Add another test for TMF::read().

* Revert change in slic3r.pl file and removed .DS_store files

* Use initialize list in TMFEditor constructor.
Add a new line in the end of slic3r.cpp

* Add partnumber to ModelObject::swap() function called by assignment operator.

* Deleted xs/t/3mf/.DS_Store

* Removed appling 3mf translation in the z_axis to the model instances.

* Add X&Y rotations, z_translation in 3D, 3D previews and layers plater. To be applied when duplicating objects, etc.

* Add the parent object part number to the split objects.

* Initialize Model metadata.
Add 2 STL test files.
Add all 3d model test files in xs/models folder.

* Add 3mf test (convert from/to STL).
Add partnumber in Model.xsp

* Changed material groups enum to scoped enums.
Fix a typo in 23_3mf.t

* Fix in 23_3mf.test.

* Include X&Y rotation angles, scaling vector in the transformation matrix in item tag.

* Add 3MF file extension to GUI.

* Fix reading slic3r:volume tag in 3mf document.

* Add X&Y rotations, scaling vecotr in the 2D platter. Those 3mf transformation parameters are applied in 2D, 3D, 3D preview and 2D toolpaths platters.

* Remove support for 3MF materials. The materials in 3mf are found at the object level and at the triangle level but not found at the volume level. They are not really useful in our case.

* Remove some warnings and add transformation matrix test.

* Remove headers included for IDE suggestions

* Some enhancements

* Some enhancements

* Fix a bug.

* Remove hardcoded Slic3r version & some enhancements.

* Add another test to 3mf and some enhancements.

* Remove models which became not used in 3mf tests.

* Small change in apply_transformation for model instance in tmf.cpp.

* Some Refactoring to TMF.cpp

* Remove size_t in for loops and use const auto ModelObject or ModelVolume in TMFEditor::write()
Some refactoring.

* Use nullptr instead of NULL in TMFEditor.

* Initialize  TMFParserContext data memebers instead of calling std::vector clear() function.

* Fix a typo caused by refactoring

* Add Export plate with modifiers as 3MF and Export object with modifiers as 3MF options to Slic3r GUI.

* * Remove zip library.
* Add Slic3r::ZipArchive class to handle creating zip files.

* Add extract_entry function to ZipArchive class

* * Modify TMF::write to adapt the new zip wrapper.
* Improvements in ZipArchive.

* * Improve TMFEditor:write by removing to_string template function aw we are using now ofstream.

* * Split ZipArchive class to .cpp and .hpp
* Add Doxygen documentation to ZipArchive class.
* Update MANIFEST file/
* Update CMakeLists.txt file

* * Some improvements to ZipArchive.

* Fix the failing 3MF test on windows.The problem was the end line is manipulated on windows as \r\n

* Add 3mf test files to MANIFEST file.
This commit is contained in:
Ahmed Samir 2017-09-04 21:40:50 +02:00 committed by Alessandro Ranellucci
parent 84275d08d2
commit 5cd9ecbe57
26 changed files with 6739 additions and 26 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

0
slic3r.pl Executable file → Normal file
View File

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

891
xs/src/libslic3r/IO/TMF.cpp Normal file
View File

@ -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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n";
fout << "<Types xmlns=\"" << namespaces.at("content_types") << "\">\n";
fout << "<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n";
fout << "<Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n";
fout << "</Types>\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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
<< "<Relationships xmlns=\"" << namespaces.at("relationships") <<
"\">\n<Relationship Id=\"rel0\" Target=\"/3D/3dmodel.model\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\" /></Relationships>\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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
// Write the model element. Append any necessary namespaces.
fout << "<model unit=\"millimeter\" xml:lang=\"en-US\"";
fout << " xmlns=\"" << namespaces.at("3mf") << "\"";
fout << " xmlns:slic3r=\"" << namespaces.at("slic3r") << "\"> \n";
// Write metadata.
write_metadata(fout);
// Write resources.
fout << " <resources> \n";
// Write Object
int object_index = 0;
for(const auto object : model->objects)
write_object(fout, object, object_index++);
// Close resources
fout << " </resources> \n";
// Write build element.
write_build(fout);
// Close the model element.
fout << "</model>\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 name=\"" << metadata.first << "\">" << metadata.second << "</metadata>\n";
}
// Write Slic3r metadata carrying the version number.
fout << " <slic3r:metadata version=\"" << SLIC3R_VERSION << "\"/>\n";
return true;
}
bool
TMFEditor::write_object(boost::nowide::ofstream& fout, const ModelObject* object, int index)
{
// Create the new object element.
fout << " <object id=\"" << (index + object_id) << "\" type=\"model\"";
// Add part number if found.
if (object->part_number != -1)
fout << " partnumber=\"" << (object->part_number) << "\"";
fout << ">\n";
// Write Slic3r custom configs.
for (const auto &key : object->config.keys()){
fout << " <slic3r:object type=\"" << key
<< "\" config=\"" << object->config.serialize(key) << "\"" << "/>\n";
}
// Create mesh element which contains the vertices and the volumes.
fout << " <mesh>\n";
// Create vertices element.
fout << " <vertices>\n";
// Save the start offset of each volume vertices in the object.
std::vector<int> vertices_offsets;
int num_vertices = 0;
for (const auto volume : object->volumes){
// Require mesh vertices.
volume->mesh.require_shared_vertices();
vertices_offsets.push_back(num_vertices);
const auto &stl = volume->mesh.stl;
for (int i = 0; i < stl.stats.shared_vertices; ++i)
{
// Subtract origin_translation in order to restore the coordinates of the parts
// before they were imported. Otherwise, when this 3MF file is reimported parts
// will be placed in the platter correctly, but we will have lost origin_translation
// thus any additional part added will not align with the others.
// In order to do this we compensate for this translation in the instance placement
// below.
fout << " <vertex";
fout << " x=\"" << (stl.v_shared[i].x - object->origin_translation.x) << "\"";
fout << " y=\"" << (stl.v_shared[i].y - object->origin_translation.y) << "\"";
fout << " z=\"" << (stl.v_shared[i].z - object->origin_translation.z) << "\"/>\n";
}
num_vertices += stl.stats.shared_vertices;
}
// Close the vertices element.
fout << " </vertices>\n";
// Append volumes in triangles element.
fout << " <triangles>\n";
// Save the start offset (triangle offset) of each volume (To be saved for writing Slic3r custom configs).
std::vector<int> triangles_offsets;
int num_triangles = 0;
int i_volume = 0;
for (const auto volume : object->volumes) {
int vertices_offset = vertices_offsets[i_volume];
triangles_offsets.push_back(num_triangles);
// Add the volume triangles to the triangles list.
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i){
fout << " <triangle";
for (int j = 0; j < 3; j++){
fout << " v" << (j+1) << "=\"" << (volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset) << "\"";
}
fout << "/>\n";
num_triangles++;
}
i_volume++;
}
triangles_offsets.push_back(num_triangles);
// Close the triangles element
fout << " </triangles>\n";
// Add Slic3r volumes group.
fout << " <slic3r:volumes>\n";
// Add each volume as <slic3r:volume> element containing Slic3r custom configs.
// Each volume has the following attributes:
// ts : "start triangle index", te : "end triangle index".
i_volume = 0;
for (const auto volume : object->volumes) {
fout << " <slic3r:volume ts=\"" << (triangles_offsets[i_volume]) << "\""
<< " te=\"" << (triangles_offsets[i_volume+1] - 1) << "\""
<< (volume->modifier ? " modifier=\"1\" " : " modifier=\"0\" ")
<< ">\n";
for (const std::string &key : volume->config.keys()){
fout << " <slic3r:metadata type=\"" << key
<< "\" config=\"" << volume->config.serialize(key) << "\"/>\n";
}
// Close Slic3r volume
fout << " </slic3r:volume>\n";
i_volume++;
}
// Close Slic3r volumes group.
fout << " </slic3r:volumes>\n";
// Close the mesh element.
fout << " </mesh>\n";
// Close the object element.
fout << " </object>\n";
return true;
}
bool
TMFEditor::write_build(boost::nowide::ofstream& fout)
{
// Create build element.
fout << " <build> \n";
// Write ModelInstances for each ModelObject.
int object_id = 0;
for(const auto object : model->objects){
for (const auto instance : object->instances){
fout << " <item objectid=\"" << (object_id + 1) << "\"";
// Get the rotation about x, y &z, translations and the scale vector.
double sc = instance->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 << " </build> \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<TMFNodeType>()),
m_model(*model),
m_object(nullptr),
m_objects_indices(std::map<std::string, int>()),
m_output_objects(std::vector<bool>()),
m_object_vertices(std::vector<float>()),
m_volume(nullptr),
m_volume_facets(std::vector<int>())
{
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 <model> 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<double> 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<double> 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<bool>(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<double> &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<double> &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<double> &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;
}
} }

171
xs/src/libslic3r/IO/TMF.hpp Normal file
View File

@ -0,0 +1,171 @@
#ifndef SLIC3R_TMF_H
#define SLIC3R_TMF_H
#include "../IO.hpp"
#include "../Zip/ZipArchive.hpp"
#include <cstdio>
#include <string>
#include <cstring>
#include <map>
#include <vector>
#include <algorithm>
#include <cmath>
#include <boost/move/move.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/iostream.hpp>
#include <expat/expat.h>
#define PI 3.141592653589793238
namespace Slic3r { namespace IO {
/// 3MF Editor class responsible for reading and writing 3mf files.
class TMFEditor
{
public:
const std::map<std::string, std::string> namespaces = {
{"3mf", "http://schemas.microsoft.com/3dmanufacturing/core/2015/02"}, // Default XML namespace.
{"slic3r", "http://schemas.slic3r.org/3mf/2017/06"}, // Slic3r namespace.
{"s", "http://schemas.microsoft.com/3dmanufacturing/slice/2015/07"}, // Slice Extension.
{"content_types", "http://schemas.openxmlformats.org/package/2006/content-types"}, // Content_Types namespace.
{"relationships", "http://schemas.openxmlformats.org/package/2006/relationships"} // Relationships namespace.
};
///< Namespaces in the 3MF document.
TMFEditor(std::string input_file, Model* _model): zip_archive(nullptr), zip_name(input_file), model(_model), object_id(1)
{}
/// Write TMF function called by TMF::write() function.
bool produce_TMF();
/// Read TMF function called by TMF::read() function.
bool consume_TMF();
~TMFEditor();
private:
ZipArchive* zip_archive; ///< The zip archive object for reading/writing zip files.
std::string zip_name; ///< The zip archive file name.
Model* model; ///< The model to be read or written.
int object_id; ///< The id available for the next object to be written.
/// Write the necessary types in the 3MF package. This function is called by produceTMF() function.
bool write_types();
/// Write the necessary relationships in the 3MF package. This function is called by produceTMF() function.
bool write_relationships();
/// Write the Model in a zip file. This function is called by produceTMF() function.
bool write_model();
/// Write the metadata of the model. This function is called by writeModel() function.
bool write_metadata(boost::nowide::ofstream& fout);
/// Write object of the current model. This function is called by writeModel() function.
/// \param fout boost::nowide::ofstream& fout output stream.
/// \param object ModelObject* a pointer to the object to be written.
/// \param index int the index of the object to be read
/// \return bool 1: write operation is successful , otherwise not.
bool write_object(boost::nowide::ofstream& fout, const ModelObject* object, int index);
/// Write the build element.
bool write_build(boost::nowide::ofstream& fout);
/// Read the Model.
bool read_model();
};
/// 3MF XML Document parser.
struct TMFParserContext{
enum TMFNodeType {
NODE_TYPE_UNKNOWN,
NODE_TYPE_MODEL,
NODE_TYPE_METADATA,
NODE_TYPE_RESOURCES,
NODE_TYPE_OBJECT,
NODE_TYPE_MESH,
NODE_TYPE_VERTICES,
NODE_TYPE_VERTEX,
NODE_TYPE_TRIANGLES,
NODE_TYPE_TRIANGLE,
NODE_TYPE_COMPONENTS,
NODE_TYPE_COMPONENT,
NODE_TYPE_BUILD,
NODE_TYPE_ITEM,
NODE_TYPE_SLIC3R_METADATA,
NODE_TYPE_SLIC3R_VOLUMES,
NODE_TYPE_SLIC3R_VOLUME,
NODE_TYPE_SLIC3R_OBJECT_CONFIG,
};
///< Nodes found in 3MF XML document.
XML_Parser m_parser;
///< Current Expat XML parser instance.
std::vector<TMFNodeType> m_path;
///< Current parsing path in the XML file.
Model &m_model;
///< Model to receive objects extracted from an 3MF file.
ModelObject *m_object;
///< Current object allocated for an model/object XML subtree.
std::map<std::string, int> m_objects_indices;
///< Mapping the object id in the document to the index in the model objects vector.
std::vector<bool> m_output_objects;
///< a vector determines whether each read object should be ignored (1) or not (0).
///< Ignored objects are the ones not referenced in build items.
std::vector<float> m_object_vertices;
///< Vertices parsed for the current m_object.
ModelVolume *m_volume;
///< Volume allocated for an model/object/mesh.
std::vector<int> m_volume_facets;
///< Faces collected for all volumes of the current object.
std::string m_value[3];
///< Generic string buffer for metadata, etc.
static void XMLCALL startElement(void *userData, const char *name, const char **atts);
static void XMLCALL endElement(void *userData, const char *name);
static void XMLCALL characters(void *userData, const XML_Char *s, int len); /* s is not 0 terminated. */
static const char* get_attribute(const char **atts, const char *id);
TMFParserContext(XML_Parser parser, Model *model);
void startElement(const char *name, const char **atts);
void endElement();
void endDocument();
void characters(const XML_Char *s, int len);
void stop();
/// Get scale, rotation and scale transformation from affine matrix.
/// \param matrix string the 3D matrix where elements are separated by space.
/// \return vector<double> a vector contains [translation, scale factor, xRotation, yRotation, zRotation].
bool get_transformations(std::string matrix, std::vector<double>& transformations);
/// Add a new volume to the current object.
/// \param start_offset size_t the start index in the m_volume_facets vector.
/// \param end_offset size_t the end index in the m_volume_facets vector.
/// \param modifier bool whether the volume is modifier or not.
/// \return ModelVolume* a pointer to the newly added volume.
ModelVolume* add_volume(int start_offset, int end_offset, bool modifier);
/// Apply scale, rotate & translate to the given object.
/// \param object ModelObject*
/// \param transfornmations vector<int>
void apply_transformation(ModelObject* object, std::vector<double>& transformations);
/// Apply scale, rotate & translate to the given instance.
/// \param instance ModelInstance*
/// \param transfornmations vector<int>
void apply_transformation(ModelInstance* instance, std::vector<double>& transformations);
};
} }
#endif //SLIC3R_TMF_H

View File

@ -8,7 +8,7 @@
namespace Slic3r {
Model::Model() {}
Model::Model() : metadata(std::map<std::string, std::string>()) {}
Model::Model(const Model &other)
{
@ -20,6 +20,9 @@ Model::Model(const Model &other)
this->objects.reserve(other.objects.size());
for (ModelObjectPtrs::const_iterator i = other.objects.begin(); i != other.objects.end(); ++i)
this->add_object(**i, true);
// copy metadata
this->metadata = other.metadata;
}
Model& Model::operator= (Model other)
@ -33,6 +36,7 @@ Model::swap(Model &other)
{
std::swap(this->materials, other.materials);
std::swap(this->objects, other.objects);
std::swap(this->metadata, other.metadata);
}
Model::~Model()
@ -53,6 +57,8 @@ Model::read_from_file(std::string input_file)
} else if (boost::algorithm::iends_with(input_file, ".amf")
|| boost::algorithm::iends_with(input_file, ".amf.xml")) {
IO::AMF::read(input_file, &model);
} else if (boost::algorithm::iends_with(input_file, ".3mf")) {
IO::TMF::read(input_file, &model);
} else {
throw std::runtime_error("Unknown file format");
}
@ -413,7 +419,7 @@ ModelMaterial::apply(const t_model_material_attributes &attributes)
ModelObject::ModelObject(Model *model)
: _bounding_box_valid(false), model(model)
: part_number(-1), _bounding_box_valid(false), model(model)
{}
ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes)
@ -423,6 +429,7 @@ ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volum
volumes(),
config(other.config),
layer_height_ranges(other.layer_height_ranges),
part_number(other.part_number),
origin_translation(other.origin_translation),
_bounding_box(other._bounding_box),
_bounding_box_valid(other._bounding_box_valid),
@ -456,6 +463,7 @@ ModelObject::swap(ModelObject &other)
std::swap(this->origin_translation, other.origin_translation);
std::swap(this->_bounding_box, other._bounding_box);
std::swap(this->_bounding_box_valid, other._bounding_box_valid);
std::swap(this->part_number, other.part_number);
}
ModelObject::~ModelObject()
@ -869,6 +877,7 @@ ModelObject::split(ModelObjectPtrs* new_objects)
ModelObject* new_object = this->model->add_object(*this, false);
new_object->input_file = "";
new_object->part_number = this->part_number; //According to 3mf part number should be given to the split parts.
ModelVolume* new_volume = new_object->add_volume(**mesh);
new_volume->name = volume->name;
new_volume->config = volume->config;
@ -992,11 +1001,11 @@ ModelVolume::assign_unique_material()
ModelInstance::ModelInstance(ModelObject *object)
: rotation(0), scaling_factor(1), object(object)
: rotation(0), x_rotation(0), y_rotation(0), scaling_factor(1),scaling_vector(Pointf3(1,1,1)), z_translation(0), object(object)
{}
ModelInstance::ModelInstance(ModelObject *object, const ModelInstance &other)
: rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object)
: rotation(other.rotation), x_rotation(other.x_rotation), y_rotation(other.y_rotation), scaling_factor(other.scaling_factor), scaling_vector(other.scaling_vector), offset(other.offset), z_translation(other.z_translation), object(object)
{}
ModelInstance& ModelInstance::operator= (ModelInstance other)
@ -1010,16 +1019,31 @@ ModelInstance::swap(ModelInstance &other)
{
std::swap(this->rotation, other.rotation);
std::swap(this->scaling_factor, other.scaling_factor);
std::swap(this->scaling_vector, other.scaling_vector);
std::swap(this->x_rotation, other.x_rotation);
std::swap(this->y_rotation, other.y_rotation);
std::swap(this->z_translation, other.z_translation);
std::swap(this->offset, other.offset);
}
void
ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
{
mesh->rotate_x(this->x_rotation);
mesh->rotate_y(this->y_rotation);
mesh->rotate_z(this->rotation); // rotate around mesh origin
mesh->scale(this->scaling_factor); // scale around mesh origin
if (!dont_translate)
mesh->translate(this->offset.x, this->offset.y, 0);
Pointf3 scale_versor = this->scaling_vector;
scale_versor.scale(this->scaling_factor);
mesh->scale(scale_versor); // scale around mesh origin
if (!dont_translate) {
float z_trans = 0;
// In 3mf models avoid keeping the objects under z = 0 plane.
if (this->y_rotation || this->x_rotation)
z_trans = -(mesh->stl.stats.min.z);
mesh->translate(this->offset.x, this->offset.y, z_trans);
}
}
BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate) const
@ -1027,6 +1051,10 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
// rotate around mesh origin
double c = cos(this->rotation);
double s = sin(this->rotation);
double cx = cos(this->x_rotation);
double sx = sin(this->x_rotation);
double cy = cos(this->y_rotation);
double sy = sin(this->y_rotation);
BoundingBoxf3 bbox;
for (int i = 0; i < mesh->stl.stats.number_of_facets; ++ i) {
const stl_facet &facet = mesh->stl.facet_start[i];
@ -1034,14 +1062,26 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
stl_vertex v = facet.vertex[j];
double xold = v.x;
double yold = v.y;
double zold = v.z;
// Rotation around x axis.
v.z = float(sx * yold + cx * zold);
yold = v.y = float(cx * yold - sx * zold);
zold = v.z;
// Rotation around y axis.
v.x = float(cy * xold + sy * zold);
v.z = float(-sy * xold + cy * zold);
xold = v.x;
// Rotation around z axis.
v.x = float(c * xold - s * yold);
v.y = float(s * xold + c * yold);
v.x *= float(this->scaling_factor);
v.y *= float(this->scaling_factor);
v.z *= float(this->scaling_factor);
v.x *= float(this->scaling_factor * this->scaling_vector.x);
v.y *= float(this->scaling_factor * this->scaling_vector.y);
v.z *= float(this->scaling_factor * this->scaling_vector.z);
if (!dont_translate) {
v.x += this->offset.x;
v.y += this->offset.y;
if (this->y_rotation || this->x_rotation)
v.z += -(mesh->stl.stats.min.z);
}
bbox.merge(Pointf3(v.x, v.y, v.z));
}
@ -1054,6 +1094,10 @@ BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, b
// rotate around mesh origin
double c = cos(this->rotation);
double s = sin(this->rotation);
double cx = cos(this->x_rotation);
double sx = sin(this->x_rotation);
double cy = cos(this->y_rotation);
double sy = sin(this->y_rotation);
Pointf3 pts[4] = {
bbox.min,
bbox.max,
@ -1065,11 +1109,21 @@ BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, b
Pointf3 &v = pts[i];
double xold = v.x;
double yold = v.y;
double zold = v.z;
// Rotation around x axis.
v.z = float(sx * yold + cx * zold);
yold = v.y = float(cx * yold - sx * zold);
zold = v.z;
// Rotation around y axis.
v.x = float(cy * xold + sy * zold);
v.z = float(-sy * xold + cy * zold);
xold = v.x;
// Rotation around z axis.
v.x = float(c * xold - s * yold);
v.y = float(s * xold + c * yold);
v.x *= this->scaling_factor;
v.y *= this->scaling_factor;
v.z *= this->scaling_factor;
v.x *= this->scaling_factor * this->scaling_vector.x;
v.y *= this->scaling_factor * this->scaling_vector.y;
v.z *= this->scaling_factor * this->scaling_vector.z;
if (!dont_translate) {
v.x += this->offset.x;
v.y += this->offset.y;

View File

@ -45,6 +45,9 @@ class Model
///< Objects are owned by a model. Each object may have multiple instances
///< , each instance having its own transformation (shift, scale, rotation).
std::map<std::string, std::string> metadata;
///< Model metadata <name, value>, this is needed for 3MF format read/write.
/// Model constructor.
Model();
@ -134,7 +137,7 @@ class Model
/// This transformation works in the XY plane only and no transformation in Z is performed.
/// \param point pointf object to center the model instances of model objects around
void center_instances_around_point(const Pointf &point);
void align_instances_to_origin();
/// Translate each ModelObject with x, y, z units.
@ -261,9 +264,10 @@ class ModelObject
DynamicPrintConfig config; ///< Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
t_layer_height_ranges layer_height_ranges; ///< Variation of a layer thickness for spans of Z coordinates.
int part_number; ///< It's used for the 3MF items part numbers in the build element.
Pointf3 origin_translation;
///< This vector accumulates the total translation applied to the object by the
///< center_around_origin() method. Callers might want to apply the same translation
@ -511,9 +515,13 @@ class ModelInstance
{
friend class ModelObject;
public:
double rotation; ///< Rotation around the Z axis, in radians around mesh center point
double scaling_factor; ///< scaling factor
Pointf offset; ///< offset in unscaled coordinates
double rotation; ///< Rotation around the Z axis, in radians around mesh center point.
double x_rotation; ///< Rotation around the X axis, in radians around mesh center point. Specific to 3MF format.
double y_rotation; ///< Rotation around the Y axis, in radians around mesh center point. Specific to 3MF format.
double scaling_factor; ///< uniform scaling factor.
Pointf3 scaling_vector; ///< scaling vector. Specific to 3MF format.
Pointf offset; ///< offset in unscaled coordinates.
double z_translation; ///< translation in z axis. Specific to 3MF format. It's not used anywhere in Slic3r except at writing/reading 3mf.
/// Get the owning ModelObject
/// \return ModelObject* pointer to the owner ModelObject

View File

@ -1798,6 +1798,12 @@ CLIConfigDef::CLIConfigDef()
def->tooltip = "Slice the model and export slices as SVG.";
def->cli = "export-svg";
def->default_value = new ConfigOptionBool(false);
def = this->add("export_3mf", coBool);
def->label = "Export 3MF";
def->tooltip = "Slice the model and export slices as 3MF.";
def->cli = "export-3mf";
def->default_value = new ConfigOptionBool(false);
def = this->add("info", coBool);
def->label = "Output Model Info";

View File

@ -627,6 +627,7 @@ class CLIConfig
ConfigOptionBool export_obj;
ConfigOptionBool export_pov;
ConfigOptionBool export_svg;
ConfigOptionBool export_3mf;
ConfigOptionBool info;
ConfigOptionStrings load;
ConfigOptionString output;
@ -651,6 +652,7 @@ class CLIConfig
OPT_PTR(export_obj);
OPT_PTR(export_pov);
OPT_PTR(export_svg);
OPT_PTR(export_3mf);
OPT_PTR(info);
OPT_PTR(load);
OPT_PTR(output);

View File

@ -0,0 +1,69 @@
#include "../../miniz/miniz.h"
#include "../Zip/ZipArchive.hpp"
namespace Slic3r {
ZipArchive::ZipArchive (std::string zip_archive_name, char zip_mode) : archive(mz_zip_archive()), zip_name(zip_archive_name), mode(zip_mode), stats(0), finalized(false)
{
// Initialize the miniz zip archive struct.
memset(&archive, 0, sizeof(archive));
if( mode == 'W'){
stats = mz_zip_writer_init_file(&archive, zip_name.c_str(), 0);
} else if (mode == 'R') {
stats = mz_zip_reader_init_file(&archive, zip_name.c_str(), 0);
} else {
std::cout << "Error:: Unknown zip mode" << std::endl;
}
}
mz_bool
ZipArchive::z_stats()
{
return stats;
}
mz_bool
ZipArchive::add_entry (std::string entry_path, std::string file_path)
{
stats = 0;
// Check if it's in the write mode.
if(mode != 'W')
return stats;
stats = mz_zip_writer_add_file(&archive, entry_path.c_str(), file_path.c_str(), nullptr, 0, ZIP_DEFLATE_COMPRESSION);
return stats;
}
mz_bool
ZipArchive::extract_entry (std::string entry_path, std::string file_path)
{
stats = 0;
// Check if it's in the read mode.
if (mode != 'R')
return stats;
stats = mz_zip_reader_extract_file_to_file(&archive, entry_path.c_str(), file_path.c_str(), 0);
return stats;
}
mz_bool
ZipArchive::finalize()
{
stats = 0;
// Finalize the archive and end writing if it's in the write mode.
if(mode == 'W') {
stats = mz_zip_writer_finalize_archive(&archive);
stats |= mz_zip_writer_end(&archive);
} else if (mode == 'R'){
stats = mz_zip_reader_end(&archive);
}
if(stats)
finalized = true;
return stats;
}
ZipArchive::~ZipArchive() {
if(!finalized)
this->finalize();
}
}

View File

@ -0,0 +1,56 @@
#ifndef SLIC3R_ZIPARCHIVE_H
#define SLIC3R_ZIPARCHIVE_H
#define MINIZ_HEADER_FILE_ONLY
#define ZIP_DEFLATE_COMPRESSION 8
#include <string>
#include <iostream>
#include "../../miniz/miniz.h"
namespace Slic3r {
/// A zip wrapper for Miniz lib.
class ZipArchive
{
public:
/// Create a zip archive.
/// \param zip_archive_name string the name of the zip file.
/// \param zip_mode char the zip archive mode ('R' means read mode, 'W' means write mode). you cannot change the mode for the current object.
ZipArchive(std::string zip_archive_name, char zip_mode);
/// Get the status of the previous operation applied on the zip_archive.
/// \return mz_bool 0: failure 1: success.
mz_bool z_stats();
/// Add a file to the current zip archive.
/// \param entry_path string the path of the entry in the zip archive.
/// \param file_path string the path of the file in the disk.
/// \return mz_bool 0: failure 1: success.
mz_bool add_entry (std::string entry_path, std::string file_path);
/// Extract a zip entry to a file on the disk.
/// \param entry_path string the path of the entry in the zip archive.
/// \param file_path string the path of the file in the disk.
/// \return mz_bool 0: failure 1: success.
mz_bool extract_entry (std::string entry_path, std::string file_path);
/// Finalize the archive and free any allocated memory.
/// \return mz_bool 0: failure 1: success.
mz_bool finalize();
~ZipArchive();
private:
mz_zip_archive archive; ///< Miniz zip archive struct.
const std::string zip_name; ///< The zip file name.
const char mode; ///< The zip mode either read or write.
mz_bool stats; ///< The status of the recently executed operation on the zip archive.
bool finalized; ///< Whether the zip archive is finalized or not. Used during the destructor of the object.
};
}
#endif //SLIC3R_ZIPARCHIVE_H

4916
xs/src/miniz/miniz.h Normal file

File diff suppressed because it is too large Load Diff

226
xs/t/23_3mf.t Normal file
View File

@ -0,0 +1,226 @@
#!/usr/bin/perl
use strict;
use warnings;
use Slic3r::XS;
use Test::More;
use IO::Uncompress::Unzip qw(unzip $UnzipError) ;
use Cwd qw(abs_path);
use File::Basename qw(dirname);
# Removes '\n' and '\r\n' from a string.
sub clean {
my $text = shift;
$text =~ s/\n//g;
$text =~ s/\r//g;
return $text;
}
my $current_path = abs_path($0);
my $expected_content_types = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
."<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n"
."<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n"
."<Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n"
."</Types>\n";
my $expected_relationships = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
."<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n"
."<Relationship Id=\"rel0\" Target=\"/3D/3dmodel.model\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\" /></Relationships>\n";
# Test 1: Check read/write.
{
my $input_path = dirname($current_path). "/models/3mf/box.3mf";
my $output_path = dirname($current_path). "/models/3mf/box2.3mf";
# Create a new model.
my $model = Slic3r::Model->new;
my $result = $model->read_tmf($input_path);
is($result, 1, 'Basic 3mf read test.');
$result = $model->write_tmf($output_path);
is($result, 1, 'Basic 3mf write test.');
# Delete the created file.
unlink($output_path);
}
# Test 2: Check read metadata/ objects/ components/ build items w/o or with tansformation matrics.
{
my $input_path = dirname($current_path). "/models/3mf/gimblekeychain.3mf";
# Create a new model.
my $model = Slic3r::Model->new;
$model->read_tmf($input_path);
# Check the number of read matadata.
is($model->metadata_count(), 8, 'Test 2: metadata count check.');
# Check the number of read objects.
is($model->objects_count(), 1, 'Test 2: objects count check.');
# Check the number of read instances.
is($model->get_object(0)->instances_count(), 1, 'Test 2: object instances count check.');
# Check the read object part number.
is($model->get_object(0)->part_number(), -1, 'Test 2: object part number check.');
# Check the number of read volumes.
is($model->get_object(0)->volumes_count(), 3, 'Test 2: object volumes count check.');
# Check the number of read number of facets (triangles).
is($model->get_object(0)->facets_count(), 19884, 'Test 2: object number of facets check.');
# Check the affine transformation matrix decomposition.
# Check translation.
cmp_ok($model->get_object(0)->get_instance(0)->offset()->x(), '<=', 0.0001, 'Test 2: X translation check.');
cmp_ok($model->get_object(0)->get_instance(0)->offset()->y(), '<=', 0.0001, 'Test 2: Y translation check.');
cmp_ok($model->get_object(0)->get_instance(0)->z_translation() - 0.0345364, '<=', 0.0001, 'Test 2: Z translation check.');
# Check scale.
cmp_ok($model->get_object(0)->get_instance(0)->scaling_vector()->x() - 25.4, '<=', 0.0001, 'Test 2: X scale check.');
cmp_ok($model->get_object(0)->get_instance(0)->scaling_vector()->y() - 25.4, '<=', 0.0001, 'Test 2: Y scale check.');
cmp_ok($model->get_object(0)->get_instance(0)->scaling_vector()->z() - 25.4, '<=', 0.0001, 'Test 2: Z scale check.');
# Check X, Y, & Z rotation.
cmp_ok($model->get_object(0)->get_instance(0)->x_rotation() - 6.2828, '<=', 0.0001, 'Test 2: X rotation check.');
cmp_ok($model->get_object(0)->get_instance(0)->y_rotation() - 6.2828, '<=', 0.0001, 'Test 2: Y rotation check.');
cmp_ok($model->get_object(0)->get_instance(0)->rotation(), '<=', 0.0001, 'Test 2: Z rotation check.');
}
# Test 3: Read an STL and write it as 3MF.
{
my $input_path = dirname($current_path). "/models/stl/spikey_top.stl";
my $output_path = dirname($current_path). "/models/3mf/spikey_top.3mf";
my $model = Slic3r::Model->new;
my $result = $model->read_stl($input_path);
is($result, 1, 'Test 3: read the stl model file.');
# Check initialization of 3mf specific atttributes.
is($model->metadata_count(), 0, 'Test 3: read stl model metadata count check .');
is($model->get_object(0)->instances_count(), 0, 'Test 3: object instances count check.');
is($model->get_object(0)->part_number(), -1, 'Test 3: object partnumber check.');
is($model->material_count(), 0, 'Test 3: model materials count check.');
$result = $model->write_tmf($output_path);
is($result, 1, 'Test 3: Write the 3mf model file.');
unlink($output_path);
}
# Test 4: Read an 3MF containig multiple objects and volumes and write it as STL.
{
my $input_path = dirname($current_path). "/models/3mf/gimblekeychain.3mf";
my $output_path = dirname($current_path). "/models/stl/gimblekeychain.stl";
my $model = Slic3r::Model->new;
my $result = $model->read_tmf($input_path);
is($result, 1, 'Test 4: Read 3MF file check.');
$result = $model->write_stl($output_path);
is($result, 1, 'Test 4: convert to stl check.');
unlink($output_path);
}
# Test 5: Basic Test with model containing vertices and triangles.
{
my $amf_test_file = dirname($current_path). "/models/amf/FaceColors.amf.xml";
my $tmf_output_file = dirname($current_path). "/models/3mf/FaceColors.3mf";
my $expected_model = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
."<model unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3r=\"http://schemas.slic3r.org/3mf/2017/06\"> \n"
." <slic3r:metadata version=\"" . Slic3r::VERSION() . "\"/>\n"
." <resources> \n"
." <object id=\"1\" type=\"model\">\n"
." <mesh>\n"
." <vertices>\n"
." <vertex x=\"-1\" y=\"-0.999998\" z=\"0.999998\"/>\n"
." <vertex x=\"-1\" y=\"-0.999998\" z=\"-1\"/>\n"
." <vertex x=\"1\" y=\"-1\" z=\"-0.999998\"/>\n"
." <vertex x=\"0.999996\" y=\"-1\" z=\"1\"/>\n"
." <vertex x=\"-1\" y=\"1\" z=\"0.999997\"/>\n"
." <vertex x=\"1\" y=\"0.999998\" z=\"1\"/>\n"
." <vertex x=\"1\" y=\"0.999998\" z=\"-0.999998\"/>\n"
." <vertex x=\"-0.999995\" y=\"1\" z=\"-1\"/>\n"
." </vertices>\n"
." <triangles>\n"
." <triangle v1=\"0\" v2=\"1\" v3=\"2\"/>\n"
." <triangle v1=\"0\" v2=\"2\" v3=\"3\"/>\n"
." <triangle v1=\"4\" v2=\"5\" v3=\"6\"/>\n"
." <triangle v1=\"4\" v2=\"6\" v3=\"7\"/>\n"
." <triangle v1=\"0\" v2=\"4\" v3=\"7\"/>\n"
." <triangle v1=\"0\" v2=\"7\" v3=\"1\"/>\n"
." <triangle v1=\"1\" v2=\"7\" v3=\"6\"/>\n"
." <triangle v1=\"1\" v2=\"6\" v3=\"2\"/>\n"
." <triangle v1=\"2\" v2=\"6\" v3=\"5\"/>\n"
." <triangle v1=\"2\" v2=\"5\" v3=\"3\"/>\n"
." <triangle v1=\"4\" v2=\"0\" v3=\"3\"/>\n"
." <triangle v1=\"4\" v2=\"3\" v3=\"5\"/>\n"
." </triangles>\n"
." <slic3r:volumes>\n"
." <slic3r:volume ts=\"0\" te=\"11\" modifier=\"0\" >\n"
." </slic3r:volume>\n"
." </slic3r:volumes>\n"
." </mesh>\n"
." </object>\n"
." </resources> \n"
." <build> \n"
." </build> \n"
."</model>\n";
# Create a new model.
my $model = Slic3r::Model->new;
# Read a simple AMF file.
$model->read_amf($amf_test_file);
# Write in 3MF format.
$model->write_tmf($tmf_output_file);
# Check contents in 3dmodel.model.
my $model_output ;
unzip $tmf_output_file => \$model_output, Name => "3D/3dmodel.model"
or die "unzip failed: $UnzipError\n";
is (clean($model_output), clean($expected_model), "Test 5: 3dmodel.model file matching");
# Check contents in content_types.xml.
my $content_types_output ;
unzip $tmf_output_file => \$content_types_output, Name => "[Content_Types].xml"
or die "unzip failed: $UnzipError\n";
is (clean($content_types_output), clean($expected_content_types), "Test 5: [Content_Types].xml file matching");
# Check contents in _rels.xml.
my $relationships_output ;
unzip $tmf_output_file => \$relationships_output, Name => "_rels/.rels"
or die "unzip failed: $UnzipError\n";
is (clean($relationships_output), clean($expected_relationships), "Test 5: _rels/.rels file matching");
unlink($tmf_output_file);
}
# Test 6: Read a Slic3r exported 3MF file.
{
my $input_path = dirname($current_path). "/models/3mf/chess.3mf";
my $output_path = dirname($current_path). "/models/3mf/chess_2.3mf";
# Read a 3MF model.
my $model = Slic3r::Model->new;
my $result = $model->read_tmf($input_path);
is($result, 1, 'Test 6: Read 3MF file check.');
# Export the 3MF file.
$result = $model->write_tmf($output_path);
is($result, 1, 'Test 6: Write 3MF file check.');
# Re-read the exported file.
my $model_2 = Slic3r::Model->new;
$result = $model_2->read_tmf($output_path);
is($result, 1, 'Test 6: Read second 3MF file check.');
is($model->metadata_count(), $model_2->metadata_count(), 'Test 6: metadata match check.');
# Check the number of read objects.
is($model->objects_count(), $model_2->objects_count(), 'Test 6: objects match check.');
unlink($output_path);
}
# Finish finish test cases.
done_testing();
__END__

BIN
xs/t/models/3mf/box.3mf Executable file

Binary file not shown.

BIN
xs/t/models/3mf/chess.3mf Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<amf>
<object id="1">
<metadata type="name">Default</metadata>
<mesh>
<vertices>
<vertex>
<coordinates>
<x>-1</x>
<y>-0.999998</y>
<z>0.999998</z>
</coordinates>
</vertex>
<vertex>
<coordinates>
<x>-1</x>
<y>-0.999998</y>
<z>-1</z>
</coordinates>
</vertex>
<vertex>
<coordinates>
<x>1</x>
<y>-1</y>
<z>-0.999998</z>
</coordinates>
</vertex>
<vertex>
<coordinates>
<x>0.999996</x>
<y>-1</y>
<z>1</z>
</coordinates>
</vertex>
<vertex>
<coordinates>
<x>-1</x>
<y>1</y>
<z>0.999997</z>
</coordinates>
</vertex>
<vertex>
<coordinates>
<x>1</x>
<y>0.999998</y>
<z>1</z>
</coordinates>
</vertex>
<vertex>
<coordinates>
<x>1</x>
<y>0.999998</y>
<z>-0.999998</z>
</coordinates>
</vertex>
<vertex>
<coordinates>
<x>-0.999995</x>
<y>1</y>
<z>-1</z>
</coordinates>
</vertex>
</vertices>
<volume>
<metadata type="name">tmp</metadata>
<triangle>
<color>
<r>0</r>
<g>0</g>
<b>0</b>
<a>1</a>
</color>
<v1>0</v1>
<v2>1</v2>
<v3>2</v3>
</triangle>
<triangle>
<color>
<r>0</r>
<g>0</g>
<b>0</b>
<a>1</a>
</color>
<v1>0</v1>
<v2>2</v2>
<v3>3</v3>
</triangle>
<triangle>
<color>
<r>0.960784</r>
<g>1</g>
<b>0.121569</b>
<a>1</a>
</color>
<v1>4</v1>
<v2>5</v2>
<v3>6</v3>
</triangle>
<triangle>
<color>
<r>0.960784</r>
<g>1</g>
<b>0.121569</b>
<a>1</a>
</color>
<v1>4</v1>
<v2>6</v2>
<v3>7</v3>
</triangle>
<triangle>
<color>
<r>0.423529</r>
<g>0.0156863</g>
<b>0</b>
<a>1</a>
</color>
<v1>0</v1>
<v2>4</v2>
<v3>7</v3>
</triangle>
<triangle>
<color>
<r>0.423529</r>
<g>0.0156863</g>
<b>0</b>
<a>1</a>
</color>
<v1>0</v1>
<v2>7</v2>
<v3>1</v3>
</triangle>
<triangle>
<color>
<r>1</r>
<g>1</g>
<b>1</b>
<a>1</a>
</color>
<v1>1</v1>
<v2>7</v2>
<v3>6</v3>
</triangle>
<triangle>
<color>
<r>1</r>
<g>1</g>
<b>1</b>
<a>1</a>
</color>
<v1>1</v1>
<v2>6</v2>
<v3>2</v3>
</triangle>
<triangle>
<color>
<r>0.960784</r>
<g>1</g>
<b>0</b>
<a>1</a>
</color>
<v1>2</v1>
<v2>6</v2>
<v3>5</v3>
</triangle>
<triangle>
<color>
<r>0.960784</r>
<g>1</g>
<b>0</b>
<a>1</a>
</color>
<v1>2</v1>
<v2>5</v2>
<v3>3</v3>
</triangle>
<triangle>
<color>
<r>1</r>
<g>1</g>
<b>1</b>
<a>1</a>
</color>
<v1>4</v1>
<v2>0</v2>
<v3>3</v3>
</triangle>
<triangle>
<color>
<r>1</r>
<g>1</g>
<b>1</b>
<a>1</a>
</color>
<v1>4</v1>
<v2>3</v2>
<v3>5</v3>
</triangle>
</volume>
</mesh>
</object>
</amf>

Binary file not shown.

View File

@ -29,6 +29,8 @@
%code%{ RETVAL = Slic3r::IO::OBJ::read(input_file, THIS); %};
bool read_amf(std::string input_file)
%code%{ RETVAL = Slic3r::IO::AMF::read(input_file, THIS); %};
bool read_tmf(std::string input_file)
%code%{ RETVAL = Slic3r::IO::TMF::read(input_file, THIS); %};
bool write_stl(std::string output_file, bool binary = false)
%code%{ RETVAL = Slic3r::IO::STL::write(*THIS, output_file, binary); %};
@ -36,7 +38,9 @@
%code%{ RETVAL = Slic3r::IO::OBJ::write(*THIS, output_file); %};
bool write_amf(std::string output_file)
%code%{ RETVAL = Slic3r::IO::AMF::write(*THIS, output_file); %};
bool write_tmf(std::string output_file)
%code%{ RETVAL = Slic3r::IO::TMF::write(*THIS, output_file); %};
%name{_add_object} Ref<ModelObject> add_object();
Ref<ModelObject> _add_object_clone(ModelObject* other, bool copy_volumes = true)
%code%{ RETVAL = THIS->add_object(*other, copy_volumes); %};
@ -77,6 +81,9 @@
size_t material_count() const
%code%{ RETVAL = THIS->materials.size(); %};
size_t metadata_count() const
%code%{ RETVAL = THIS->metadata.size(); %};
bool has_objects_with_no_instances();
bool add_default_instances();
Clone<BoundingBoxf3> bounding_box();
@ -192,6 +199,8 @@ ModelMaterial::attributes()
%code%{ THIS->input_file = value; %};
Ref<DynamicPrintConfig> config()
%code%{ RETVAL = &THIS->config; %};
int part_number()
%code%{ RETVAL = THIS->part_number; %};
Ref<Model> model()
%code%{ RETVAL = THIS->get_model(); %};
@ -287,17 +296,33 @@ ModelMaterial::attributes()
double rotation()
%code%{ RETVAL = THIS->rotation; %};
double x_rotation()
%code%{ RETVAL = THIS->x_rotation; %};
double y_rotation()
%code%{ RETVAL = THIS->y_rotation; %};
double scaling_factor()
%code%{ RETVAL = THIS->scaling_factor; %};
Ref<Pointf3> scaling_vector()
%code%{ RETVAL = &THIS->scaling_vector; %};
Ref<Pointf> offset()
%code%{ RETVAL = &THIS->offset; %};
double z_translation()
%code%{ RETVAL = THIS->z_translation; %};
void set_rotation(double val)
%code%{ THIS->rotation = val; %};
void set_x_rotation(double val)
%code%{ THIS->x_rotation = val; %};
void set_y_rotation(double val)
%code%{ THIS->y_rotation = val; %};
void set_scaling_factor(double val)
%code%{ THIS->scaling_factor = val; %};
void set_scaling_vector(Pointf3 *vec)
%code%{ THIS->scaling_vector = *vec; %};
void set_offset(Pointf *offset)
%code%{ THIS->offset = *offset; %};
void set_z_translation(double val)
%code%{ THIS->z_translation = val; %};
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
void transform_polygon(Polygon* polygon) const;