mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'master' into lm_tm_hollowing
This commit is contained in:
		
						commit
						b3f15b1c98
					
				
					 68 changed files with 1399 additions and 917 deletions
				
			
		|  | @ -34,8 +34,12 @@ namespace pt = boost::property_tree; | |||
| // VERSION NUMBERS
 | ||||
| // 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
 | ||||
| // 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
 | ||||
| // 2 : Meshes saved in their local system; Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file.
 | ||||
| const unsigned int VERSION_3MF = 2; | ||||
| // 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading.
 | ||||
| // WARNING !! -> the version number has been rolled back to 1
 | ||||
| //               the next change should use 3
 | ||||
| const unsigned int VERSION_3MF = 1; | ||||
| // Allow loading version 2 file as well.
 | ||||
| const unsigned int VERSION_3MF_COMPATIBLE = 2; | ||||
| const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
 | ||||
| 
 | ||||
| const std::string MODEL_FOLDER = "3D/"; | ||||
|  | @ -52,7 +56,7 @@ const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights | |||
| const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; | ||||
| const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; | ||||
| const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; | ||||
| const std::string CUSTOM_GCODE_PER_HEIGHT_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_height.xml"; | ||||
| const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; | ||||
| 
 | ||||
| const char* MODEL_TAG = "model"; | ||||
| const char* RESOURCES_TAG = "resources"; | ||||
|  | @ -422,7 +426,7 @@ namespace Slic3r { | |||
|         void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
|         void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
| 
 | ||||
|         void _extract_custom_gcode_per_height_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
|         void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
| 
 | ||||
|         void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); | ||||
|         bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); | ||||
|  | @ -638,10 +642,10 @@ namespace Slic3r { | |||
|                     // extract slic3r print config file
 | ||||
|                     _extract_print_config_from_archive(archive, stat, config, filename); | ||||
|                 } | ||||
|                 if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_HEIGHT_FILE)) | ||||
|                 if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) | ||||
|                 { | ||||
|                     // extract slic3r layer config ranges file
 | ||||
|                     _extract_custom_gcode_per_height_from_archive(archive, stat); | ||||
|                     _extract_custom_gcode_per_print_z_from_archive(archive, stat); | ||||
|                 } | ||||
|                 else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) | ||||
|                 { | ||||
|  | @ -1163,7 +1167,7 @@ namespace Slic3r { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     void _3MF_Importer::_extract_custom_gcode_per_height_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat) | ||||
|     void _3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat) | ||||
|     { | ||||
|         if (stat.m_uncomp_size > 0) | ||||
|         { | ||||
|  | @ -1178,24 +1182,23 @@ namespace Slic3r { | |||
|             pt::ptree main_tree; | ||||
|             pt::read_xml(iss, main_tree); | ||||
| 
 | ||||
|             if (main_tree.front().first != "custom_gcodes_per_height") | ||||
|             if (main_tree.front().first != "custom_gcodes_per_print_z") | ||||
|                 return; | ||||
|             pt::ptree code_tree = main_tree.front().second; | ||||
| 
 | ||||
|             if (!m_model->custom_gcode_per_height.empty()) | ||||
|                 m_model->custom_gcode_per_height.clear(); | ||||
|             m_model->custom_gcode_per_print_z.clear(); | ||||
| 
 | ||||
|             for (const auto& code : code_tree) | ||||
|             { | ||||
|                 if (code.first != "code") | ||||
|                     continue; | ||||
|                 pt::ptree tree = code.second; | ||||
|                 double height       = tree.get<double>("<xmlattr>.height"); | ||||
|                 std::string gcode   = tree.get<std::string>("<xmlattr>.gcode"); | ||||
|                 int extruder        = tree.get<int>("<xmlattr>.extruder"); | ||||
|                 std::string color   = tree.get<std::string>("<xmlattr>.color"); | ||||
|                 double print_z      = tree.get<double>      ("<xmlattr>.print_z"    ); | ||||
|                 std::string gcode   = tree.get<std::string> ("<xmlattr>.gcode"      ); | ||||
|                 int extruder        = tree.get<int>         ("<xmlattr>.extruder"   ); | ||||
|                 std::string color   = tree.get<std::string> ("<xmlattr>.color"      ); | ||||
| 
 | ||||
|                 m_model->custom_gcode_per_height.push_back(Model::CustomGCode(height, gcode, extruder, color)) ; | ||||
|                 m_model->custom_gcode_per_print_z.push_back(Model::CustomGCode{print_z, gcode, extruder, color}) ; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1611,7 +1614,7 @@ namespace Slic3r { | |||
|         { | ||||
|             m_version = (unsigned int)atoi(m_curr_characters.c_str()); | ||||
| 
 | ||||
|             if (m_check_version && (m_version > VERSION_3MF)) | ||||
|             if (m_check_version && (m_version > VERSION_3MF_COMPATIBLE)) | ||||
|             { | ||||
|                 // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
 | ||||
|                 // throw version_error(msg.c_str());
 | ||||
|  | @ -1797,20 +1800,19 @@ namespace Slic3r { | |||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             Slic3r::Geometry::Transformation transform; | ||||
|             if (m_version > 1) | ||||
|             Transform3d volume_matrix_to_object = Transform3d::Identity(); | ||||
|             bool        has_transform 		    = false; | ||||
|             // extract the volume transformation from the volume's metadata, if present
 | ||||
|             for (const Metadata& metadata : volume_data.metadata) | ||||
|             { | ||||
|                 // extract the volume transformation from the volume's metadata, if present
 | ||||
|                 for (const Metadata& metadata : volume_data.metadata) | ||||
|                 if (metadata.key == MATRIX_KEY) | ||||
|                 { | ||||
|                     if (metadata.key == MATRIX_KEY) | ||||
|                     { | ||||
|                         transform.set_from_string(metadata.value); | ||||
|                         break; | ||||
|                     } | ||||
|                     volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); | ||||
|                     has_transform 			= ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             Transform3d inv_matrix = transform.get_matrix().inverse(); | ||||
|             Transform3d inv_matrix = volume_matrix_to_object.inverse(); | ||||
| 
 | ||||
|             // splits volume out of imported geometry
 | ||||
| 			TriangleMesh triangle_mesh; | ||||
|  | @ -1831,10 +1833,10 @@ namespace Slic3r { | |||
|                 { | ||||
|                     unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3; | ||||
|                     Vec3f vertex(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]); | ||||
|                     if (m_version > 1) | ||||
|                     facet.vertex[v] = has_transform ? | ||||
|                         // revert the vertices to the original mesh reference system
 | ||||
|                         vertex = (inv_matrix * vertex.cast<double>()).cast<float>(); | ||||
|                     ::memcpy(facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float)); | ||||
|                         (inv_matrix * vertex.cast<double>()).cast<float>() : | ||||
|                         vertex; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -1843,8 +1845,8 @@ namespace Slic3r { | |||
| 
 | ||||
| 			ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); | ||||
|             // apply the volume matrix taken from the metadata, if present
 | ||||
|             if (m_version > 1) | ||||
|                 volume->set_transformation(transform); | ||||
|             if (has_transform) | ||||
|                 volume->set_transformation(Slic3r::Geometry::Transformation(volume_matrix_to_object)); | ||||
|             volume->calculate_convex_hull(); | ||||
| 
 | ||||
|             // apply the remaining volume's metadata
 | ||||
|  | @ -1985,7 +1987,7 @@ namespace Slic3r { | |||
|         bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|         bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); | ||||
|         bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); | ||||
|         bool _add_custom_gcode_per_height_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|         bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|     }; | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
|  | @ -2096,9 +2098,9 @@ namespace Slic3r { | |||
|         } | ||||
|          | ||||
| 
 | ||||
|         // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_height.xml").
 | ||||
|         // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
 | ||||
|         // All custom gcode per height of whole Model are stored here
 | ||||
|         if (!_add_custom_gcode_per_height_file_to_archive(archive, model)) | ||||
|         if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model)) | ||||
|         { | ||||
|             close_zip_writer(&archive); | ||||
|             boost::filesystem::remove(filename); | ||||
|  | @ -2622,6 +2624,9 @@ namespace Slic3r { | |||
|     bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data) | ||||
|     { | ||||
|         std::stringstream stream; | ||||
|         // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
 | ||||
|         // when loaded as accurately as possible.
 | ||||
| 		stream << std::setprecision(std::numeric_limits<double>::max_digits10); | ||||
|         stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; | ||||
|         stream << "<" << CONFIG_TAG << ">\n"; | ||||
| 
 | ||||
|  | @ -2719,20 +2724,20 @@ namespace Slic3r { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| bool _3MF_Exporter::_add_custom_gcode_per_height_file_to_archive( mz_zip_archive& archive, Model& model) | ||||
| bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model) | ||||
| { | ||||
|     std::string out = ""; | ||||
| 
 | ||||
|     if (!model.custom_gcode_per_height.empty()) | ||||
|     if (!model.custom_gcode_per_print_z.empty()) | ||||
|     { | ||||
|         pt::ptree tree; | ||||
|         pt::ptree& main_tree = tree.add("custom_gcodes_per_height", ""); | ||||
|         pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); | ||||
| 
 | ||||
|         for (const Model::CustomGCode& code : model.custom_gcode_per_height) | ||||
|         for (const Model::CustomGCode& code : model.custom_gcode_per_print_z) | ||||
|         { | ||||
|             pt::ptree& code_tree = main_tree.add("code", ""); | ||||
|             // store minX and maxZ
 | ||||
|             code_tree.put("<xmlattr>.height"    , code.height   ); | ||||
|             code_tree.put("<xmlattr>.print_z"   , code.print_z  ); | ||||
|             code_tree.put("<xmlattr>.gcode"     , code.gcode    ); | ||||
|             code_tree.put("<xmlattr>.extruder"  , code.extruder ); | ||||
|             code_tree.put("<xmlattr>.color"     , code.color    ); | ||||
|  | @ -2751,9 +2756,9 @@ bool _3MF_Exporter::_add_custom_gcode_per_height_file_to_archive( mz_zip_archive | |||
| 
 | ||||
|     if (!out.empty()) | ||||
|     { | ||||
|         if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_HEIGHT_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) | ||||
|         if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) | ||||
|         { | ||||
|             add_error("Unable to add custom Gcodes per height file to archive"); | ||||
|             add_error("Unable to add custom Gcodes per print_z file to archive"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -41,8 +41,11 @@ namespace pt = boost::property_tree; | |||
| //     Added x and y components of rotation
 | ||||
| //     Added x, y and z components of scale
 | ||||
| //     Added x, y and z components of mirror
 | ||||
| // 3 : Meshes saved in their local system; Added volumes' matrices and source data
 | ||||
| const unsigned int VERSION_AMF = 3; | ||||
| // 3 : Added volumes' matrices and source data, meshes transformed back to their coordinate system on loading.
 | ||||
| // WARNING !! -> the version number has been rolled back to 2
 | ||||
| //               the next change should use 4
 | ||||
| const unsigned int VERSION_AMF = 2; | ||||
| const unsigned int VERSION_AMF_COMPATIBLE = 3; | ||||
| const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; | ||||
| 
 | ||||
| const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; | ||||
|  | @ -228,6 +231,8 @@ struct AMFParserContext | |||
|     ModelVolume             *m_volume; | ||||
|     // Faces collected for the current m_volume.
 | ||||
|     std::vector<int>         m_volume_facets; | ||||
|     // Transformation matrix of a volume mesh from its coordinate system to Object's coordinate system.
 | ||||
|     Transform3d 			 m_volume_transform; | ||||
|     // Current material allocated for an amf/metadata subtree.
 | ||||
|     ModelMaterial           *m_material; | ||||
|     // Current instance allocated for an amf/constellation/instance subtree.
 | ||||
|  | @ -319,6 +324,7 @@ void AMFParserContext::startElement(const char *name, const char **atts) | |||
| 			else if (strcmp(name, "volume") == 0) { | ||||
| 				assert(! m_volume); | ||||
|                 m_volume = m_object->add_volume(TriangleMesh()); | ||||
|                 m_volume_transform = Transform3d::Identity(); | ||||
|                 node_type_new = NODE_TYPE_VOLUME; | ||||
| 			} | ||||
|         } else if (m_path[2] == NODE_TYPE_INSTANCE) { | ||||
|  | @ -578,27 +584,25 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|         stl.stats.original_num_facets = stl.stats.number_of_facets; | ||||
|         stl_allocate(&stl); | ||||
| 
 | ||||
|         Slic3r::Geometry::Transformation transform; | ||||
|         if (m_version > 2) | ||||
|             transform = m_volume->get_transformation(); | ||||
| 
 | ||||
|         Transform3d inv_matrix = transform.get_matrix().inverse(); | ||||
| 
 | ||||
|         bool has_transform = ! m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); | ||||
|         Transform3d inv_matrix = m_volume_transform.inverse(); | ||||
|         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) | ||||
|             { | ||||
|                 unsigned int tri_id = m_volume_facets[i++] * 3; | ||||
|                 Vec3f vertex(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]); | ||||
|                 if (m_version > 2) | ||||
|                 facet.vertex[v] = has_transform ? | ||||
|                     // revert the vertices to the original mesh reference system
 | ||||
|                     vertex = (inv_matrix * vertex.cast<double>()).cast<float>(); | ||||
|                 ::memcpy((void*)facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float)); | ||||
|                     (inv_matrix * vertex.cast<double>()).cast<float>() : | ||||
|                     vertex; | ||||
|             } | ||||
|         } | ||||
|         }         | ||||
|         stl_get_size(&stl); | ||||
|         mesh.repair(); | ||||
| 		m_volume->set_mesh(std::move(mesh)); | ||||
| 		if (has_transform) | ||||
| 			m_volume->set_transformation(m_volume_transform); | ||||
|         if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) | ||||
|         { | ||||
|             m_volume->source.object_idx = (int)m_model.objects.size() - 1; | ||||
|  | @ -637,7 +641,7 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|         int extruder = atoi(m_value[2].c_str()); | ||||
|         const std::string& color = m_value[3]; | ||||
| 
 | ||||
|         m_model.custom_gcode_per_height.push_back(Model::CustomGCode(height, gcode, extruder, color)); | ||||
|         m_model.custom_gcode_per_print_z.push_back(Model::CustomGCode{height, gcode, extruder, color}); | ||||
| 
 | ||||
|         for (std::string& val: m_value) | ||||
|             val.clear(); | ||||
|  | @ -718,9 +722,7 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|                     m_volume->set_type(ModelVolume::type_from_string(m_value[1])); | ||||
|                 } | ||||
|                 else if (strcmp(opt_key, "matrix") == 0) { | ||||
|                     Geometry::Transformation transform; | ||||
|                     transform.set_from_string(m_value[1]); | ||||
|                     m_volume->set_transformation(transform); | ||||
|                     m_volume_transform = Slic3r::Geometry::transform3d_from_string(m_value[1]); | ||||
|                 } | ||||
|                 else if (strcmp(opt_key, "source_file") == 0) { | ||||
|                     m_volume->source.input_file = m_value[1]; | ||||
|  | @ -910,7 +912,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi | |||
| 
 | ||||
|     ctx.endDocument(); | ||||
| 
 | ||||
|     if (check_version && (ctx.m_version > VERSION_AMF)) | ||||
|     if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE)) | ||||
|     { | ||||
|         // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
 | ||||
|         // throw std::runtime_error(msg.c_str());
 | ||||
|  | @ -1146,6 +1148,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|             stream << "        <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n"; | ||||
|             stream << "        <metadata type=\"slic3r.matrix\">"; | ||||
|             const Transform3d& matrix = volume->get_matrix(); | ||||
| 			stream << std::setprecision(std::numeric_limits<double>::max_digits10); | ||||
|             for (int r = 0; r < 4; ++r) | ||||
|             { | ||||
|                 for (int c = 0; c < 4; ++c) | ||||
|  | @ -1165,6 +1168,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|                 stream << "        <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n"; | ||||
|                 stream << "        <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n"; | ||||
|             } | ||||
| 			stream << std::setprecision(std::numeric_limits<float>::max_digits10); | ||||
|             const indexed_triangle_set &its = volume->mesh().its; | ||||
|             for (size_t i = 0; i < its.indices.size(); ++i) { | ||||
|                 stream << "        <triangle>\n"; | ||||
|  | @ -1221,21 +1225,21 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|         stream << "  </constellation>\n"; | ||||
|     } | ||||
| 
 | ||||
|     if (!model->custom_gcode_per_height.empty()) | ||||
|     if (!model->custom_gcode_per_print_z.empty()) | ||||
|     { | ||||
|         std::string out = ""; | ||||
|         pt::ptree tree; | ||||
| 
 | ||||
|         pt::ptree& main_tree = tree.add("custom_gcodes_per_height", ""); | ||||
| 
 | ||||
|         for (const Model::CustomGCode& code : model->custom_gcode_per_height) | ||||
|         for (const Model::CustomGCode& code : model->custom_gcode_per_print_z) | ||||
|         { | ||||
|             pt::ptree& code_tree = main_tree.add("code", ""); | ||||
|             // store minX and maxZ
 | ||||
|             code_tree.put("<xmlattr>.height", code.height); | ||||
|             code_tree.put("<xmlattr>.gcode", code.gcode); | ||||
|             code_tree.put("<xmlattr>.extruder", code.extruder); | ||||
|             code_tree.put("<xmlattr>.color", code.color); | ||||
|             code_tree.put("<xmlattr>.print_z"   , code.print_z  ); | ||||
|             code_tree.put("<xmlattr>.gcode"     , code.gcode    ); | ||||
|             code_tree.put("<xmlattr>.extruder"  , code.extruder ); | ||||
|             code_tree.put("<xmlattr>.color"     , code.color    ); | ||||
|         } | ||||
| 
 | ||||
|         if (!tree.empty()) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Matena
						Lukas Matena