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