mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-07-31 09:21:58 +08:00
[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:
parent
84275d08d2
commit
5cd9ecbe57
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
11
xs/MANIFEST
11
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
|
||||
|
@ -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
891
xs/src/libslic3r/IO/TMF.cpp
Normal 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
171
xs/src/libslic3r/IO/TMF.hpp
Normal 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
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
69
xs/src/libslic3r/Zip/ZipArchive.cpp
Normal file
69
xs/src/libslic3r/Zip/ZipArchive.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
56
xs/src/libslic3r/Zip/ZipArchive.hpp
Normal file
56
xs/src/libslic3r/Zip/ZipArchive.hpp
Normal 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
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
226
xs/t/23_3mf.t
Normal 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
BIN
xs/t/models/3mf/box.3mf
Executable file
Binary file not shown.
BIN
xs/t/models/3mf/chess.3mf
Normal file
BIN
xs/t/models/3mf/chess.3mf
Normal file
Binary file not shown.
BIN
xs/t/models/3mf/gimblekeychain.3mf
Normal file
BIN
xs/t/models/3mf/gimblekeychain.3mf
Normal file
Binary file not shown.
201
xs/t/models/amf/FaceColors.amf.xml
Normal file
201
xs/t/models/amf/FaceColors.amf.xml
Normal 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>
|
BIN
xs/t/models/stl/spikey_top.stl
Normal file
BIN
xs/t/models/stl/spikey_top.stl
Normal file
Binary file not shown.
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user