From 4e97def8a32147b245759f04ceceeefd937b43f5 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 6 Sep 2023 17:12:27 +0200 Subject: [PATCH] Geometry Nodes: Expose sharp edge status with builtin nodes Change the existing "Is Shade Smooth" node to be named "Is Face Smooth" and add a new "Is Edge Smooth" node. Also give the "Set Shade Smooth" node the ability to set face or edge smoothness. The fact that the nodes process "smooth" data reversed from the builtin "sharp" attributes can be reversed with versioning in a separate commit. While it's tempting to abstract the sharpness status into a single node, face and edge smoothness are accessed separately in edit mode, and the subtlety of interacting with data on different domains would make that confusing. Instead, a separate "Is Shade Smooth" node group asset will give all the sharp elements taking into account both builtin attributes. The fact that sharpness is stored separately on two domains makes the best design for simple operations non-obvious. For example, you should be able to remove all sharpness or make everything flat with a single node. The behavior depends on whether the two attributes exist and the combination of values between the domains. --- ![image](/attachments/c3f053c4-2b0f-44ac-9227-62071065fe56) ![image](/attachments/fd489fb3-314b-42ff-a5a9-e79578cbdfe7) Pull Request: https://projects.blender.org/blender/blender/pulls/112029 --- .../startup/bl_ui/node_add_menu_geometry.py | 1 + .../blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenkernel/BKE_node.h | 3 +- .../blenloader/intern/versioning_400.cc | 13 ++++ source/blender/makesrna/RNA_enum_items.hh | 1 + .../blender/makesrna/intern/rna_attribute.cc | 6 ++ source/blender/nodes/NOD_static_types.h | 3 +- source/blender/nodes/geometry/CMakeLists.txt | 3 +- ...mooth.cc => node_geo_input_edge_smooth.cc} | 10 +-- .../nodes/node_geo_input_face_smooth.cc | 31 +++++++++ .../nodes/node_geo_set_shade_smooth.cc | 65 +++++++++++++++---- 11 files changed, 116 insertions(+), 22 deletions(-) rename source/blender/nodes/geometry/nodes/{node_geo_input_shade_smooth.cc => node_geo_input_edge_smooth.cc} (56%) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_input_face_smooth.cc diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index d8c7195bf30..718a57dd001 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -360,6 +360,7 @@ class NODE_MT_geometry_node_GEO_MESH_READ(Menu): node_add_menu.add_node_type(layout, "GeometryNodeToolFaceSet") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceIsPlanar") node_add_menu.add_node_type(layout, "GeometryNodeInputShadeSmooth") + node_add_menu.add_node_type(layout, "GeometryNodeInputEdgeSmooth") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshIsland") node_add_menu.add_node_type(layout, "GeometryNodeInputShortestEdgePaths") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshVertexNeighbors") diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 02b0103087d..0bc34afb0b5 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 22 +#define BLENDER_FILE_SUBVERSION 23 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 3bd15931a37..129453f7c1b 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1205,7 +1205,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_INPUT_RADIUS 1105 #define GEO_NODE_INPUT_CURVE_TILT 1106 #define GEO_NODE_INPUT_CURVE_HANDLES 1107 -#define GEO_NODE_INPUT_SHADE_SMOOTH 1108 +#define GEO_NODE_INPUT_FACE_SMOOTH 1108 #define GEO_NODE_INPUT_SPLINE_RESOLUTION 1109 #define GEO_NODE_INPUT_SPLINE_CYCLIC 1110 #define GEO_NODE_SET_CURVE_RADIUS 1111 @@ -1312,6 +1312,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_TOOL_FACE_SET 2112 #define GEO_NODE_TOOL_SET_FACE_SET 2113 #define GEO_NODE_POINTS_TO_CURVES 2114 +#define GEO_NODE_INPUT_EDGE_SMOOTH 2115 /** \} */ diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 1a621863fce..49c19e726ce 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -34,6 +34,7 @@ #include "BLI_string_ref.hh" #include "BKE_armature.h" +#include "BKE_attribute.h" #include "BKE_effect.h" #include "BKE_grease_pencil.hh" #include "BKE_idprop.hh" @@ -1031,6 +1032,18 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 23)) { + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == GEO_NODE_SET_SHADE_SMOOTH) { + node->custom1 = ATTR_DOMAIN_FACE; + } + } + } + } + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/makesrna/RNA_enum_items.hh b/source/blender/makesrna/RNA_enum_items.hh index 5ba7d647479..273739a1c54 100644 --- a/source/blender/makesrna/RNA_enum_items.hh +++ b/source/blender/makesrna/RNA_enum_items.hh @@ -223,6 +223,7 @@ DEF_ENUM(rna_enum_attribute_type_items) DEF_ENUM(rna_enum_color_attribute_type_items) DEF_ENUM(rna_enum_attribute_type_with_auto_items) DEF_ENUM(rna_enum_attribute_domain_items) +DEF_ENUM(rna_enum_attribute_domain_edge_face_items) DEF_ENUM(rna_enum_attribute_domain_only_mesh_items) DEF_ENUM(rna_enum_attribute_domain_point_face_curve_items) DEF_ENUM(rna_enum_attribute_curves_domain_items) diff --git a/source/blender/makesrna/intern/rna_attribute.cc b/source/blender/makesrna/intern/rna_attribute.cc index 071adb69d19..7b130725df6 100644 --- a/source/blender/makesrna/intern/rna_attribute.cc +++ b/source/blender/makesrna/intern/rna_attribute.cc @@ -106,6 +106,12 @@ const EnumPropertyItem rna_enum_attribute_domain_point_face_curve_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +const EnumPropertyItem rna_enum_attribute_domain_edge_face_items[] = { + {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, + {ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + const EnumPropertyItem rna_enum_attribute_domain_without_corner_items[] = { {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index de1dfe7aafc..47a83a58ca6 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -357,7 +357,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "No DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "Retrieve a vector indicating the location of each element") DefNode(GeometryNode, GEO_NODE_INPUT_RADIUS, 0, "INPUT_RADIUS", InputRadius, "Radius", "Retrieve the radius at each point on curve or point cloud geometry") DefNode(GeometryNode, GEO_NODE_INPUT_SCENE_TIME, 0, "INPUT_SCENE_TIME", InputSceneTime, "Scene Time", "Retrieve the current time in the scene's animation in units of seconds or frames") -DefNode(GeometryNode, GEO_NODE_INPUT_SHADE_SMOOTH, 0, "INPUT_SHADE_SMOOTH", InputShadeSmooth, "Is Shade Smooth", "Retrieve whether each face is marked for smooth shading") +DefNode(GeometryNode, GEO_NODE_INPUT_FACE_SMOOTH, 0, "INPUT_SHADE_SMOOTH", InputShadeSmooth, "Is Face Smooth", "Retrieve whether each face is marked for smooth or sharp normals") DefNode(GeometryNode, GEO_NODE_INPUT_SHORTEST_EDGE_PATHS, 0, "SHORTEST_EDGE_PATHS", InputShortestEdgePaths, "Shortest Edge Paths", "") DefNode(GeometryNode, GEO_NODE_INPUT_SIGNED_DISTANCE, 0, "SIGNED_DISTANCE", InputSignedDistance, "Signed Distance", "Retrieve the signed distance field grid called 'distance' from a volume") DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_CYCLIC, 0, "INPUT_SPLINE_CYCLIC",InputSplineCyclic, "Is Spline Cyclic", "Retrieve whether each spline endpoint connects to the beginning") @@ -461,6 +461,7 @@ DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, 0, "VOLUME_TO_MESH", VolumeToMesh DefNode(GeometryNode, GEO_NODE_INTERPOLATE_CURVES, 0, "INTERPOLATE_CURVES", InterpolateCurves, "Interpolate Curves", "Generate new curves on points by interpolating between existing curves") DefNode(GeometryNode, GEO_NODE_POINTS_TO_CURVES, 0, "POINTS_TO_CURVES", PointsToCurves, "Points to Curves", "Split all points to curve by its group ID and reorder by weight") +DefNode(GeometryNode, GEO_NODE_INPUT_EDGE_SMOOTH, 0, "INPUT_EDGE_SMOOTH", InputEdgeSmooth, "Is Edge Smooth", "Retrieve whether each edge is marked for smooth or split normals") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 00ea1abbfd5..fb1e83f55c0 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -102,7 +102,8 @@ set(SRC nodes/node_geo_input_position.cc nodes/node_geo_input_radius.cc nodes/node_geo_input_scene_time.cc - nodes/node_geo_input_shade_smooth.cc + nodes/node_geo_input_edge_smooth.cc + nodes/node_geo_input_face_smooth.cc nodes/node_geo_input_shortest_edge_paths.cc nodes/node_geo_input_signed_distance.cc nodes/node_geo_input_spline_cyclic.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_shade_smooth.cc b/source/blender/nodes/geometry/nodes/node_geo_input_edge_smooth.cc similarity index 56% rename from source/blender/nodes/geometry/nodes/node_geo_input_shade_smooth.cc rename to source/blender/nodes/geometry/nodes/node_geo_input_edge_smooth.cc index d0699be904c..5716c993eda 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_shade_smooth.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_edge_smooth.cc @@ -4,7 +4,7 @@ #include "node_geometry_util.hh" -namespace blender::nodes::node_geo_input_shade_smooth_cc { +namespace blender::nodes::node_geo_input_edge_smooth_cc { static void node_declare(NodeDeclarationBuilder &b) { @@ -13,19 +13,19 @@ static void node_declare(NodeDeclarationBuilder &b) static void node_geo_exec(GeoNodeExecParams params) { - Field shade_smooth_field = AttributeFieldInput::Create("sharp_face"); - params.set_output("Smooth", fn::invert_boolean_field(shade_smooth_field)); + Field sharp = AttributeFieldInput::Create("sharp_edge"); + params.set_output("Smooth", fn::invert_boolean_field(std::move(sharp))); } static void node_register() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_INPUT_SHADE_SMOOTH, "Is Shade Smooth", NODE_CLASS_INPUT); + geo_node_type_base(&ntype, GEO_NODE_INPUT_EDGE_SMOOTH, "Is Edge Smooth", NODE_CLASS_INPUT); ntype.geometry_node_execute = node_geo_exec; ntype.declare = node_declare; nodeRegisterType(&ntype); } NOD_REGISTER_NODE(node_register) -} // namespace blender::nodes::node_geo_input_shade_smooth_cc +} // namespace blender::nodes::node_geo_input_edge_smooth_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_face_smooth.cc b/source/blender/nodes/geometry/nodes/node_geo_input_face_smooth.cc new file mode 100644 index 00000000000..640f2c845b9 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_face_smooth.cc @@ -0,0 +1,31 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_face_smooth_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output("Smooth").field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field sharp = AttributeFieldInput::Create("sharp_face"); + params.set_output("Smooth", fn::invert_boolean_field(std::move(sharp))); +} + +static void node_register() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_FACE_SMOOTH, "Is Face Smooth", NODE_CLASS_INPUT); + ntype.geometry_node_execute = node_geo_exec; + ntype.declare = node_declare; + nodeRegisterType(&ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_input_face_smooth_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc index f4e01887478..014f3a5d5f5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc @@ -4,6 +4,13 @@ #include "DNA_mesh_types.h" +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "NOD_rna_define.hh" + +#include "RNA_enum_types.hh" + #include "node_geometry_util.hh" namespace blender::nodes::node_geo_set_shade_smooth_cc { @@ -16,12 +23,23 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Geometry").propagate_all(); } +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "domain", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + node->custom1 = ATTR_DOMAIN_FACE; +} + /** * When the `sharp_face` attribute doesn't exist, all faces are considered smooth. If all faces * are selected and the sharp value is a constant false value, we can remove the attribute instead * as an optimization to avoid storing it and propagating it in the future. */ static bool try_removing_sharp_attribute(Mesh &mesh, + const StringRef name, const Field &selection_field, const Field &sharp_field) { @@ -36,48 +54,65 @@ static bool try_removing_sharp_attribute(Mesh &mesh, if (sharp) { return false; } - mesh.attributes_for_write().remove("sharp_face"); + mesh.attributes_for_write().remove(name); return true; } -static void set_sharp_faces(Mesh &mesh, - const Field &selection_field, - const Field &sharp_field) +static void set_sharp(Mesh &mesh, + const eAttrDomain domain, + const StringRef name, + const Field &selection_field, + const Field &sharp_field) { - if (mesh.faces_num == 0) { + const int domain_size = mesh.attributes().domain_size(domain); + if (mesh.attributes().domain_size(domain) == 0) { return; } - if (try_removing_sharp_attribute(mesh, selection_field, sharp_field)) { + if (try_removing_sharp_attribute(mesh, name, selection_field, sharp_field)) { return; } MutableAttributeAccessor attributes = mesh.attributes_for_write(); - AttributeWriter sharp_faces = attributes.lookup_or_add_for_write("sharp_face", - ATTR_DOMAIN_FACE); + AttributeWriter sharp = attributes.lookup_or_add_for_write(name, domain); - const bke::MeshFieldContext field_context{mesh, ATTR_DOMAIN_FACE}; - fn::FieldEvaluator evaluator{field_context, mesh.faces_num}; + const bke::MeshFieldContext field_context{mesh, domain}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(sharp_field, sharp_faces.varray); + evaluator.add_with_destination(sharp_field, sharp.varray); evaluator.evaluate(); - sharp_faces.finish(); + sharp.finish(); } static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); + const eAttrDomain domain = eAttrDomain(params.node().custom1); Field selection_field = params.extract_input>("Selection"); Field smooth_field = params.extract_input>("Shade Smooth"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (Mesh *mesh = geometry_set.get_mesh_for_write()) { - set_sharp_faces(*mesh, selection_field, fn::invert_boolean_field(smooth_field)); + set_sharp(*mesh, + domain, + domain == ATTR_DOMAIN_FACE ? "sharp_face" : "sharp_edge", + selection_field, + fn::invert_boolean_field(smooth_field)); } }); params.set_output("Geometry", std::move(geometry_set)); } +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum(srna, + "domain", + "Domain", + "", + rna_enum_attribute_domain_edge_face_items, + NOD_inline_enum_accessors(custom1)); +} + static void node_register() { static bNodeType ntype; @@ -85,7 +120,11 @@ static void node_register() geo_node_type_base(&ntype, GEO_NODE_SET_SHADE_SMOOTH, "Set Shade Smooth", NODE_CLASS_GEOMETRY); ntype.geometry_node_execute = node_geo_exec; ntype.declare = node_declare; + ntype.initfunc = node_init; + ntype.draw_buttons = node_layout; nodeRegisterType(&ntype); + + node_rna(ntype.rna_ext.srna); } NOD_REGISTER_NODE(node_register)