Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support 3MF Production Extension #10808

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 169 additions & 23 deletions src/libslic3r/Format/3mf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
const std::string CUT_INFORMATION_FILE = "Metadata/Prusa_Slicer_cut_information.xml";

static constexpr const char *RELATIONSHIP_TAG = "Relationship";

static constexpr const char* TARGET_ATTR = "Target";
static constexpr const char* RELS_TYPE_ATTR = "Type";

static constexpr const char* MODEL_TAG = "model";
static constexpr const char* RESOURCES_TAG = "resources";
static constexpr const char* OBJECT_TAG = "object";
Expand Down Expand Up @@ -118,6 +123,7 @@ static constexpr const char* Z_ATTR = "z";
static constexpr const char* V1_ATTR = "v1";
static constexpr const char* V2_ATTR = "v2";
static constexpr const char* V3_ATTR = "v3";
static constexpr const char* PPATH_ATTR = "p:path";
static constexpr const char* OBJECTID_ATTR = "objectid";
static constexpr const char* TRANSFORM_ATTR = "transform";
static constexpr const char* PRINTABLE_ATTR = "printable";
Expand Down Expand Up @@ -336,18 +342,21 @@ namespace Slic3r {

class _3MF_Importer : public _3MF_Base
{
typedef std::pair<std::string, int> PathId;

struct Component
{
int object_id;
PathId object_id;
std::string path;
Transform3d transform;

explicit Component(int object_id)
explicit Component(PathId object_id)
: object_id(object_id)
, transform(Transform3d::Identity())
{
}

Component(int object_id, const Transform3d& transform)
Component(PathId object_id, const Transform3d &transform)
: object_id(object_id)
, transform(transform)
{
Expand Down Expand Up @@ -465,11 +474,11 @@ namespace Slic3r {
};

// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
typedef std::map<int, int> IdToModelObjectMap;
typedef std::map<int, ComponentsList> IdToAliasesMap;
typedef std::map<PathId, int> IdToModelObjectMap;
typedef std::map<PathId, ComponentsList> IdToAliasesMap;
typedef std::vector<Instance> InstancesList;
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
typedef std::map<int, Geometry> IdToGeometryMap;
typedef std::map<PathId, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
Expand Down Expand Up @@ -509,6 +518,8 @@ namespace Slic3r {
std::string m_curr_metadata_name;
std::string m_curr_characters;
std::string m_name;
std::string m_start_part_path;
std::string m_model_path;

public:
_3MF_Importer();
Expand All @@ -533,6 +544,7 @@ namespace Slic3r {

bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
bool _is_svg_shape_file(const std::string &filename) const;
bool _extract_relationships_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
Expand All @@ -546,6 +558,11 @@ namespace Slic3r {
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);

// handlers to parse the .rels file
void _handle_start_relationships_element(const char* name, const char** attributes);
void _handle_end_relationships_element(const char* name);
bool _handle_start_relationship(const char **attributes, unsigned int num_attributes);

// handlers to parse the .model file
void _handle_start_model_xml_element(const char* name, const char** attributes);
void _handle_end_model_xml_element(const char* name);
Expand Down Expand Up @@ -597,7 +614,7 @@ namespace Slic3r {
bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes);
bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes);

bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
bool _create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);

void _apply_transform(ModelInstance& instance, const Transform3d& transform);

Expand All @@ -617,6 +634,10 @@ namespace Slic3r {

bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);

// callbacks to parse the .rels file
static void XMLCALL _handle_start_relationships_element(void *userData, const char *name, const char **attributes);
static void XMLCALL _handle_end_relationships_element(void* userData, const char* name);

// callbacks to parse the .model file
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name);
Expand Down Expand Up @@ -705,7 +726,14 @@ namespace Slic3r {

m_name = boost::filesystem::path(filename).stem().string();

int index = mz_zip_reader_locate_file(&archive, RELATIONSHIPS_FILE.c_str(), nullptr, 0);
if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat))
return false;
if (!_extract_relationships_from_archive(archive, stat))
return false;

// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
mz_zip_archive_file_stat start_part_stat { -1 };
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
std::string name(stat.m_filename);
Expand All @@ -715,6 +743,11 @@ namespace Slic3r {
try
{
// valid model name -> extract model
m_model_path = "/" + name;
if (m_model_path == m_start_part_path) {
start_part_stat = stat;
continue;
}
if (!_extract_model_from_archive(archive, stat)) {
close_zip_reader(&archive);
add_error("Archive does not contain a valid model");
Expand All @@ -731,6 +764,21 @@ namespace Slic3r {
}
}

if (start_part_stat.m_file_index >= 0) {
try {
m_model_path.clear();
if (!_extract_model_from_archive(archive, start_part_stat)) {
close_zip_reader(&archive);
add_error("Archive does not contain a valid model");
return false;
}
} catch (const std::exception &e) {
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
throw Slic3r::FileIOError(e.what());
}
}

// we then loop again the entries to read other files stored in the archive
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
Expand Down Expand Up @@ -863,7 +911,7 @@ namespace Slic3r {
ObjectMetadata::VolumeMetadataList volumes;
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;

IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
if (obj_metadata != m_objects_metadata.end()) {
// config data has been found, this model was saved using slic3r pe

Expand Down Expand Up @@ -954,6 +1002,47 @@ namespace Slic3r {
return boost::starts_with(name, MODEL_FOLDER) && boost::ends_with(name, ".svg");
}

bool _3MF_Importer::_extract_relationships_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
{
if (stat.m_uncomp_size == 0) {
add_error("Found invalid size");
return false;
}

_destroy_xml_parser();

m_xml_parser = XML_ParserCreate(nullptr);
if (m_xml_parser == nullptr) {
add_error("Unable to create parser");
return false;
}

XML_SetUserData(m_xml_parser, (void *) this);
XML_SetElementHandler(m_xml_parser, _handle_start_relationships_element, _handle_end_relationships_element);

void *parser_buffer = XML_GetBuffer(m_xml_parser, (int) stat.m_uncomp_size);
if (parser_buffer == nullptr) {
add_error("Unable to create buffer");
return false;
}

mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t) stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading config data to buffer");
return false;
}

if (!XML_ParseBuffer(m_xml_parser, (int) stat.m_uncomp_size, 1)) {
char error_buf[1024];
::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)),
(int) XML_GetCurrentLineNumber(m_xml_parser));
add_error(error_buf);
return false;
}

return true;
}

bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
if (stat.m_uncomp_size == 0) {
Expand Down Expand Up @@ -1511,7 +1600,58 @@ namespace Slic3r {
}
}

void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
void XMLCALL _3MF_Importer::_handle_start_relationships_element(void *userData, const char *name, const char **attributes)
{
_3MF_Importer *importer = (_3MF_Importer *) userData;
if (importer != nullptr)
importer->_handle_start_relationships_element(name, attributes);
}

void XMLCALL _3MF_Importer::_handle_end_relationships_element(void *userData, const char *name)
{
_3MF_Importer *importer = (_3MF_Importer *) userData;
if (importer != nullptr)
importer->_handle_end_relationships_element(name);
}

void _3MF_Importer::_handle_start_relationships_element(const char *name, const char **attributes)
{
if (m_xml_parser == nullptr)
return;

bool res = true;
unsigned int num_attributes = (unsigned int) XML_GetSpecifiedAttributeCount(m_xml_parser);

if (::strcmp(RELATIONSHIP_TAG, name) == 0)
res = _handle_start_relationship(attributes, num_attributes);

m_curr_characters.clear();
if (!res)
_stop_xml_parser();
}

void _3MF_Importer::_handle_end_relationships_element(const char *name)
{
if (m_xml_parser == nullptr)
return;

bool res = true;

if (!res)
_stop_xml_parser();
}

bool _3MF_Importer::_handle_start_relationship(const char **attributes, unsigned int num_attributes)
{
std::string path = get_attribute_value_string(attributes, num_attributes, TARGET_ATTR);
std::string type = get_attribute_value_string(attributes, num_attributes, RELS_TYPE_ATTR);
if (boost::starts_with(type, "http://schemas.microsoft.com/3dmanufacturing/") && boost::ends_with(type, "3dmodel")) {
m_start_part_path = path;
}
return true;
}

void _3MF_Importer::_handle_start_model_xml_element(const char *name, const char **attributes)
{
if (m_xml_parser == nullptr)
return;
Expand Down Expand Up @@ -1650,6 +1790,9 @@ namespace Slic3r {

bool _3MF_Importer::_handle_end_model()
{
if (!m_model_path.empty())
return true;

// deletes all non-built or non-instanced objects
for (const IdToModelObjectMap::value_type& object : m_objects) {
if (object.second >= int(m_model->objects.size())) {
Expand Down Expand Up @@ -1718,33 +1861,34 @@ namespace Slic3r {
bool _3MF_Importer::_handle_end_object()
{
if (m_curr_object.object != nullptr) {
PathId object_id{m_model_path, m_curr_object.id};
if (m_curr_object.geometry.empty()) {
// no geometry defined
// remove the object from the model
m_model->delete_object(m_curr_object.object);

if (m_curr_object.components.empty()) {
// no components defined -> invalid object, delete it
IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id);
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
if (object_item != m_objects.end())
m_objects.erase(object_item);

IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id);
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
if (alias_item != m_objects_aliases.end())
m_objects_aliases.erase(alias_item);
}
else
// adds components to aliases
m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components });
m_objects_aliases.insert({ object_id, m_curr_object.components });
}
else {
// geometry defined, store it for later use
m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) });
m_geometries.insert({ object_id, std::move(m_curr_object.geometry) });

// stores the object for later use
if (m_objects.find(m_curr_object.id) == m_objects.end()) {
m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx });
m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself
if (m_objects.find(object_id) == m_objects.end()) {
m_objects.insert({ object_id, m_curr_object.model_object_idx });
m_objects_aliases.insert({object_id, {1, Component(object_id)}}); // aliases itself
}
else {
add_error("Found object with duplicate id");
Expand Down Expand Up @@ -1855,19 +1999,21 @@ namespace Slic3r {

bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
{
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
std::string path = get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));

IdToModelObjectMap::iterator object_item = m_objects.find(object_id);

PathId path_id { path, object_id };
IdToModelObjectMap::iterator object_item = m_objects.find(path_id);
if (object_item == m_objects.end()) {
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(path_id);
if (alias_item == m_objects_aliases.end()) {
add_error("Found component with invalid object id");
return false;
}
}

m_curr_object.components.emplace_back(object_id, transform);
m_curr_object.components.emplace_back(path_id, transform);

return true;
}
Expand Down Expand Up @@ -1903,7 +2049,7 @@ namespace Slic3r {
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);

return _create_object_instance(object_id, transform, printable, 1);
return _create_object_instance({m_model_path, object_id}, transform, printable, 1);
}

bool _3MF_Importer::_handle_end_item()
Expand Down Expand Up @@ -2059,7 +2205,7 @@ namespace Slic3r {
return true;
}

bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
bool _3MF_Importer::_create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
{
static const unsigned int MAX_RECURSIONS = 10;

Expand Down