Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIyad Ahmed <iyadahmed430@gmail.com>2022-06-06 20:57:38 +0300
committerAras Pranckevicius <aras@nesnausk.org>2022-06-06 20:57:38 +0300
commit7c511f1b47d857f37aa36ee6ed8107cb88eb5c39 (patch)
treed376a5e21bc21f40e0b062f9623887f4018ed61a /source/blender/io/stl
parent14fc89f38f0e3ce00e4fd6fffd72eea5d998af5a (diff)
STL: Add new C++ based STL importer
A new experimentatl STL importer, written in C++. Roughly 7-9x faster than the Python based one. Reviewed By: Aras Pranckevicius, Hans Goudey. Differential Revision: https://developer.blender.org/D14941
Diffstat (limited to 'source/blender/io/stl')
-rw-r--r--source/blender/io/stl/CMakeLists.txt44
-rw-r--r--source/blender/io/stl/IO_stl.cc16
-rw-r--r--source/blender/io/stl/IO_stl.h35
-rw-r--r--source/blender/io/stl/importer/stl_import.cc114
-rw-r--r--source/blender/io/stl/importer/stl_import.hh22
-rw-r--r--source/blender/io/stl/importer/stl_import_ascii_reader.cc159
-rw-r--r--source/blender/io/stl/importer/stl_import_ascii_reader.hh32
-rw-r--r--source/blender/io/stl/importer/stl_import_binary_reader.cc58
-rw-r--r--source/blender/io/stl/importer/stl_import_binary_reader.hh31
-rw-r--r--source/blender/io/stl/importer/stl_import_mesh.cc114
-rw-r--r--source/blender/io/stl/importer/stl_import_mesh.hh71
11 files changed, 696 insertions, 0 deletions
diff --git a/source/blender/io/stl/CMakeLists.txt b/source/blender/io/stl/CMakeLists.txt
new file mode 100644
index 00000000000..e0c48bbbf7e
--- /dev/null
+++ b/source/blender/io/stl/CMakeLists.txt
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(INC
+ .
+ ./importer
+ ../common
+ ../../blenkernel
+ ../../blenlib
+ ../../bmesh
+ ../../bmesh/intern
+ ../../depsgraph
+ ../../editors/include
+ ../../makesdna
+ ../../makesrna
+ ../../nodes
+ ../../windowmanager
+ ../../../../extern/fast_float
+ ../../../../intern/guardedalloc
+)
+
+set(INC_SYS
+
+)
+
+set(SRC
+ IO_stl.cc
+ importer/stl_import_mesh.cc
+ importer/stl_import_ascii_reader.cc
+ importer/stl_import_binary_reader.cc
+ importer/stl_import.cc
+
+ IO_stl.h
+ importer/stl_import_mesh.hh
+ importer/stl_import_ascii_reader.hh
+ importer/stl_import_binary_reader.hh
+ importer/stl_import.hh
+)
+
+set(LIB
+ bf_blenkernel
+ bf_io_common
+)
+
+blender_add_lib(bf_stl "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/io/stl/IO_stl.cc b/source/blender/io/stl/IO_stl.cc
new file mode 100644
index 00000000000..b26c1533692
--- /dev/null
+++ b/source/blender/io/stl/IO_stl.cc
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#include "BLI_timeit.hh"
+
+#include "IO_stl.h"
+#include "stl_import.hh"
+
+void STL_import(bContext *C, const struct STLImportParams *import_params)
+{
+ SCOPED_TIMER("STL Import");
+ blender::io::stl::importer_main(C, *import_params);
+}
diff --git a/source/blender/io/stl/IO_stl.h b/source/blender/io/stl/IO_stl.h
new file mode 100644
index 00000000000..bbe537948e8
--- /dev/null
+++ b/source/blender/io/stl/IO_stl.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#pragma once
+
+#include "BKE_context.h"
+#include "BLI_path_util.h"
+#include "IO_orientation.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct STLImportParams {
+ /** Full path to the source STL file to import. */
+ char filepath[FILE_MAX];
+ eIOAxis forward_axis;
+ eIOAxis up_axis;
+ bool use_facet_normal;
+ bool use_scene_unit;
+ float global_scale;
+ bool use_mesh_validate;
+};
+
+/**
+ * C-interface for the importer.
+ */
+void STL_import(bContext *C, const struct STLImportParams *import_params);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/io/stl/importer/stl_import.cc b/source/blender/io/stl/importer/stl_import.cc
new file mode 100644
index 00000000000..f358598a216
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import.cc
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#include <cstdio>
+
+#include "BKE_customdata.h"
+#include "BKE_layer.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+
+#include "DNA_collection_types.h"
+#include "DNA_scene_types.h"
+
+#include "BLI_fileops.hh"
+#include "BLI_math_vector.h"
+#include "BLI_memory_utils.hh"
+
+#include "DNA_object_types.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+#include "stl_import.hh"
+#include "stl_import_ascii_reader.hh"
+#include "stl_import_binary_reader.hh"
+
+namespace blender::io::stl {
+
+void importer_main(bContext *C, const STLImportParams &import_params)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ importer_main(bmain, scene, view_layer, import_params);
+}
+
+void importer_main(Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ const STLImportParams &import_params)
+{
+ FILE *file = BLI_fopen(import_params.filepath, "rb");
+ if (!file) {
+ fprintf(stderr, "Failed to open STL file:'%s'.\n", import_params.filepath);
+ return;
+ }
+ BLI_SCOPED_DEFER([&]() { fclose(file); });
+
+ /* Detect STL file type by comparing file size with expected file size,
+ * could check if file starts with "solid", but some files do not adhere,
+ * this is the same as the old Python importer.
+ */
+ uint32_t num_tri = 0;
+ size_t file_size = BLI_file_size(import_params.filepath);
+ fseek(file, BINARY_HEADER_SIZE, SEEK_SET);
+ fread(&num_tri, sizeof(uint32_t), 1, file);
+ bool is_ascii_stl = (file_size != (BINARY_HEADER_SIZE + 4 + BINARY_STRIDE * num_tri));
+
+ /* Name used for both mesh and object. */
+ char ob_name[FILE_MAX];
+ BLI_strncpy(ob_name, BLI_path_basename(import_params.filepath), FILE_MAX);
+ BLI_path_extension_replace(ob_name, FILE_MAX, "");
+
+ Mesh *mesh;
+ if (is_ascii_stl) {
+ mesh = read_stl_ascii(import_params.filepath, bmain, ob_name, import_params.use_facet_normal);
+ }
+ else {
+ mesh = read_stl_binary(file, bmain, ob_name, import_params.use_facet_normal);
+ }
+
+ if (import_params.use_mesh_validate) {
+ bool verbose_validate = false;
+#ifdef DEBUG
+ verbose_validate = true;
+#endif
+ BKE_mesh_validate(mesh, verbose_validate, false);
+ }
+
+ BKE_view_layer_base_deselect_all(view_layer);
+ LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
+ Object *obj = BKE_object_add_only_object(bmain, OB_MESH, ob_name);
+ BKE_mesh_assign_object(bmain, obj, mesh);
+ BKE_collection_object_add(bmain, lc->collection, obj);
+ Base *base = BKE_view_layer_base_find(view_layer, obj);
+ BKE_view_layer_base_select_and_set_active(view_layer, base);
+
+ float global_scale = import_params.global_scale;
+ if ((scene->unit.system != USER_UNIT_NONE) && import_params.use_scene_unit) {
+ global_scale *= scene->unit.scale_length;
+ }
+ float scale_vec[3] = {global_scale, global_scale, global_scale};
+ float obmat3x3[3][3];
+ unit_m3(obmat3x3);
+ float obmat4x4[4][4];
+ unit_m4(obmat4x4);
+ /* +Y-forward and +Z-up are the Blender's default axis settings. */
+ mat3_from_axis_conversion(
+ IO_AXIS_Y, IO_AXIS_Z, import_params.forward_axis, import_params.up_axis, obmat3x3);
+ copy_m4_m3(obmat4x4, obmat3x3);
+ rescale_m4(obmat4x4, scale_vec);
+ BKE_object_apply_mat4(obj, obmat4x4, true, false);
+
+ DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE);
+ int flags = ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
+ ID_RECALC_BASE_FLAGS;
+ DEG_id_tag_update_ex(bmain, &obj->id, flags);
+ DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS);
+ DEG_relations_tag_update(bmain);
+}
+} // namespace blender::io::stl
diff --git a/source/blender/io/stl/importer/stl_import.hh b/source/blender/io/stl/importer/stl_import.hh
new file mode 100644
index 00000000000..377544c26af
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import.hh
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#pragma once
+
+#include "IO_stl.h"
+
+namespace blender::io::stl {
+
+/* Main import function used from within Blender. */
+void importer_main(bContext *C, const STLImportParams &import_params);
+
+/* Used from tests, where full bContext does not exist. */
+void importer_main(Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ const STLImportParams &import_params);
+
+} // namespace blender::io::stl
diff --git a/source/blender/io/stl/importer/stl_import_ascii_reader.cc b/source/blender/io/stl/importer/stl_import_ascii_reader.cc
new file mode 100644
index 00000000000..2f2495fa4ca
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import_ascii_reader.cc
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#include <cstdint>
+#include <cstdio>
+
+#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 <charconv> 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_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<char *>(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<char *>(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
diff --git a/source/blender/io/stl/importer/stl_import_ascii_reader.hh b/source/blender/io/stl/importer/stl_import_ascii_reader.hh
new file mode 100644
index 00000000000..b0216d98496
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import_ascii_reader.hh
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#pragma once
+
+#include <cstdio>
+
+#include "BKE_mesh.h"
+
+#include "stl_import.hh"
+
+/* ASCII STL spec.:
+ * solid name
+ * facet normal ni nj nk
+ * outer loop
+ * vertex v1x v1y v1z
+ * vertex v2x v2y v2z
+ * vertex v3x v3y v3z
+ * endloop
+ * endfacet
+ * ...
+ * endsolid name
+ */
+
+namespace blender::io::stl {
+
+Mesh *read_stl_ascii(const char *filepath, Main *bmain, char *mesh_name, bool use_custom_normals);
+
+} // namespace blender::io::stl
diff --git a/source/blender/io/stl/importer/stl_import_binary_reader.cc b/source/blender/io/stl/importer/stl_import_binary_reader.cc
new file mode 100644
index 00000000000..c05b6ad1426
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import_binary_reader.cc
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#include <cstdint>
+#include <cstdio>
+
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+
+#include "BLI_array.hh"
+#include "BLI_memory_utils.hh"
+
+#include "DNA_mesh_types.h"
+
+#include "stl_import_binary_reader.hh"
+#include "stl_import_mesh.hh"
+
+namespace blender::io::stl {
+
+#pragma pack(push, 1)
+struct STLBinaryTriangle {
+ float normal[3];
+ float v1[3], v2[3], v3[3];
+ uint16_t attribute_byte_count;
+};
+#pragma pack(pop)
+
+Mesh *read_stl_binary(FILE *file, Main *bmain, char *mesh_name, bool use_custom_normals)
+{
+ const int chunk_size = 1024;
+ uint32_t num_tris = 0;
+ fseek(file, BINARY_HEADER_SIZE, SEEK_SET);
+ fread(&num_tris, sizeof(uint32_t), 1, file);
+ if (num_tris == 0) {
+ return BKE_mesh_add(bmain, mesh_name);
+ }
+
+ Array<STLBinaryTriangle> tris_buf(chunk_size);
+ STLMeshHelper stl_mesh(num_tris, use_custom_normals);
+ size_t num_read_tris;
+ while (num_read_tris = fread(tris_buf.data(), sizeof(STLBinaryTriangle), chunk_size, file)) {
+ for (size_t i = 0; i < num_read_tris; i++) {
+ if (use_custom_normals) {
+ stl_mesh.add_triangle(tris_buf[i].v1, tris_buf[i].v2, tris_buf[i].v3, tris_buf[i].normal);
+ }
+ else {
+ stl_mesh.add_triangle(tris_buf[i].v1, tris_buf[i].v2, tris_buf[i].v3);
+ }
+ }
+ }
+
+ return stl_mesh.to_mesh(bmain, mesh_name);
+}
+
+} // namespace blender::io::stl
diff --git a/source/blender/io/stl/importer/stl_import_binary_reader.hh b/source/blender/io/stl/importer/stl_import_binary_reader.hh
new file mode 100644
index 00000000000..71d5dbbbe58
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import_binary_reader.hh
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#pragma once
+
+#include <cstdio>
+
+#include "BKE_mesh.h"
+
+/* Binary STL spec.:
+ * UINT8[80] – Header - 80 bytes
+ * UINT32 – Number of triangles - 4 bytes
+ * For each triangle - 50 bytes:
+ * REAL32[3] – Normal vector - 12 bytes
+ * REAL32[3] – Vertex 1 - 12 bytes
+ * REAL32[3] – Vertex 2 - 12 bytes
+ * REAL32[3] – Vertex 3 - 12 bytes
+ * UINT16 – Attribute byte count - 2 bytes
+ */
+
+namespace blender::io::stl {
+
+const size_t BINARY_HEADER_SIZE = 80;
+const size_t BINARY_STRIDE = 12 * 4 + 2;
+
+Mesh *read_stl_binary(FILE *file, Main *bmain, char *mesh_name, bool use_custom_normals);
+
+} // namespace blender::io::stl
diff --git a/source/blender/io/stl/importer/stl_import_mesh.cc b/source/blender/io/stl/importer/stl_import_mesh.cc
new file mode 100644
index 00000000000..5f5c2624414
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import_mesh.cc
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#include "BKE_customdata.h"
+#include "BKE_lib_id.h"
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+
+#include "BLI_array.hh"
+#include "BLI_math_vector.h"
+#include "BLI_math_vector.hh"
+#include "BLI_task.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "stl_import_mesh.hh"
+
+namespace blender::io::stl {
+
+STLMeshHelper::STLMeshHelper(int num_tris, bool use_custom_normals)
+ : m_use_custom_normals(use_custom_normals)
+{
+ m_num_degenerate_tris = 0;
+ m_num_duplicate_tris = 0;
+ m_tris.reserve(num_tris);
+ /* Upper bound (all vertices are unique). */
+ m_verts.reserve(num_tris * 3);
+ if (use_custom_normals) {
+ m_loop_normals.reserve(num_tris * 3);
+ }
+}
+
+bool STLMeshHelper::add_triangle(const float3 &a, const float3 &b, const float3 &c)
+{
+ int v1_id = m_verts.index_of_or_add(a);
+ int v2_id = m_verts.index_of_or_add(b);
+ int v3_id = m_verts.index_of_or_add(c);
+ if ((v1_id == v2_id) || (v1_id == v3_id) || (v2_id == v3_id)) {
+ m_num_degenerate_tris++;
+ return false;
+ }
+ if (!m_tris.add({v1_id, v2_id, v3_id})) {
+ m_num_duplicate_tris++;
+ return false;
+ }
+ return true;
+}
+
+void STLMeshHelper::add_triangle(const float3 &a,
+ const float3 &b,
+ const float3 &c,
+ const float3 &custom_normal)
+{
+ if (add_triangle(a, b, c)) {
+ m_loop_normals.append_n_times(custom_normal, 3);
+ }
+}
+
+Mesh *STLMeshHelper::to_mesh(Main *bmain, char *mesh_name)
+{
+ if (m_num_degenerate_tris > 0) {
+ std::cout << "STL Importer: " << m_num_degenerate_tris << "degenerate triangles were removed"
+ << std::endl;
+ }
+ if (m_num_duplicate_tris > 0) {
+ std::cout << "STL Importer: " << m_num_duplicate_tris << "duplicate triangles were removed"
+ << std::endl;
+ }
+
+ Mesh *mesh = BKE_mesh_add(bmain, mesh_name);
+ /* User count is already 1 here, but will be set later in #BKE_mesh_assign_object. */
+ id_us_min(&mesh->id);
+
+ mesh->totvert = m_verts.size();
+ mesh->mvert = static_cast<MVert *>(
+ CustomData_add_layer(&mesh->vdata, CD_MVERT, CD_CALLOC, nullptr, mesh->totvert));
+ for (int i = 0; i < mesh->totvert; i++) {
+ copy_v3_v3(mesh->mvert[i].co, m_verts[i]);
+ }
+
+ mesh->totpoly = m_tris.size();
+ mesh->totloop = m_tris.size() * 3;
+ mesh->mpoly = static_cast<MPoly *>(
+ CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_CALLOC, nullptr, mesh->totpoly));
+ mesh->mloop = static_cast<MLoop *>(
+ CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_CALLOC, nullptr, mesh->totloop));
+
+ threading::parallel_for(m_tris.index_range(), 2048, [&](IndexRange tris_range) {
+ for (const int i : tris_range) {
+ mesh->mpoly[i].loopstart = 3 * i;
+ mesh->mpoly[i].totloop = 3;
+
+ mesh->mloop[3 * i].v = m_tris[i].v1;
+ mesh->mloop[3 * i + 1].v = m_tris[i].v2;
+ mesh->mloop[3 * i + 2].v = m_tris[i].v3;
+ }
+ });
+
+ /* NOTE: edges must be calculated first before setting custom normals. */
+ BKE_mesh_calc_edges(mesh, false, false);
+
+ if (m_use_custom_normals && m_loop_normals.size() == mesh->totloop) {
+ BKE_mesh_set_custom_normals(mesh, reinterpret_cast<float(*)[3]>(m_loop_normals.data()));
+ mesh->flag |= ME_AUTOSMOOTH;
+ }
+
+ return mesh;
+}
+
+} // namespace blender::io::stl
diff --git a/source/blender/io/stl/importer/stl_import_mesh.hh b/source/blender/io/stl/importer/stl_import_mesh.hh
new file mode 100644
index 00000000000..7827d2a408c
--- /dev/null
+++ b/source/blender/io/stl/importer/stl_import_mesh.hh
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup stl
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include "BLI_math_vec_types.hh"
+#include "BLI_set.hh"
+#include "BLI_vector.hh"
+#include "BLI_vector_set.hh"
+
+#include "DNA_mesh_types.h"
+
+namespace blender::io::stl {
+class Triangle {
+ public:
+ int v1, v2, v3;
+ /* Based on an old version of Python's frozenset hash
+ * https://web.archive.org/web/20220520211017/https://stackoverflow.com/questions/20832279/python-frozenset-hashing-algorithm-implementation
+ */
+ uint64_t hash() const
+ {
+ uint64_t res = 1927868237UL;
+ res *= 4;
+ res ^= (v1 ^ (v1 << 16) ^ 89869747UL) * 3644798167UL;
+ res ^= (v2 ^ (v2 << 16) ^ 89869747UL) * 3644798167UL;
+ res ^= (v3 ^ (v3 << 16) ^ 89869747UL) * 3644798167UL;
+ return res * 69069U + 907133923UL;
+ }
+ friend bool operator==(const Triangle &a, const Triangle &b)
+ {
+ bool i = (a.v1 == b.v1) && (a.v2 == b.v2) && (a.v3 == b.v3);
+ bool j = (a.v1 == b.v1) && (a.v3 == b.v2) && (a.v2 == b.v3);
+ bool k = (a.v2 == b.v1) && (a.v1 == b.v2) && (a.v3 == b.v3);
+
+ bool l = (a.v2 == b.v1) && (a.v3 == b.v2) && (a.v1 == b.v3);
+ bool m = (a.v3 == b.v1) && (a.v1 == b.v2) && (a.v2 == b.v3);
+ bool n = (a.v3 == b.v1) && (a.v2 == b.v2) && (a.v1 == b.v3);
+
+ return i || j || k || l || m || n;
+ }
+};
+
+class STLMeshHelper {
+ private:
+ VectorSet<float3> m_verts;
+ VectorSet<Triangle> m_tris;
+ Vector<float3> m_loop_normals;
+ int m_num_degenerate_tris;
+ int m_num_duplicate_tris;
+ const bool m_use_custom_normals;
+
+ public:
+ STLMeshHelper(int num_tris, bool use_custom_normals);
+
+ /* Creates a new triangle from specified vertex locations,
+ * duplicate vertices and triangles are merged.
+ */
+ bool add_triangle(const float3 &a, const float3 &b, const float3 &c);
+ void add_triangle(const float3 &a,
+ const float3 &b,
+ const float3 &c,
+ const float3 &custom_normal);
+ Mesh *to_mesh(Main *bmain, char *mesh_name);
+};
+
+} // namespace blender::io::stl