/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup stl */ #include #include #include "BKE_mesh.h" #include "BLI_fileops.hh" #include "BLI_memory_utils.hh" #include "BLI_string_ref.hh" #include "DNA_mesh_types.h" /* NOTE: we could use C++17 from_chars to parse * floats, but even if some compilers claim full support, * their standard libraries are not quite there yet. * LLVM/libc++ only has a float parser since LLVM 14, * and gcc/libstdc++ since 11.1. So until at least these are * the minimum spec, use an external library. */ #include "fast_float.h" #include "stl_import.hh" #include "stl_import_ascii_reader.hh" #include "stl_import_mesh.hh" namespace blender::io::stl { class StringBuffer { private: char *start; const char *end; public: StringBuffer(char *buf, size_t len) { start = buf; end = start + len; } bool is_empty() const { return start == end; } void drop_leading_control_chars() { while ((start < end) && (*start) <= ' ') { start++; } } void drop_leading_non_control_chars() { while ((start < end) && (*start) > ' ') { start++; } } void drop_line() { while (start < end && *start != '\n') { start++; } } bool parse_token(const char *token, size_t token_length) { drop_leading_control_chars(); if (end - start < token_length + 1) { return false; } if (memcmp(start, token, token_length) != 0) { return false; } if (start[token_length] > ' ') { return false; } start += token_length + 1; return true; } void drop_token() { drop_leading_non_control_chars(); drop_leading_control_chars(); } void parse_float(float &out) { drop_leading_control_chars(); /* Skip '+' */ if (start < end && *start == '+') { start++; } fast_float::from_chars_result res = fast_float::from_chars(start, end, out); if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { out = 0.0f; } start = const_cast(res.ptr); } }; static inline void parse_float3(StringBuffer &buf, float out[3]) { for (int i = 0; i < 3; i++) { buf.parse_float(out[i]); } } Mesh *read_stl_ascii(const char *filepath, Main *bmain, char *mesh_name, bool use_custom_normals) { size_t buffer_len; void *buffer = BLI_file_read_text_as_mem(filepath, 0, &buffer_len); if (buffer == nullptr) { fprintf(stderr, "STL Importer: cannot read from ASCII STL file: '%s'\n", filepath); return BKE_mesh_add(bmain, mesh_name); } BLI_SCOPED_DEFER([&]() { MEM_freeN(buffer); }); int num_reserved_tris = 1024; StringBuffer str_buf(static_cast(buffer), buffer_len); STLMeshHelper stl_mesh(num_reserved_tris, use_custom_normals); float triangle_buf[3][3]; float custom_normal_buf[3]; str_buf.drop_line(); /* Skip header line */ while (!str_buf.is_empty()) { if (str_buf.parse_token("vertex", 6)) { parse_float3(str_buf, triangle_buf[0]); if (str_buf.parse_token("vertex", 6)) { parse_float3(str_buf, triangle_buf[1]); } if (str_buf.parse_token("vertex", 6)) { parse_float3(str_buf, triangle_buf[2]); } if (use_custom_normals) { stl_mesh.add_triangle( triangle_buf[0], triangle_buf[1], triangle_buf[2], custom_normal_buf); } else { stl_mesh.add_triangle(triangle_buf[0], triangle_buf[1], triangle_buf[2]); } } else if (str_buf.parse_token("facet", 5)) { str_buf.drop_token(); /* Expecting "normal" */ parse_float3(str_buf, custom_normal_buf); } else { str_buf.drop_token(); } } return stl_mesh.to_mesh(bmain, mesh_name); } } // namespace blender::io::stl