From d1455c41389302eadeeae1d291b482c1fa499a9f Mon Sep 17 00:00:00 2001 From: Devashish Lal Date: Mon, 10 Jun 2024 20:47:37 +0200 Subject: [PATCH] Geometry Nodes: Add STL Import Node This commit adds an initial STL import node, the first of the nodes from the current Google Summer of Code Project [0]. The importer is refactored to output a mesh pointer, and a node is added to wrap around the importer. The node supports error messages from the importer. A new experimental option is added to hide the nodes by default until they're ready to be exposed generally. 0: https://devtalk.blender.org/t/gsoc-2024-geometry-nodes-file-import-nodes/34482) Pull Request: https://projects.blender.org/blender/blender/pulls/122418 --- .../startup/bl_ui/node_add_menu_geometry.py | 13 ++- scripts/startup/bl_ui/space_userpref.py | 1 + source/blender/blenkernel/BKE_node.hh | 1 + source/blender/io/stl/IO_stl.cc | 5 ++ source/blender/io/stl/IO_stl.hh | 3 + source/blender/io/stl/importer/stl_import.cc | 47 +++++----- source/blender/io/stl/importer/stl_import.hh | 5 +- source/blender/makesdna/DNA_userdef_types.h | 3 +- source/blender/makesrna/intern/rna_userdef.cc | 4 + source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/geometry/CMakeLists.txt | 5 ++ .../nodes/geometry/node_geometry_util.cc | 7 ++ .../nodes/geometry/node_geometry_util.hh | 1 + .../geometry/nodes/node_geo_import_stl.cc | 90 +++++++++++++++++++ 14 files changed, 163 insertions(+), 23 deletions(-) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_import_stl.cc diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index b8c8a42c72d..975e98231e6 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -243,11 +243,13 @@ class NODE_MT_geometry_node_GEO_INPUT(Menu): bl_idname = "NODE_MT_geometry_node_GEO_INPUT" bl_label = "Input" - def draw(self, _context): + def draw(self, context): layout = self.layout layout.menu("NODE_MT_geometry_node_GEO_INPUT_CONSTANT") layout.menu("NODE_MT_geometry_node_GEO_INPUT_GROUP") layout.menu("NODE_MT_geometry_node_GEO_INPUT_SCENE") + if context.preferences.experimental.use_new_file_import_nodes: + layout.menu("NODE_MT_category_IMPORT") node_add_menu.draw_assets_for_catalog(layout, self.bl_label) @@ -448,6 +450,14 @@ class NODE_MT_category_PRIMITIVES_MESH(Menu): node_add_menu.add_node_type(layout, "GeometryNodeMeshUVSphere") node_add_menu.draw_assets_for_catalog(layout, "Mesh/Primitives") +class NODE_MT_category_IMPORT(Menu): + bl_idname = "NODE_MT_category_IMPORT" + bl_label = "Import" + + def draw(self, _context): + layout = self.layout + node_add_menu.add_node_type(layout, "GeometryNodeImportSTL") + node_add_menu.draw_assets_for_catalog(layout, "Input/Import") class NODE_MT_geometry_node_mesh_topology(Menu): bl_idname = "NODE_MT_geometry_node_mesh_topology" @@ -816,6 +826,7 @@ classes = ( NODE_MT_geometry_node_GEO_MESH_OPERATIONS, NODE_MT_category_GEO_UV, NODE_MT_category_PRIMITIVES_MESH, + NODE_MT_category_IMPORT, NODE_MT_geometry_node_mesh_topology, NODE_MT_category_GEO_POINT, NODE_MT_category_simulation, diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 2e9236dcb0d..7dccea1248f 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -2798,6 +2798,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_extended_asset_browser"}, ("blender/blender/projects/10", "Pipeline, Assets & IO Project Page")), ({"property": "use_new_volume_nodes"}, ("blender/blender/issues/103248", "#103248")), + ({"property": "use_new_file_import_nodes"}, ("blender/blender/issues/122846", "#122846")), ({"property": "use_shader_node_previews"}, ("blender/blender/issues/110353", "#110353")), ), ) diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index e3f6f8cec26..050f387536f 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -1333,6 +1333,7 @@ void BKE_nodetree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index #define GEO_NODE_TOOL_ACTIVE_ELEMENT 2135 #define GEO_NODE_SET_INSTANCE_TRANSFORM 2136 #define GEO_NODE_INPUT_INSTANCE_TRANSFORM 2137 +#define GEO_NODE_IMPORT_STL 2138 /** \} */ diff --git a/source/blender/io/stl/IO_stl.cc b/source/blender/io/stl/IO_stl.cc index 6fdecd4ff12..f34262b493f 100644 --- a/source/blender/io/stl/IO_stl.cc +++ b/source/blender/io/stl/IO_stl.cc @@ -23,3 +23,8 @@ void STL_export(bContext *C, const STLExportParams *export_params) SCOPED_TIMER("STL Export"); blender::io::stl::exporter_main(C, *export_params); } + +Mesh *STL_import_mesh(const STLImportParams *import_params) +{ + return blender::io::stl::read_stl_file(*import_params); +} diff --git a/source/blender/io/stl/IO_stl.hh b/source/blender/io/stl/IO_stl.hh index 1e3bc6e6237..55e73966345 100644 --- a/source/blender/io/stl/IO_stl.hh +++ b/source/blender/io/stl/IO_stl.hh @@ -14,6 +14,7 @@ #include "IO_orientation.hh" +struct Mesh; struct bContext; struct ReportList; @@ -48,3 +49,5 @@ struct STLExportParams { void STL_import(bContext *C, const STLImportParams *import_params); void STL_export(bContext *C, const STLExportParams *export_params); + +Mesh *STL_import_mesh(const STLImportParams *import_params); diff --git a/source/blender/io/stl/importer/stl_import.cc b/source/blender/io/stl/importer/stl_import.cc index 96060400a9e..682e3864382 100644 --- a/source/blender/io/stl/importer/stl_import.cc +++ b/source/blender/io/stl/importer/stl_import.cc @@ -47,18 +47,7 @@ void stl_import_report_error(FILE *file) } } -void importer_main(const 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) +Mesh *read_stl_file(const STLImportParams &import_params) { FILE *file = BLI_fopen(import_params.filepath, "rb"); if (!file) { @@ -67,7 +56,7 @@ void importer_main(Main *bmain, RPT_ERROR, "STL Import: Cannot open file '%s'", import_params.filepath); - return; + return nullptr; } BLI_SCOPED_DEFER([&]() { fclose(file); }); @@ -84,15 +73,10 @@ void importer_main(Main *bmain, RPT_ERROR, "STL Import: Failed to read file '%s'", import_params.filepath); - return; + return nullptr; } 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]; - STRNCPY(ob_name, BLI_path_basename(import_params.filepath)); - BLI_path_extension_strip(ob_name); - Mesh *mesh = is_ascii_stl ? read_stl_ascii(import_params.filepath, import_params.use_facet_normal) : read_stl_binary(file, import_params.use_facet_normal); @@ -103,7 +87,7 @@ void importer_main(Main *bmain, RPT_ERROR, "STL Import: Failed to import mesh from file '%s'", import_params.filepath); - return; + return nullptr; } if (import_params.use_mesh_validate) { @@ -114,6 +98,29 @@ void importer_main(Main *bmain, BKE_mesh_validate(mesh, verbose_validate, false); } + return mesh; +} + +void importer_main(const 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) +{ + /* Name used for both mesh and object. */ + char ob_name[FILE_MAX]; + STRNCPY(ob_name, BLI_path_basename(import_params.filepath)); + BLI_path_extension_strip(ob_name); + + Mesh *mesh = read_stl_file(import_params); + Mesh *mesh_in_main = BKE_mesh_add(bmain, ob_name); BKE_mesh_nomain_to_mesh(mesh, mesh_in_main, nullptr); BKE_view_layer_base_deselect_all(scene, view_layer); diff --git a/source/blender/io/stl/importer/stl_import.hh b/source/blender/io/stl/importer/stl_import.hh index cb566a9d163..89c6aafa35a 100644 --- a/source/blender/io/stl/importer/stl_import.hh +++ b/source/blender/io/stl/importer/stl_import.hh @@ -12,6 +12,7 @@ struct bContext; struct Main; +struct Mesh; struct Scene; struct ViewLayer; @@ -19,6 +20,9 @@ namespace blender::io::stl { void stl_import_report_error(FILE *file); +/* Used from Geo nodes import for Mesh* access */ +Mesh *read_stl_file(const STLImportParams &import_params); + /* Main import function used from within Blender. */ void importer_main(const bContext *C, const STLImportParams &import_params); @@ -27,5 +31,4 @@ void importer_main(Main *bmain, Scene *scene, ViewLayer *view_layer, const STLImportParams &import_params); - } // namespace blender::io::stl diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index c5c2d06834e..da90f76cbb8 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -752,10 +752,11 @@ typedef struct UserDef_Experimental { char use_grease_pencil_version3; char enable_overlay_next; char use_new_volume_nodes; + char use_new_file_import_nodes; char use_shader_node_previews; char use_grease_pencil_version3_convert_on_load; char use_animation_baklava; - char _pad[3]; + char _pad[2]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index be02662ac81..67bf093b4bb 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -7401,6 +7401,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) RNA_def_property_ui_text( prop, "New Volume Nodes", "Enables visibility of the new Volume nodes in the UI"); + prop = RNA_def_property(srna, "use_new_file_import_nodes", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_text( + prop, "New File Import Nodes", "Enables visibility of the new File Import nodes in the UI"); + prop = RNA_def_property(srna, "use_shader_node_previews", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text( prop, "Shader Node Previews", "Enables previews in the shader node editor"); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 394f630e271..b3211e783e6 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -353,6 +353,7 @@ DefNode(GeometryNode, GEO_NODE_GRID_TO_MESH, 0, "GRID_TO_MESH", GridToMesh, "Gri DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image") DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture") DefNode(GeometryNode, GEO_NODE_IMAGE, def_geo_image, "IMAGE", InputImage, "Image", "Input image") +DefNode(GeometryNode, GEO_NODE_IMPORT_STL, 0, "IMPORT_STL", ImportSTL, "Import STL", "Import a mesh from an STL file") DefNode(GeometryNode, GEO_NODE_INDEX_OF_NEAREST, 0, "INDEX_OF_NEAREST", IndexOfNearest, "Index of Nearest", "Find the nearest element in a group. Similar to the \"Sample Nearest\" node") DefNode(GeometryNode, GEO_NODE_INDEX_SWITCH, def_geo_index_switch, "INDEX_SWITCH", IndexSwitch, "Index Switch", "Choose between an arbitrary number of values with an index") DefNode(GeometryNode, GEO_NODE_INPUT_ACTIVE_CAMERA, 0, "INPUT_ACTIVE_CAMERA", InputActiveCamera, "Active Camera", "Retrieve the scene's active camera") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 32e6a266036..81b44289277 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -20,6 +20,8 @@ set(INC ../../modifiers ../../render ../../windowmanager + ../../io/common + ../../io/stl # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) @@ -85,6 +87,7 @@ set(SRC nodes/node_geo_image.cc nodes/node_geo_image_info.cc nodes/node_geo_image_texture.cc + nodes/node_geo_import_stl.cc nodes/node_geo_index_of_nearest.cc nodes/node_geo_index_switch.cc nodes/node_geo_input_active_camera.cc @@ -237,6 +240,8 @@ set(LIB bf_nodes PRIVATE bf::intern::atomic PRIVATE bf::extern::fmtlib + PRIVATE bf_io_common + PRIVATE bf_io_stl ) if(WITH_BULLET) diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index 14c1ff1a9cd..ff837b41dfa 100644 --- a/source/blender/nodes/geometry/node_geometry_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -41,6 +41,13 @@ void search_link_ops_for_volume_grid_node(GatherLinkSearchOpParams ¶ms) } } +void search_link_ops_for_import_node(GatherLinkSearchOpParams ¶ms) +{ + if (U.experimental.use_new_file_import_nodes) { + nodes::search_link_ops_for_basic_node(params); + } +} + namespace enums { const EnumPropertyItem *attribute_type_type_with_socket_fn(bContext * /*C*/, diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index a5bf9f064af..dd02d168488 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -33,6 +33,7 @@ namespace blender::nodes { bool check_tool_context_and_error(GeoNodeExecParams ¶ms); void search_link_ops_for_tool_node(GatherLinkSearchOpParams ¶ms); void search_link_ops_for_volume_grid_node(GatherLinkSearchOpParams ¶ms); +void search_link_ops_for_import_node(GatherLinkSearchOpParams ¶ms); void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, const VArray &positions, diff --git a/source/blender/nodes/geometry/nodes/node_geo_import_stl.cc b/source/blender/nodes/geometry/nodes/node_geo_import_stl.cc new file mode 100644 index 00000000000..978e2949d92 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_import_stl.cc @@ -0,0 +1,90 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +#include "BKE_mesh.hh" + +#include "BKE_report.hh" +#include "BLI_string.h" + +#include "IO_stl.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_import_stl { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Path").default_value("").description("Path to a STL file"); + + b.add_output("Mesh"); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const std::string path = params.extract_input("Path"); + + if (path.empty()) { + params.set_default_remaining_outputs(); + return; + } + + STLImportParams import_params; + + STRNCPY(import_params.filepath, path.c_str()); + + import_params.forward_axis = IO_AXIS_NEGATIVE_Z; + import_params.up_axis = IO_AXIS_Y; + import_params.use_facet_normal = false; + import_params.use_scene_unit = false; + import_params.global_scale = 1.0f; + import_params.use_mesh_validate = true; + + ReportList reports; + BKE_reports_init(&reports, RPT_STORE); + import_params.reports = &reports; + + Mesh *mesh = STL_import_mesh(&import_params); + + LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { + NodeWarningType type; + + switch (report->type) { + case RPT_ERROR: + type = NodeWarningType::Error; + break; + default: + type = NodeWarningType::Info; + break; + } + + params.error_message_add(type, TIP_(report->message)); + } + + BKE_reports_free(&reports); + + if (mesh != nullptr) { + params.set_output("Mesh", GeometrySet::from_mesh(mesh)); + } + else { + params.set_default_remaining_outputs(); + } +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_IMPORT_STL, "Import STL", NODE_CLASS_INPUT); + + ntype.geometry_node_execute = node_geo_exec; + ntype.declare = node_declare; + ntype.gather_link_search_ops = search_link_ops_for_import_node; + + blender::bke::nodeRegisterType(&ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_import_stl