From 323b12fbeec4fcd3f93c2a9d3267e884e13a7c36 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Thu, 21 Oct 2021 15:33:51 +0200 Subject: [PATCH] Change 3mf XML store/load text - NOT back compatible --- src/libslic3r/Format/3mf.cpp | 224 ++++++++++++++++-------- src/libslic3r/TextConfiguration.hpp | 1 + src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 33 ++-- tests/libslic3r/test_emboss.cpp | 2 - 5 files changed, 170 insertions(+), 92 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index acf250eaa..a843b3b8c 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -34,7 +35,7 @@ namespace pt = boost::property_tree; #include #include "miniz_extension.hpp" -#include "TextConfigurationSerialization.hpp" +#include "TextConfiguration.hpp" #include @@ -125,7 +126,6 @@ static constexpr const char* VOLUME_TYPE = "volume"; static constexpr const char* NAME_KEY = "name"; static constexpr const char* MODIFIER_KEY = "modifier"; -static constexpr const char* TEXT_CONFIGURATION_KEY = "text_configuration"; static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; static constexpr const char* MATRIX_KEY = "matrix"; static constexpr const char* SOURCE_FILE_KEY = "source_file"; @@ -143,6 +143,18 @@ static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed"; static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; +// Store / load of TextConfiguration +static constexpr const char *TEXT_TAG = "emboss"; +static constexpr const char *TEXT_DATA_ATTR = "text"; +// TextConfiguration::FontItem +static constexpr const char *FONT_DESCRIPTOR_ATTR = "font_descriptor"; + +// TextConfiguration::FontProperty +static constexpr const char *CHAR_GAP_ATTR = "char_gap"; +static constexpr const char *LINE_GAP_ATTR = "line_gap"; +static constexpr const char *FLATNESS_ATTR = "flatness"; +static constexpr const char *LINE_HEIGHT_ATTR = "line_height"; +static constexpr const char *DEPTH_ATTR = "depth"; const unsigned int VALID_OBJECT_TYPES_COUNT = 1; const char* VALID_OBJECT_TYPES[] = @@ -394,6 +406,7 @@ namespace Slic3r { unsigned int last_triangle_id; MetadataList metadata; RepairedMeshErrors mesh_stats; + std::optional text_configuration; VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id) : first_triangle_id(first_triangle_id) @@ -531,6 +544,9 @@ namespace Slic3r { bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_metadata(); + bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes); + bool _handle_end_text_configuration(); + bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Transform3d& transform); @@ -1408,6 +1424,8 @@ namespace Slic3r { res = _handle_start_config_volume_mesh(attributes, num_attributes); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_start_config_metadata(attributes, num_attributes); + else if (::strcmp(TEXT_TAG, name) == 0) + res = _handle_start_text_configuration(attributes, num_attributes); if (!res) _stop_xml_parser(); @@ -1430,6 +1448,8 @@ namespace Slic3r { res = _handle_end_config_volume_mesh(); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_end_config_metadata(); + else if (::strcmp(TEXT_TAG, name) == 0) + res = _handle_end_text_configuration(); if (!res) _stop_xml_parser(); @@ -1754,6 +1774,40 @@ namespace Slic3r { return true; } + bool _3MF_Importer::_handle_start_text_configuration(const char **attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Cannot assign volume mesh to a valid object"); + return false; + } + if (object->second.volumes.empty()) { + add_error("Cannot assign mesh to a valid volume"); + return false; + } + ObjectMetadata::VolumeMetadata& volume = object->second.volumes.back(); + + std::string text = get_attribute_value_string(attributes, num_attributes, TEXT_DATA_ATTR); + std::string font_name = ""; + std::string font_descriptor = get_attribute_value_string(attributes, num_attributes, FONT_DESCRIPTOR_ATTR); + FontItem::Type font_type = FontItem::Type::loaded_from_3mf; // default font type + FontItem fi(font_name, font_descriptor, font_type); + + FontProp fp; + fp.char_gap = get_attribute_value_int(attributes, num_attributes, CHAR_GAP_ATTR); + fp.line_gap = get_attribute_value_int(attributes, num_attributes, LINE_GAP_ATTR); + fp.flatness = get_attribute_value_float(attributes, num_attributes, FLATNESS_ATTR); + fp.size_in_mm = get_attribute_value_float(attributes, num_attributes, LINE_HEIGHT_ATTR); + fp.emboss = get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR); + + volume.text_configuration = TextConfiguration(fi, fp, text); + return true; + } + + bool _3MF_Importer::_handle_end_text_configuration() { + return true; + } + bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; @@ -1870,7 +1924,7 @@ namespace Slic3r { return false; } if (object->second.volumes.empty()) { - add_error("Cannot assign mesh to a valid olume"); + add_error("Cannot assign mesh to a valid volume"); return false; } @@ -2034,6 +2088,7 @@ namespace Slic3r { volume->supported_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); volume->mmu_segmentation_facets.shrink_to_fit(); + volume->text_configuration = volume_data.text_configuration; // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { @@ -2043,8 +2098,6 @@ namespace Slic3r { volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); else if (metadata.key == VOLUME_TYPE_KEY) volume->set_type(ModelVolume::type_from_string(metadata.value)); - else if (metadata.key == TEXT_CONFIGURATION_KEY) - volume->text_configuration = TextConfigurationSerialization::deserialize(metadata.value); else if (metadata.key == SOURCE_FILE_KEY) volume->source.input_file = metadata.value; else if (metadata.key == SOURCE_OBJECT_ID_KEY) @@ -2969,85 +3022,106 @@ namespace Slic3r { for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) { const ModelObject* obj = obj_metadata.second.object; - if (obj != nullptr) { - // Output of instances count added because of github #3435, currently not used by PrusaSlicer - stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n"; + if (obj == nullptr) continue; + // Output of instances count added because of github #3435, currently not used by PrusaSlicer + stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n"; - // stores object's name - if (!obj->name.empty()) - add_metadata(stream, 2, MetadataType::object, "name", xml_escape(obj->name)); - // stores object's config data - for (const std::string& key : obj->config.keys()) - add_metadata(stream, 2, MetadataType::object, key, obj->config.opt_serialize(key)); + // stores object's name + if (!obj->name.empty()) + add_metadata(stream, 2, MetadataType::object, "name", xml_escape(obj->name)); + // stores object's config data + for (const std::string& key : obj->config.keys()) + add_metadata(stream, 2, MetadataType::object, key, obj->config.opt_serialize(key)); - for (const ModelVolume* volume : obj_metadata.second.object->volumes) { - if (volume != nullptr) { - const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets; - VolumeToOffsetsMap::const_iterator it = offsets.find(volume); - if (it != offsets.end()) { - // stores volume's offsets - stream << " <" << VOLUME_TAG << " "; - stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" "; - stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n"; + for (const ModelVolume* volume : obj_metadata.second.object->volumes) { + if (volume == nullptr) continue; + const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets; + VolumeToOffsetsMap::const_iterator it = offsets.find(volume); + if (it != offsets.end()) { + // stores volume's offsets + stream << " <" << VOLUME_TAG << " "; + stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" "; + stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n"; - // stores volume's name - if (!volume->name.empty()) - add_metadata(stream, 3, MetadataType::volume, NAME_KEY, xml_escape(volume->name)); + // stores volume's name + if (!volume->name.empty()) + add_metadata(stream, 3, MetadataType::volume, NAME_KEY, xml_escape(volume->name)); - // stores volume's modifier field (legacy, to support old slicers) - if (volume->is_modifier()) - add_metadata(stream, 3, MetadataType::volume, MODIFIER_KEY, "1"); - // stores volume's type (overrides the modifier field above) - add_metadata(stream, 3, MetadataType::volume, VOLUME_TYPE_KEY, ModelVolume::type_to_string(volume->type())); + // stores volume's modifier field (legacy, to support old slicers) + if (volume->is_modifier()) + add_metadata(stream, 3, MetadataType::volume, MODIFIER_KEY, "1"); + // stores volume's type (overrides the modifier field above) + add_metadata(stream, 3, MetadataType::volume, VOLUME_TYPE_KEY, ModelVolume::type_to_string(volume->type())); - // stores volume's text data - if (volume->text_configuration.has_value()) - add_metadata(stream, 3, MetadataType::volume, TEXT_CONFIGURATION_KEY, - xml_escape(TextConfigurationSerialization::serialize(*volume->text_configuration))); - - // stores volume's local matrix - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; - Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - stream << matrix(r, c); - if (r != 3 || c != 3) - stream << " "; - } - } - stream << "\"/>\n"; - - // stores volume's source data - { - std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); - std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; - if (! volume->source.input_file.empty()) { - stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; - stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; - stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; - } - assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); - if (volume->source.is_converted_from_inches) - stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - else if (volume->source.is_converted_from_meters) - stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - } - - // stores volume's config data - for (const std::string& key : volume->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; - } - - stream << " \n"; + // stores volume's local matrix + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; + Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { + stream << matrix(r, c); + if (r != 3 || c != 3) + stream << " "; } } - } + stream << "\"/>\n"; - stream << " \n"; + // stores volume's source data + { + std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); + std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; + if (! volume->source.input_file.empty()) { + stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; + stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; + stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; + } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); + if (volume->source.is_converted_from_inches) + stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + else if (volume->source.is_converted_from_meters) + stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + } + + // stores volume's config data + for (const std::string& key : volume->config.keys()) { + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; + } + + // stores volume's text data + if (volume->text_configuration.has_value()) { + + const TextConfiguration& tc = *volume->text_configuration; + stream << " <" << TEXT_TAG << " "; + + stream << TEXT_DATA_ATTR << "=\"" << xml_escape(tc.text) << "\" "; + // font item + const auto &fi = tc.font_item; + stream << FONT_DESCRIPTOR_ATTR << "=\"" << fi.path << "\" "; + // font property + const auto &fp = tc.font_prop; + stream << CHAR_GAP_ATTR << "=\"" << fp.char_gap << "\" "; + stream << LINE_GAP_ATTR << "=\"" << fp.line_gap << "\" "; + stream << FLATNESS_ATTR << "=\"" << fp.flatness << "\" "; + stream << LINE_HEIGHT_ATTR << "=\"" << fp.size_in_mm << "\" "; + stream << DEPTH_ATTR << "=\"" << fp.emboss << "\" "; + stream << "/>\n"; // end TEXT_TAG + } + + // stores mesh's statistics + const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors; + stream << " <" << MESH_TAG << " "; + stream << MESH_STAT_EDGES_FIXED << "=\"" << stats.edges_fixed << "\" "; + stream << MESH_STAT_DEGENERATED_FACETS << "=\"" << stats.degenerate_facets << "\" "; + stream << MESH_STAT_FACETS_REMOVED << "=\"" << stats.facets_removed << "\" "; + stream << MESH_STAT_FACETS_RESERVED << "=\"" << stats.facets_reversed << "\" "; + stream << MESH_STAT_BACKWARDS_EDGES << "=\"" << stats.backwards_edges << "\"/>\n"; + + stream << " \n"; + } } + stream << " \n"; } stream << "\n"; diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 9f5505474..259b4ac97 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -26,6 +26,7 @@ struct FontItem undefined = 0, file_path, // path is file loacation on computer - no move between computers wx_font_descr, // path is font descriptor generated by wxWidgets - limits for os/language move + loaded_from_3mf }; }; using FontList = std::vector; diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 1c3e05365..3d1704f8c 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -472,7 +472,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty if (mng.get_current_type() == GLGizmosManager::Emboss || mng.open_gizmo(GLGizmosManager::Emboss)) { GLGizmoEmboss *emboss = dynamic_cast(mng.get_current()); - emboss->set_volume_type(type); + if (emboss != nullptr) emboss->set_volume_type(type); } }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 868341f88..b034e8b2f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -60,7 +60,7 @@ public: // os specific load of wxFont static std::optional load_font(const wxFont &font); // Must be in gui because of wxWidget - static std::optional load_font(const FontItem &fi); + static std::optional load_font(FontItem &fi); static FontItem get_font_item(const wxFont &font); @@ -921,28 +921,26 @@ bool GLGizmoEmboss::load_configuration(ModelVolume *volume) { if (volume == nullptr) return false; if (!volume->text_configuration.has_value()) return false; - const TextConfiguration &configuration = *volume->text_configuration; - const FontItem & c_font_item = configuration.font_item; + TextConfiguration &configuration = *volume->text_configuration; + FontItem & c_font_item = configuration.font_item; if (!notify_unknown_font_type(volume)) { // try to find font in local font list - size_t index = m_font_list.size(); - for (const FontItem &font_item : m_font_list) { - if (font_item.type == c_font_item.type && - font_item.path == c_font_item.path) { - index = &font_item - &m_font_list.front(); - } - } + auto is_config = [&c_font_item](const FontItem &font_item)->bool { + return font_item.path == c_font_item.path; + }; + auto it = std::find_if(m_font_list.begin(), m_font_list.end(), is_config); + size_t prev_font_selected = m_font_selected; // when not in font list add to list - if (index >= m_font_list.size()) { + if (it == m_font_list.end()) { // add font to list m_font_selected = m_font_list.size(); m_font_list.emplace_back(c_font_item); } else { - m_font_selected = index; + m_font_selected = it - m_font_list.begin(); } - + // When can't load font if (!load_font()) { // remove bad loadabled font, for correct prev index @@ -1126,13 +1124,20 @@ bool GLGizmoEmboss::draw_button(IconType icon, bool disable) return false; } -std::optional WxFontUtils::load_font(const FontItem &fi) +std::optional WxFontUtils::load_font(FontItem &fi) { switch (fi.type) { case FontItem::Type::file_path: return Emboss::load_font(fi.path.c_str()); case FontItem::Type::wx_font_descr: return WxFontUtils::load_font(WxFontUtils::load_wxFont(fi.path)); + case FontItem::Type::loaded_from_3mf: { + wxFont font = WxFontUtils::load_wxFont(fi.path); + // fix name + fi.name = get_human_readable_name(font); + fi.type = FontItem::Type::wx_font_descr; + return WxFontUtils::load_font(font); + } case FontItem::Type::undefined: default: return {}; diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index ec2def966..ef58288ee 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -169,8 +169,6 @@ TEST_CASE("ray segment intersection", "[MeshBoolean]") CHECK(abs(*t1 - *t2) < std::numeric_limits::epsilon()); } - - TEST_CASE("triangle intersection", "[]") { Vec2d point(1, 1);