Blender V5.0
node_geo_set_material.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
7#include "DNA_curves_types.h"
8#include "DNA_mesh_types.h"
10#include "DNA_volume_types.h"
11
12#include "BKE_curves.hh"
13#include "BKE_grease_pencil.hh"
14#include "BKE_material.hh"
15
17
19
21{
22 b.use_custom_socket_order();
23 b.allow_any_socket_order();
24 b.add_input<decl::Geometry>("Geometry")
25 .supported_type({GeometryComponent::Type::Mesh,
26 GeometryComponent::Type::Volume,
27 GeometryComponent::Type::PointCloud,
28 GeometryComponent::Type::Curve,
29 GeometryComponent::Type::GreasePencil});
30 b.add_output<decl::Geometry>("Geometry")
31 .propagate_all()
32 .align_with_previous()
33 .description("Geometry to assign a material to");
34 b.add_input<decl::Bool>("Selection").default_value(true).hide_value().field_on_all();
35 b.add_input<decl::Material>("Material").optional_label();
36}
37
39 const fn::FieldContext &field_context,
40 const Field<bool> &selection_field,
41 MutableAttributeAccessor &attributes,
42 const AttrDomain domain,
43 Material *material)
44{
45 const int domain_size = attributes.domain_size(domain);
46 fn::FieldEvaluator selection_evaluator{field_context, domain_size};
47 selection_evaluator.set_selection(selection_field);
48 selection_evaluator.evaluate();
49 const IndexMask selection = selection_evaluator.get_evaluated_selection_as_mask();
50
51 if (selection.size() != attributes.domain_size(domain)) {
52 /* If the entire geometry isn't selected, and there is no material slot yet, add an empty
53 * slot so that the faces that aren't selected can still refer to the default material. */
55 }
56
57 int new_index = -1;
58 const int orig_materials_num = *BKE_id_material_len_p(id);
59 if (Material **materials = *BKE_id_material_array_p(id)) {
60 new_index = Span(materials, orig_materials_num).first_index_try(material);
61 }
62
63 if (new_index == -1) {
64 /* Append a new material index. */
65 new_index = orig_materials_num;
66 BKE_id_material_eval_assign(id, new_index + 1, material);
67 }
68
69 SpanAttributeWriter<int> indices = attributes.lookup_or_add_for_write_span<int>("material_index",
70 domain);
71 index_mask::masked_fill(indices.span, new_index, selection);
72 indices.finish();
73}
74
76{
77 Material *material = params.extract_input<Material *>("Material");
78 const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
79
80 GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
81
82 /* Only add the warnings once, even if there are many unique instances. */
83 bool no_faces_warning = false;
84 bool point_selection_warning = false;
85 bool volume_selection_warning = false;
86 bool curves_selection_warning = false;
87
88 geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
89 if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
90 if (mesh->faces_num == 0) {
91 if (mesh->verts_num > 0) {
92 no_faces_warning = true;
93 }
94 }
95 else {
96 const bke::MeshFieldContext field_context{*mesh, AttrDomain::Face};
97 MutableAttributeAccessor attributes = mesh->attributes_for_write();
99 &mesh->id, field_context, selection_field, attributes, AttrDomain::Face, material);
100 }
101 }
102 if (Volume *volume = geometry_set.get_volume_for_write()) {
103 BKE_id_material_eval_assign(&volume->id, 1, material);
104 if (selection_field.node().depends_on_input()) {
105 volume_selection_warning = true;
106 }
107 }
108 if (PointCloud *pointcloud = geometry_set.get_pointcloud_for_write()) {
109 BKE_id_material_eval_assign(&pointcloud->id, 1, material);
110 if (selection_field.node().depends_on_input()) {
111 point_selection_warning = true;
112 }
113 }
114 if (Curves *curves = geometry_set.get_curves_for_write()) {
115 BKE_id_material_eval_assign(&curves->id, 1, material);
116 if (selection_field.node().depends_on_input()) {
117 curves_selection_warning = true;
118 }
119 }
120 if (GreasePencil *grease_pencil = geometry_set.get_grease_pencil_for_write()) {
121 using namespace blender::bke::greasepencil;
122 for (const int layer_index : grease_pencil->layers().index_range()) {
123 Drawing *drawing = grease_pencil->get_eval_drawing(grease_pencil->layer(layer_index));
124 if (drawing == nullptr) {
125 continue;
126 }
128 if (curves.is_empty()) {
129 continue;
130 }
131
132 const bke::GreasePencilLayerFieldContext field_context{
133 *grease_pencil, AttrDomain::Curve, layer_index};
134 MutableAttributeAccessor attributes = curves.attributes_for_write();
135 assign_material_to_id_geometry(&grease_pencil->id,
136 field_context,
137 selection_field,
138 attributes,
140 material);
141 }
142 }
143 });
144
145 if (no_faces_warning) {
146 params.error_message_add(NodeWarningType::Info,
147 TIP_("Mesh has no faces for material assignment"));
148 }
149 if (volume_selection_warning) {
150 params.error_message_add(
152 TIP_("Volumes only support a single material; selection input cannot be a field"));
153 }
154 if (point_selection_warning) {
155 params.error_message_add(
157 TIP_("Point clouds only support a single material; selection input cannot be a field"));
158 }
159 if (curves_selection_warning) {
160 params.error_message_add(
162 TIP_("Curves only support a single material; selection input cannot be a field"));
163 }
164
165 params.set_output("Geometry", std::move(geometry_set));
166}
167
168static void node_register()
169{
170 static blender::bke::bNodeType ntype;
171
172 geo_node_type_base(&ntype, "GeometryNodeSetMaterial", GEO_NODE_SET_MATERIAL);
173 ntype.ui_name = "Set Material";
174 ntype.ui_description = "Assign a material to geometry elements";
175 ntype.enum_name_legacy = "SET_MATERIAL";
177 ntype.declare = node_declare;
180}
182
183} // namespace blender::nodes::node_geo_set_material_cc
Low-level operations for curves.
Low-level operations for grease pencil.
General operations, lookup, etc. for materials.
void BKE_id_material_eval_ensure_default_slot(ID *id)
short * BKE_id_material_len_p(ID *id)
Material *** BKE_id_material_array_p(ID *id)
void BKE_id_material_eval_assign(ID *id, int slot, Material *material)
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_SET_MATERIAL
#define TIP_(msgid)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
constexpr int64_t first_index_try(const T &search_value) const
Definition BLI_span.hh:387
int domain_size(const AttrDomain domain) const
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, AttrType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
bke::CurvesGeometry & strokes_for_write()
void set_selection(Field< bool > selection)
Definition FN_field.hh:383
IndexMask get_evaluated_selection_as_mask() const
Definition field.cc:817
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void foreach_real_geometry(bke::GeometrySet &geometry, FunctionRef< void(bke::GeometrySet &geometry_set)> fn)
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
static void assign_material_to_id_geometry(ID *id, const fn::FieldContext &field_context, const Field< bool > &selection_field, MutableAttributeAccessor &attributes, const AttrDomain domain, Material *material)
static void node_geo_exec(GeoNodeExecParams params)
static void node_declare(NodeDeclarationBuilder &b)
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
Definition DNA_ID.h:414
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362